{ +-------------------------------------------------------------+ }
{ |                                                             | }
{ |   GM-Software                                               | }
{ |   ===========                                               | }
{ |                                                             | }
{ |   Project: DB Projects                                      | }
{ |                                                             | }
{ |   Description: ODBC Data Access Objects.                    | }
{ |                                                             | }
{ |                                                             | }
{ |   Copyright (C) - 2000 - Gerrit Moeller.                    | }
{ |                                                             | }
{ |   Source code dstributed under MIT license.                 | }
{ |                                                             | }
{ |   See: https://www.gm-software.de                           | }
{ |                                                             | }
{ +-------------------------------------------------------------+ }

{$INCLUDE GMCompilerSettings.inc}

unit GMOdbcObj;

interface

uses

  {$IFDEF JEDIAPI}jwaWinType,{$ELSE}Windows,{$ENDIF} 
  GMStrDef, GMCommon, GMCollections, GMUnionValue, GMSql, GMOdbcAPI, GMIntf, GMActiveX; // GMZStrm


type

  TRecordCountStrategy = (csUseCountFromODBCDriver, csUseSelectCountStatement, csCountByLogarithmicPositioning);
  TRecordCountStrategies = set of TRecordCountStrategy;

  // caAllowDriverDialogs
  TOdbcConnectionAttribute = (caAsynchronOperations, caAutoCommitTransactions, caCaseSensitiveCatalogNames, caCompleteConnectionString);
  TOdbcConnectionAttributes = set of TOdbcConnectionAttribute;

  TOdbcAllowDriverDialogs = (adpNoDriverDialogs, adpPromtIfIncomplete, adpAlwaysPromt, adpAlwaysPromtReqiredOnly);

//TGMMaxStringLengthOrigin = (sloLengthInChars, sloLengthInBytes);


const

  cDfltODBCVersion = odbc3x;
  cDfltConnectionPooling = cpOff;
  cDfltCPSelectStartegy = ssExactMatch;
  cDfltAccessMode = amReadWrite;
  cDfltLoginTimeout = SQL_DEFAULT_LOGIN_TIMEOUT;
  cDfltStatementTimeout = SQL_DEFAULT_STATEMENT_TIMEOUT;
  cDfltCursorOrigin = coUseODBCCursorsIfNeeded;
  cDfltNetPacketSize = 0;                                  
  //cDfltTransactionIsolation = [tiReadCommitted];
  cDfltUpdateStrategy = usMinimalLock;
  cDfltCursorSensitivity = csUseDriverDefault; // csUnspecified;
  cDfltCursorType = ctKeyset;
  cDfltKeysetSize = 0;
  cDfltMaxRecords = 0;
  cDfltUsername = '';
  cDfltUseConnectionValue = True;
  cDfltEnableTracing = Boolean(SQL_OPT_TRACE_DEFAULT);
  cDfltTranslateOptions = SQL_DEFAULT_TRANSLATE_OPTIONS;
  cDfltMaxFieldDataSize = SQL_DEFAULT_MAX_DATA_SIZE;
  cDfltAsyncOperations = Boolean(SQL_ASYNC_ENABLE_DEFAULT);
  cDfltCaseSensitiveCatalogNames = Boolean(SQL_METADATA_ID_DEFAULT);
  cDfltDSNFmtStr = '"%s" [%s]';
//cDfltUserDSNFmtStr = 'User DSN: "%s" [%s]';
//cDfltSystemDSNFmtStr = 'System DSN: "%s" [%s]';
  CPropEnumDSNFmt = cStrCnStrDSN + '=%s';
  cDfltRecordCountStrategies = [csUseSelectCountStatement]; // csUseCountFromODBCDriver
//cDfltMaxStringLengthOrigin = sloLengthInChars;
//cDfltMaxStringLengthCharSizeFactor = 1.0;

  cDfltUseSQLEscapeSequences = not Boolean(SQL_NOSCAN_DEFAULT);
  cDfltPositionedUpdateSimulation = pusUseDriverDefault; // pusAllowNonUniqueUpdate;
  cDfltConnectionAttributes = [caAutoCommitTransactions];
  cDfltDriverDialogs = adpPromtIfIncomplete;

//cDfltBlobCompressionType = 0; // ctMaximum;

  cDfltRSAttributes = [raAutoSaveChanges, raAutoEdit, raConfrimDeletions, raStripTrailingBlanks];  
  cSimplestRecordsetAttributes = [raAutoSaveChanges, raAutoEdit];

  cRowArraySize = 1;

  cInvalidBufferSize = -1;

  cNoCompression = 0;

  cGetDataBufSizeInChars = $8000; // <- 32 K chars


type

  TGMOdbcConnectProperties = class;
  TGMOdbcConnection = class;
  TGMOdbcEnvironment = class;
  TGMOdbcField = class;
  TGMOdbcStatementBase = class;
  TGMOdbcStatement = class;
  TGMOdbcRecordsetBase = class;
  TGMOdbcRecordset = class;


  IGMOdbcConnectProperties = interface(IUnknown)
    ['{5C8594D1-5972-411F-822B-9D0D8D61CF77}']
    function Obj: TGMOdbcConnectProperties;
  end;

  TGMOdbcConnectProperties = class(TGMRefCountedObj, IGMLoadStoreData, IGMAssignFromObj, IGMAssignToObj, IGMAssignToIntf,
                                                     IGMAssignFromIntf, IGMGetName, IGMOdbcConnectProperties)
   protected
    FOwners: array of TObject;
    FAccessMode: TAccessMode;
    //FAsynchronOperations: Boolean;
    FUsername: TGMString;
    FPassword: TGMString;
    FConnectionString: TGMString;
    FTimeoutForStatements: SQLUINTEGER;
    FTimeoutForLogin: SQLUINTEGER;
    FCursorOrigin: TCursorOrigin;
    FTransactionIsolation: TTransactionIsolations;
    FAttributes: TOdbcConnectionAttributes;
    FAllowDriverDialogs: TOdbcAllowDriverDialogs;

//  procedure CheckIsInactive(const ACallingName: TGMString);

//  procedure SetAccessMode(const AValue: TAccessMode);
//  procedure SetTimeoutForStatements(const AValue: SQLUINTEGER);
//  procedure SetTimeoutForLogin(const AValue: SQLUINTEGER);
//  procedure SetConnectionString(const AValue: TGMString);
//  procedure SetCursorOrigin(const AValue: TCursorOrigin);
    procedure SetTransactionIsolation(const AValue: TTransactionIsolations);
    procedure SetAttributes(const AValue: TOdbcConnectionAttributes);

   public
    constructor Create(const ARefLifeTime: Boolean = False); overload; override;
    constructor CreateFromObj(const ASource: TObject; const ARefLifeTime: Boolean = False); reintroduce; overload;
    constructor CreateFromIntf(const ASource: IUnknown; const ACryptCtrlData: PGMCryptCtrlData = nil; const ARefLifeTime: Boolean = False); reintroduce; overload;
    constructor CreateOwned(const AOwner: TObject; const ARefLifeTime: Boolean = False); reintroduce; overload;

    function Obj: TGMOdbcConnectProperties;
    function GetName: TGMString; stdcall;
    function DatabaseName: TGMString;
    procedure SetDefaultValues;

    function AddOwner(const AOwner: TObject): TObject;
    function FindOwner(const AOwner: TObject; var AIdx: LongInt): Boolean;
    procedure RemoveOwner(const AOwner: TObject);

    procedure AssignFromObj(const ASource: TObject); stdcall;
    procedure AssignToObj(const ADest: TObject); stdcall;

    procedure AssignToIntf(const ADest: IUnknown); stdcall;
    procedure AssignFromIntf(const ASource: IUnknown); stdcall;

    procedure LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); virtual; stdcall;
    procedure StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); virtual; stdcall;

   //published
    property AccessMode: TAccessMode read FAccessMode write FAccessMode default cDfltAccessMode; // SetAccessMode
    property Attributes: TOdbcConnectionAttributes read FAttributes write SetAttributes default cDfltConnectionAttributes;
    property AllowDriverDialogs: TOdbcAllowDriverDialogs read FAllowDriverDialogs write FAllowDriverDialogs;
    //property AsynchronOperations: Boolean read FAsynchronOperations Write FAsynchronOperations default cDfltAsyncOperations;
    property Username: TGMString read FUsername write FUsername;
    property Password: TGMString read FPassword write FPassword;
    property ConnectionString: TGMString read FConnectionString write FConnectionString; // SetConnectionString;
    property TimeoutForStatements: SQLUINTEGER read FTimeoutForStatements write FTimeoutForStatements default cDfltStatementTimeout; // SetTimeoutForStatements
    property TimeoutForLogin: SQLUINTEGER read FTimeoutForLogin write FTimeoutForLogin default cDfltLoginTimeout; // SetTimeoutForLogin
    property CursorOrigin: TCursorOrigin read FCursorOrigin write FCursorOrigin default cDfltCursorOrigin; // SetCursorOrigin
    property TransactionIsolation: TTransactionIsolations read FTransactionIsolation write SetTransactionIsolation default cSqlDefaultTransactionIsolation;
  end;


  TOdbcHandleAllocObj = class(TGMHandleActivateObj, IGMGetHandleType, IGMCriticalSection)
   protected
    FCriticalSection: IGMCriticalSection;

    procedure ReleaseHandle; override;
    function GetHandleType: LongWord; virtual; stdcall; abstract;

   public
    constructor Create(const ArefLifeTime: Boolean = False); overload; override;
    property CriticalSection: IGMCriticalSection read FCriticalSection implements IGMCriticalSection;
    property HandleType: LongWord read GetHandleType;

   //published
    property ActivationProperties;
    property OnBeforeActiveChange;
    property OnAfterActiveChange;
    property CallEventsWhenDisabled;
  end;


  TTransactionSupport = (txnNone, txnDMLOnly, txnDDLCommit, txnDDLIgnore, txnAll);

  TGMOdbcTransactedObj = class(TOdbcHandleAllocObj, IGMTransactions)
   protected
    FTransactionLevel: LongInt;

    function GetTransactionSupport: TTransactionSupport; virtual;

   public
    function GetTransactionLevel: LongInt; virtual; stdcall;
    procedure BeginTransaction; virtual; stdcall;
    procedure CommitTransaction; virtual; stdcall;
    procedure RollbackTransaction; virtual; stdcall;

    property TransactionSupport: TTransactionSupport read GetTransactionSupport;
    property TransactionLevel: LongInt read GetTransactionLevel;
  end;


  TGMOdbcEnvironment = class(TGMOdbcTransactedObj)
   protected
    FODBCVersion: TOdbcVersion;
    FConnectionPooling: TOdbcConnectionPooling;
    FConnectionPoolSelectStrategy: TOdbcConnectionPoolSelectStrategy;

    procedure SetODBCConnectionPoolSelectStrategy(const AValue: TOdbcConnectionPoolSelectStrategy);
    procedure SetODBCConnectionPooling(const AValue: TOdbcConnectionPooling);
    procedure SetODBCODBCVersion(const AValue: TOdbcVersion);

    procedure SetODBCVersion(const AValue: TOdbcVersion);
    procedure SetConnectionPooling(const AValue: TOdbcConnectionPooling);
    procedure SetConnectionPoolSelectStrategy(const AValue: TOdbcConnectionPoolSelectStrategy);

    procedure AllocHandle; override;

   public
    constructor Create(const ARefLifeTime: Boolean); overload; override; 
    constructor Create(const AOdbcVersion: TOdbcVersion;
                       const AConnectionPooling: TOdbcConnectionPooling;
                       const AConnectionPoolSelectStrategy: TOdbcConnectionPoolSelectStrategy;
                       const ARefLifeTime: Boolean); reintroduce; overload;

    procedure AssignFromObj(const Source: TObject);
    function GetHandleType: LongWord; override;

   //published
    property ODBCVersion: TOdbcVersion read FODBCVersion write SetODBCVersion default cDfltODBCVersion;
    property ConnectionPooling: TOdbcConnectionPooling read FConnectionPooling write SetConnectionPooling default cDfltConnectionPooling;
    property ConnectionPoolSelectStrategy: TOdbcConnectionPoolSelectStrategy read FConnectionPoolSelectStrategy write SetConnectionPoolSelectStrategy default cDfltCPSelectStartegy;
  end;


  TOdbcComponentSubProperty = class(TGMRefCountedObj)
   protected
    FOwner: TGMActivatableObject;
    function ODBCConnection: TGMOdbcConnection;
    function ODBCStatement: TGMOdbcStatementBase;

   public
    constructor Create(const AOwner: TGMActivatableObject); reintroduce; overload;
  end;


  TOdbcTraceProperties = class(TOdbcComponentSubProperty)
   protected
    FEnableTracing: Boolean;
    FTraceFilename: TGMString;

    procedure SetODBCEnableTracing(const AValue: Boolean);
    procedure SetODBCTraceFilename(const AValue: TGMString);

    procedure SetEnableTracing(const AValue: Boolean);
    procedure SetTraceFilename(const AValue: TGMString);

   public
    constructor Create(const AOwner: TGMActivatableObject); reintroduce; overload;

    function IsEqualTo(const AValue: TOdbcTraceProperties): Boolean;
    procedure AssignFromObj(const Source: TObject);
    procedure SetODBCValues;

   //published
    property EnableTracing: Boolean read FEnableTracing write SetEnableTracing default cDfltEnableTracing;
    property TraceFilename: TGMString read FTraceFilename write SetTraceFilename;
  end;


  TOdbcTranslateProperties = class(TOdbcComponentSubProperty)
   protected
    FTranslationDLLFilename: TGMString;
    FTranslationAttributes: SQLUINTEGER;

    procedure SetODBCTranslationDLLFilename(const AValue: TGMString);
    procedure SetODBCTranslationAttributes(const AValue: PtrUInt);

    procedure SetTranslationDLLFilename(const AValue: TGMString);
    procedure SetTranslationAttributes(const AValue: SQLUINTEGER);

   public
    constructor Create(const AOwner: TOdbcHandleAllocObj); reintroduce; overload;

    function IsEqualTo(const AValue: TOdbcTranslateProperties): Boolean;
    procedure AssignFromObj(const Source: TObject);
    procedure SetODBCValues;

   //published
    property TranslationDLLFilename: TGMString read FTranslationDLLFilename write SetTranslationDLLFilename;
    property TranslationAttributes: SQLUINTEGER read FTranslationAttributes write SetTranslationAttributes default cDfltTranslateOptions;
  end;


  TGMOdbcConnection = class(TGMOdbcTransactedObj, IGMGetName, IGMAskBoolean, IGMExecuteSQL, IGMOdbcConnectProperties,
                                                  IGMLoadStoreData, IGMSqlSyntaxElements)
   protected
    FDataSourceCatalog: TGMString;
    FNetworkPacketSize: SQLUINTEGER;
    FTraceProperties: TOdbcTraceProperties;
    FTranslateProperties: TOdbcTranslateProperties;
    FInternalConnected: Boolean;
    FProperties: IGMOdbcConnectProperties;
    FLoadStore: IGMLoadStoreData;

    function GetODBCEnvironmentIntf: IUnknown;
    function GetODBCEnvironment: TGMOdbcEnvironment;

    procedure SetODBCTimeoutForLogin(const AValue: SQLULEN);
    procedure SetODBCTimeoutForStatements(const AValue: SQLULEN);
    procedure SetODBCAccessMode(const AValue: TAccessMode);
    procedure SetODBCDataSorceCatalog(const AValue: TGMString);
    procedure SetODBCCursorOrigin(const AValue: TCursorOrigin);
    procedure SetODBCAsynchronOperations(const AValue: Boolean);
    procedure SetODBCAutoCommitTransactions(const AValue: Boolean);
    procedure SetODBCDriverDialogPrntWnd(const AValue: HWnd);
    procedure SetODBCCaseSensitiveCatalogNames(const AValue: Boolean);
    procedure SetODBCNetworkPacketSize(const AValue: SQLULEN);
    procedure SetODBCTransactionIsolation(const AValue: TTransactionIsolations);

    function GetNetworkPacketSize: SQLUINTEGER;
    function GetTransactionSupport: TTransactionSupport; override;

    procedure SetODBCEnvironment(const AValue: TGMOdbcEnvironment);
    procedure SetODBCEnvironmentIntf(const AValue: IUnknown);
    //procedure SetAsynchronOperations(const AValue: Boolean);
    //procedure SetAutoCommitTransactions(const AValue: Boolean);
    //procedure SetAllowDriverDialogs(const AValue: Boolean);
    procedure SetDataSourceCatalog(const AValue: TGMString);
    //procedure SetCaseSensitiveCatalogNames(const AValue: Boolean);
    procedure SetNetworkPacketSize(const AValue: SQLUINTEGER);
    procedure SetTraceProperties(const AValue: TOdbcTraceProperties);
    procedure SetTranslateProperties(const AValue: TOdbcTranslateProperties);

    procedure OnBeforeIntfSourceChange(const AOldSource, ANewSource: IUnknown);
    function AskBoolean(const AValueId: LongInt): LongInt; virtual; stdcall;

    procedure AllocHandle; override;
    procedure ReleaseHandle; override;

   public
    constructor Create(const ArefLifeTime: Boolean = False); overload; override;
    constructor Create(const AOdbcEnvironment: IUnknown; const AConnectionString: TGMString = ''; const ARefLifeTime: Boolean = True); reintroduce; overload;
    destructor Destroy; override;
    function GetName: TGMString; virtual; stdcall;

    procedure SetSharedProperties(const AProperties: IGMOdbcConnectProperties);
    //procedure AssignFromObj(const Source: TObject);
    function GetHandleType: LongWord; override;
    function SqlIdentifierQuoteChar: TGMString;
    function SqlDateTimeFormatStr: TGMString;

    function ExecuteSQL(const ASQL: TGMString): SQLLEN; virtual;

    function ConnetionAlive: Boolean;

    property InternalConnected: Boolean read FInternalConnected;
    property ODBCEnvironmentIntf: IUnknown read GetODBCEnvironmentIntf write SetODBCEnvironmentIntf;

   //published
    property ODBCEnvironment: TGMOdbcEnvironment read GetODBCEnvironment write SetODBCEnvironment;
    property DataSourceCatalog: TGMString read FDataSourceCatalog write SetDataSourceCatalog;
    property NetworkPacketSize: SQLUINTEGER read GetNetworkPacketSize write SetNetworkPacketSize default cDfltNetPacketSize;
    property TraceProperties: TOdbcTraceProperties read FTraceProperties write SetTraceProperties;
    property TranslateProperties: TOdbcTranslateProperties read FTranslateProperties write SetTranslateProperties;

    property Properties: IGMOdbcConnectProperties read FProperties implements IGMOdbcConnectProperties;
    property LoadStore: IGMLoadStoreData read FLoadStore implements IGMLoadStoreData;
//  property Name: IGMGetName read FName implements IGMGetName;
  end;


  TOdbcBlobStreamBase = class(TGMSequentialIStream)
   protected
    FOwner: TObject;
    //FPosition: LongInt;
    FStatementHandle: SQLHANDLE;

   public
    constructor Create(const AMode: DWORD; const AOwner: TObject; const AStatementHandle: SQLHANDLE; const ARefLifeTime: Boolean = True); reintroduce; overload;
  end;


  TOdbcBlobReadStream = class(TOdbcBlobStreamBase)
   protected
    FStartData: AnsiString;
    FDataType: TGMDBColumnDataType;
    FColumnPosition: LongInt;

   public
    constructor Create(const AOwner: TObject; const ADataType: TGMDBColumnDataType; const AColumnPosition: LongInt;
                       const AStatementHandle: SQLHANDLE; const ATotalSize: Int64; const   AStartData: AnsiString; const ARefLifeTime: Boolean = True); reintroduce; overload;
    procedure InternalRead(pv: Pointer; cb: LongWord; var pcbRead: LongWord); override;
    procedure InternalWrite(pv: Pointer; cb: LongWord; var pcbWritten: LongWord); override;
    //function Seek(dlibMove: Int64; dwOrigin: LongInt; out libNewPosition: Int64): HResult; override;
  end;


  TOdbcBlobWriteStream = class(TOdbcBlobStreamBase)
   public
    procedure InternalRead(pv: Pointer; cb: LongWord; var pcbRead: LongWord); override;
    procedure InternalWrite(pv: Pointer; cb: LongWord; var pcbWritten: LongWord); override;
    function Seek(dlibMove: Int64; dwOrigin: LongInt; libNewPosition: PInt64): HResult; override;
  end;


  TODBCFieldStateValueBuffer = class(TGMFieldValueBuffer, IGMAskInteger)
   protected
    FDataLength: SQLLEN;
//  FFirstDataRead: Boolean;

    function OwnerRecordset: TGMOdbcRecordsetBase;
    //procedure InternalSetSize(ANewSize: Int64); override;
//  procedure AccessBufferContents(const AAccessMode: TGMValueBufferAccessMode; const ANewSize: LongInt = 0);
    function GetDataLength: PtrInt; override;
    procedure SetDataLength(const AValue: PtrInt); override;
    function InternalGetUnionValue: RGMUnionValue; override;
    procedure InternalSetUnionValue(const AValue: RGMUnionValue); override;
    procedure InternalSetNullValue; override;
    function InternalBuildDisplayText: TGMString; override;

   public
    constructor Create(const AOwner: TObject;
                       const ADataType: TGMDBColumnDataType;
                       const AZeroInit: Boolean = False;
                       const AFreeMemoryOnDestroy: Boolean = True;
                       const ARefLifeTime: Boolean = False); override;

    function WriteAt(ulOffset: Int64; pv: Pointer; cb: LongInt; pcbWritten: PLongint): HResult; override;
    function AskInteger(const AValueId: LongInt): LongInt; virtual; stdcall;

    //function ODBCFetchSuccess: Boolean;
    function IsNull: Boolean; override;

    procedure Invalidate(const AResetOffset, ASetToNULL: Boolean); override;
    function CalculateBufferSize: LongInt; override;
//  procedure SetUnionValue(const AValue: RGMUnionValue); override;

    //procedure ReadFirstData; virtual;
    procedure AssignFromIntf(const Source: IUnknown); override;
    function IsBookmarkColumn: Boolean; virtual;
  end;                                                                                                                     


  TODBCFieldValueBuffer = class(TODBCFieldStateValueBuffer)
   protected
    FSavedDataLength: Integer;
//  FFirstDataSize: Integer;
    FBufferBound: Boolean;

    procedure OnAfterRealloc(const Sender: TObject); override;

   public
//  constructor CreateFieldBuffer(const AOwner: TObject;
//                                const ADataType: TGMDBColumnDataType;
//                                const AColumnPosition: LongInt;
//                                const AFieldName: TGMString;
//                                const ASizeInBytes: Cardinal;
//                                const AMaxStrLength: Cardinal;
//                                const AStatementHandle: THandle); override;
    destructor Destroy; override;

    function IsFixedBufferSize: Boolean; override;
    procedure SetupDataLengthForUpdate;
    procedure RestoreDataLength;
    function IsBookmarkColumn: Boolean; override;
    function CalculateBufferSize: LongInt; override;
//  procedure InvalidateAndReadFirstData(const AResetOffset: Boolean); virtual;
    //procedure Invalidate(const AResetOffset: Boolean); override;
    //procedure InternalFetchData(const AForDisplayText: Boolean = False); override;
//  procedure FetchBlobData; virtual;
    //procedure ReadFirstData; override;
//  procedure StoreBlobData(const CompressionType: Integer);
    procedure StoreBlobData;
    procedure BindBuffer(const ABind: Boolean); virtual;
    //procedure BindBlobBuffer(const ABind: Boolean);
//  procedure AssignFromIntf(const ASource: IUnknown); override;
    function CreateValueStream(const AMode: DWORD): ISequentialStream; override;
  end;


  TGMOdbcField = class(TGMDBField) // IGMAccessValueBuffer
   protected
    function ValueBufferCreateClass: TGMFieldValueBufferClass; override;
//  function AccessValueBuffer(const AAccessMode: LongInt; const AIID: TGUID; out Intf; const AValueBufferInstance: LongInt = Ord(vbiValue)): HResult; virtual; stdcall;

   public
//  procedure AfterActiveChange(const ANewActive: Boolean); override;
//  procedure AfterPositionChange; override;
//  procedure AfterOperation(const AOperation: Integer; const AParameter: IUnknown = nil); override;

   public
    //constructor Create(const ARecordset: TObject; const ACreateData: RGMFieldCreateData); override;
    //destructor Destroy; override;

    function ODBCValueBuffer(const AValueBufferInstance: EGMValueBufferInstance): TODBCFieldValueBuffer;
    procedure BindBuffer(const ABind: Boolean);

    procedure StoreBlobData;
    procedure SetupDataLengthForUpdate;
    procedure RestoreDataLength;

    procedure SwapBufferMap; override;
  end;

  TGMOdbcFieldClass = class of TGMOdbcField;


  TStmtCNAwareSubProperty = class(TOdbcComponentSubProperty)
   protected
    FUseValueFromConnection: Boolean;

    procedure SetUseValueFromConnection(const AValue: Boolean);

   public
    constructor Create(const AOwner: TGMActivatableObject); reintroduce; overload;
    function IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean; virtual;
    procedure AssignFromObj(const ASource: TObject);
    procedure SetODBCValue; virtual; abstract;
    procedure ValueFromConnectionChanged; virtual; abstract;

    procedure LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); virtual; stdcall;
    procedure StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); virtual; stdcall;

   //published
    property UseValueFromConnection: Boolean read FUseValueFromConnection write SetUseValueFromConnection default cDfltUseConnectionValue;
  end;


  TStmtTimeoutProperties = class(TStmtCNAwareSubProperty)
   protected
    FTimeoutForStatements: SQLUINTEGER;

    function GetTimeoutForStatements: SQLUINTEGER;
    procedure SetODBCTimeoutForStatements(const AValue: PtrUInt);
    procedure SetTimeoutForStatements(const AValue: SQLUINTEGER);

   public
    constructor Create(const AOwner: TGMActivatableObject = nil); reintroduce; overload;
    function IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean; override;
    procedure AssignFromObj(const Source: TObject);
    procedure SetODBCValue; override;
    procedure ValueFromConnectionChanged; override;

    procedure LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;
    procedure StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;

   //published
    property TimeoutForStatements: SQLUINTEGER read GetTimeoutForStatements write SetTimeoutForStatements default cDfltStatementTimeout;
  end;


  TStmtAsyncOperationProperties = class(TStmtCNAwareSubProperty)
   protected
    FAsynchronOperations: Boolean;

    function GetAsynchronOperations: Boolean;
    procedure SetODBCAsynchronOperations(const AValue: Boolean);
    procedure SetAsynchronOperations(const AValue: Boolean);

   public
    constructor Create(const AOwner: TGMActivatableObject = nil); reintroduce; overload;
    function IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean; override;
    procedure AssignFromObj(const Source: TObject); 
    procedure SetODBCValue; override;
    procedure ValueFromConnectionChanged; override;

    procedure LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;
    procedure StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;

   //published
    property AsynchronOperations: Boolean read GetAsynchronOperations write SetAsynchronOperations default cDfltAsyncOperations;
  end;


  TStmtCatalogNameCaseProperties = class(TStmtCNAwareSubProperty)
   protected
    FCaseSensitiveCatalogNames: Boolean;

    function GetCaseSensitiveCatalogNames: Boolean;
    procedure SetODBCCaseSensitiveCatalogNames(const AValue: Boolean);
    procedure SetCaseSensitiveCatalogNames(const AValue: Boolean);

   public
    constructor Create(const AOwner: TGMActivatableObject = nil); reintroduce; overload;
    function IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean; override;
    procedure AssignFromObj(const Source: TObject); 
    procedure SetODBCValue; override;
    procedure ValueFromConnectionChanged; override;

    procedure LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;
    procedure StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;

   //published
    property CaseSensitiveCatalogNames: Boolean read GetCaseSensitiveCatalogNames write SetCaseSensitiveCatalogNames default cDfltCaseSensitiveCatalogNames;
  end;


  TGMOdbcStatementProperties = class(TGMRefCountedObj, IGMLoadStoreData, IGMAssignFromObj, IGMAssignToObj, IGMAssignToIntf, IGMAssignFromIntf)
   protected
    FAssignSQL: Boolean;
    FSQL: TGMString;
    FReExecuteAfterSQLChange: Boolean;
    FUseSQLEscapeSequences: Boolean;
    FTimedReExecutionDelay: Integer;
    FTimeoutProperties: TStmtTimeoutProperties;
    FAsyncOperationProperties: TStmtAsyncOperationProperties;
    FCatalogNameCaseProperties: TStmtCatalogNameCaseProperties;

    procedure SetTimeoutProperties(const AValue: TStmtTimeoutProperties);
    procedure SetAsyncOperationProperties(const AValue: TStmtAsyncOperationProperties);
    procedure SetCatalogNameCaseProperties(const AValue: TStmtCatalogNameCaseProperties);

    procedure SetDefaultValues; virtual;

   public
    constructor Create(const AAssignSQL: Boolean = True; const ARefLifeTime: Boolean = False); reintroduce; overload;
    constructor Create(const ASource: TObject; const AAssignSQL: Boolean = True; const ARefLifeTime: Boolean = False); reintroduce; overload;
    constructor Create(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil; const AAssignSQL: Boolean = True; const ARefLifeTime: Boolean = False); reintroduce; overload;
    destructor Destroy; override;

    procedure AssignFromObj(const ASource: TObject); virtual; stdcall;
    procedure AssignToObj(const ADest: TObject); virtual; stdcall;

    procedure AssignToIntf(const ADest: IUnknown); stdcall;
    procedure AssignFromIntf(const ASource: IUnknown); stdcall;

    procedure LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); virtual; stdcall;
    procedure StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); virtual; stdcall;

    property AssignSQL: Boolean read FAssignSQL;

   //published
    property SQL: TGMString read FSQL write FSQL;
    property ReExecuteAfterSQLChange: Boolean read FReExecuteAfterSQLChange write FReExecuteAfterSQLChange default cDfltReExecuteAfterSQLChange;
    property UseSQLEscapeSequences: Boolean read FUseSQLEscapeSequences write FUseSQLEscapeSequences default cDfltUseSQLEscapeSequences;
    property TimedReExecutionDelay: Integer read FTimedReExecutionDelay write FTimedReExecutionDelay default cDfltReExecutionDelay;
    property TimeoutProperties: TStmtTimeoutProperties read FTimeoutProperties write SetTimeoutProperties;
    property AsyncOperationProperties: TStmtAsyncOperationProperties read FAsyncOperationProperties write SetAsyncOperationProperties;
    property CatalogNameCaseProperties: TStmtCatalogNameCaseProperties read FCatalogNameCaseProperties write SetCatalogNameCaseProperties;
  end;


  TRecordStausArray = array [0..cRowArraySize-1] of SQLUSMALLINT;

  TGMOdbcStatementBase = class(TGMSqlStatementBase, IGMGetHandleType)
   protected
    FUseSQLEscapeSequences: Boolean;

    FTimeoutProperties: TStmtTimeoutProperties;
    FAsyncOperationProperties: TStmtAsyncOperationProperties;
    FCatalogNameCaseProperties: TStmtCatalogNameCaseProperties;

    function GetOdbcConnection: TGMOdbcConnection;

    procedure SetUseSQLEscapeSequences(const AValue: Boolean);
    procedure SetTimeoutProperties(const AValue: TStmtTimeoutProperties);
    procedure SetAsyncProperties(const AValue: TStmtAsyncOperationProperties);
    procedure SetCatalogNameCaseProperties(const AValue: TStmtCatalogNameCaseProperties);
    procedure SetODBCConnection(const AValue: TGMOdbcConnection);

    procedure AllocHandle; override;
    procedure ReleaseHandle; override;
    procedure SetODBCAttributes; virtual;
    procedure APIExecuteSQL(const ASQLText: TGMString); override;

   public
    constructor Create(const ARefLifeTime: Boolean); overload; override;
    destructor Destroy; override;

    procedure AssignFromObj(const Source: TObject); override;
    function GetHandleType: LongWord; stdcall;
    procedure CancelExecution;
    function StillExecuting: Boolean;
    function AffectedRecordCount: SQLLEN; 

    property HandleType: LongWord read GetHandleType;

   //published
    property ActivationProperties;
    property OnBeforeActiveChange;
    property OnAfterActiveChange;
    property CallEventsWhenDisabled;
    property ODBCConnection: TGMOdbcConnection read GetOdbcConnection write SetODBCConnection;
    property TimeoutProperties: TStmtTimeoutProperties read FTimeoutProperties write SetTimeoutProperties;
    property AsynchronOperationProperties: TStmtAsyncOperationProperties read FAsyncOperationProperties write SetAsyncProperties;
    property CatalogNameCaseProperties: TStmtCatalogNameCaseProperties read FCatalogNameCaseProperties write SetCatalogNameCaseProperties;
    property TimedReExecutionDelay: Integer read GetTimedReExecutionDelay write SetTimedReExecutionDelay default cDfltReExecutionDelay;
    property UseSQLEscapeSequences: Boolean read FUseSQLEscapeSequences write SetUseSQLEscapeSequences default cDfltUseSQLEscapeSequences;
    property OnAfterSQLChange;
  end;


  TGMOdbcStatement = class(TGMOdbcStatementBase)
   //published
    property SQL;
    property UseSQLEscapeSequences;
  end;


  TOdbcFieldState = class(TGMFieldStateHolder)
   protected
    function ValueBufferCreateClass: TGMValueBufferClass; override;
  end;


  TGMOdbcRecordsetState = class(TGMRecordsetStateHolder)
   protected
    function FieldStateCreateClass: TGMFieldStateCreateClass; override;
  end;


  TGMOdbcRecordsetProperties = class;

  IGMOdbcRecordsetProperties = interface(IGMAssignToObj)
    ['{02C7DF42-9CCF-45C8-8A34-B7101E588EB7}']
    function Obj: TGMOdbcRecordsetProperties;
  end;

  TGMOdbcRecordsetProperties = class(TGMOdbcStatementProperties, IGMOdbcRecordsetProperties)
   protected
    FAttributes: TGMRecordsetAttributes;
    FUpdateStrategy: TUpdateStrategy;
    FPositionedUpdateSimulation: TPositionedUpdateSimulation;
    FCursorSensitivity: TCursorSensitivity;
    FCursorType: TGMCursorType;
    FKeysetSize: SQLUINTEGER;
    FMaxRecordsReturned: SQLUINTEGER;
    FMaxFieldDataSize: SQLUINTEGER;
//  FBlobCompressionType: Integer; // TGMCompressionType;
    FRecordCountStrategies: TRecordCountStrategies;
//  FMaxStringLengthOrigin: TGMMaxStringLengthOrigin;
//  FMaxStringLengthCharSizeFactor: Double;

    procedure SetDefaultValues; override;

   public
    function Obj: TGMOdbcRecordsetProperties;

    procedure AssignFromObj(const ASource: TObject); override;
    procedure AssignToObj(const ADest: TObject); override;

    procedure LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;
    procedure StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); override;

   //published
    property Attributes: TGMRecordsetAttributes read FAttributes write FAttributes default cDfltRSAttributes;
    property UpdateStrategy: TUpdateStrategy read FUpdateStrategy write FUpdateStrategy default cDfltUpdateStrategy;
    property PositionedUpdateSimulation: TPositionedUpdateSimulation read FPositionedUpdateSimulation write FPositionedUpdateSimulation default cDfltPositionedUpdateSimulation;
    property CursorSensitivity: TCursorSensitivity read FCursorSensitivity write FCursorSensitivity default cDfltCursorSensitivity;
    property CursorType: TGMCursorType read FCursorType write FCursorType default cDfltCursorType;
    property KeysetSize: SQLUINTEGER read FKeysetSize write FKeysetSize default cDfltKeysetSize;
    property MaxRecordsReturned: SQLUINTEGER read FMaxRecordsReturned write FMaxRecordsReturned default cDfltMaxRecords;
    property MaxFieldDataSize: SQLUINTEGER read FMaxFieldDataSize write FMaxFieldDataSize default cDfltMaxFieldDataSize;
//  property BlobCompressionType: Integer read FBlobCompressionType write FBlobCompressionType default cDfltBlobCompressionType; // TGMCompressionType
    property RecordCountStrategies: TRecordCountStrategies read FRecordCountStrategies write FRecordCountStrategies default cDfltRecordCountStrategies;
//  property MaxStringLengthOrigin: TGMMaxStringLengthOrigin read FMaxStringLengthOrigin write FMaxStringLengthOrigin;
//  property MaxStringLengthCharSizeFactor: Double read FMaxStringLengthCharSizeFactor write FMaxStringLengthCharSizeFactor;
  end;


  TGMOdbcRecordsetOperationEvent = procedure (Sender: TGMOdbcRecordsetBase; const Operation: TGMRecordsetOperation) of object;
  TGMOdbcRecordsetEvent = procedure (Sender: TGMOdbcRecordsetBase) of object;
  TGMOdbcRecordsetFieldValueChangeEvent = procedure (Sender: TGMOdbcRecordsetBase; const FieldName: TGMString) of object;
  TGMOdbcFieldCreateEvent = procedure (const FieldName: TGMString; var CreateData: RGMFieldCreateData) of object;

  RGMFetchResult = record
    ResultCode: SQLRETURN;
    ErrorText: TGMString;
  end;


  TGMOdbcRecordsetBase = class(TGMOdbcStatementBase, IGMUnidirectionalCursor,
                                                     IGMBidirectionalCursor,
                                                     IGMCursorFirstLast,
                                                     IGMGetPosition,
                                                     IGMGetSetPosition,
                                                     IGMGetIntfByName,
                                                     IGMGetIntfByPosition,
                                                     IGMGetCount,
                                                     IGMLookupValues,
                                                     IGMLocateValues,
                                                     IGMPositionOfValues,
                                                     IGMGetAttributes,
                                                     IGMGetSetAttributes,
                                                     IGMNamedValueChange,
                                                     IGMAskBoolean,
                                                     IGMAskInteger,
                                                     IGMLoadStoreData)
   protected
    FFieldPosList: TGMObjArrayCollection;
    FFieldNameList: TGMObjArrayCollection;
    FSearchName: IGMGetSetName;
    FCascadedContentsProperties: TGMCascadedContentsProperties;

    FRecordCountStrategies: TRecordCountStrategies;
    FUpdateStrategy: TUpdateStrategy;
    FPositionedUpdateSimulation: TPositionedUpdateSimulation;
    FCursorSensitivity: TCursorSensitivity;
    FCursorType: TGMCursorType;
    FKeysetSize: SQLUINTEGER;
    FMaxRecordsReturned: SQLUINTEGER;
    FMaxFieldDataSize: SQLUINTEGER;
    FRecordCount: PtrInt;
    FPosition: PtrInt;
    FFetchResult: RGMFetchResult;
    FAttributes: TGMRecordsetAttributes;
//  FBlobCompressionType: Integer; // TGMCompressionType;
    FRowStatusArray: TRecordStausArray;
//  FMaxStringLengthOrigin: TGMMaxStringLengthOrigin;
//  FMaxStringLengthCharSizeFactor: Double;

    FOnBeforeOperation: TGMOdbcRecordsetOperationEvent;
    FOnAfterOperation: TGMOdbcRecordsetOperationEvent;
    FOnBeforePositionChange: TGMOdbcRecordsetEvent;
    FOnAfterPositionChange: TGMOdbcRecordsetEvent;
    FOnAfterFieldValueChange: TGMOdbcRecordsetFieldValueChangeEvent;
    FOnValidateFieldValues: TGMOdbcRecordsetEvent;
    FOnCreateField: TGMOdbcFieldCreateEvent;

    function ActivationPropertyCreateClass: TGMActivationPropertyClass; override;

    function GetODBCRowNumber: SQLLEN;
    function ODBCRecordCount: SQLLEN;

    procedure SetODBCUseSQLEscapeSequences(const AValue: Boolean);
    procedure SetODBCCursorType(const AValue: TGMCursorType);
    procedure SetODBCUpdateStrategy(const AValue: TUpdateStrategy);
    procedure SetODBCCursorScrollable(const AValue: Boolean);
    procedure SetODBCCursorSensitivity(const AValue: TCursorSensitivity);
    procedure SetODBCPositionedUpdateSimulation(const AValue: TPositionedUpdateSimulation);
    procedure SetODBCKeysetSize(const AValue: SQLULEN);
    procedure SetODBCMaxRecordsReturned(const AValue: SQLULEN);
    procedure SetODBCMaxFieldDataSize(const AValue: SQLULEN);
    procedure SetODBCBookmarksEnabled(const AValue: Boolean);
    procedure SetODBCRowArraySize(const AValue: PtrUInt);
    procedure SetODBCRowStatusPtr(const AValue: SQLPOINTER);

    procedure SetUpdateStrategy(const AValue: TUpdateStrategy);
    procedure SetPositionedUpdateSimulation(const AValue: TPositionedUpdateSimulation);
    procedure SetCursorSensitivity(const AValue: TCursorSensitivity);
    procedure SetCursorType(const AValue: TGMCursorType);
    procedure SetKeysetSize(const AValue: SQLUINTEGER);
    procedure SetMaxRecordsReturned(const AValue: SQLUINTEGER);
    procedure SetMaxFieldDataSize(const AValue: SQLUINTEGER);
//  procedure SetBlobCompressionType(const Value: Integer); // TGMCompressionType
    procedure IDESetAttributes(const AValue: TGMRecordsetAttributes);
    procedure SetCascadedContentsProperties(const AValue: TGMCascadedContentsProperties);

    procedure ResetMembers; override;
    procedure AllocHandle; override;
    procedure ReleaseHandle; override;
    procedure SetODBCAttributes; override;

    procedure InternalApplyChanges; virtual;

    function GetODBCFieldCount: SQLSMALLINT; // virtual;
    procedure CreateFields; virtual;
    procedure ClearFieldLists; virtual;
    procedure DoCursorMove(const ADirection: SQLSMALLINT; AOffset: SQLLEN; const AMethodName: TGMString);
    procedure DoCursorFetch(const ADirection: SQLSMALLINT; AOffset: SQLLEN;
                            const AMethodName: TGMString; const ANotifyPositionChange: Boolean = True);

    procedure CheckLocateValues(const Values: IGMGetIntfByPosition);
    function LocateResult(const Values: IGMGetIntfByPosition; const FindPos: LongInt): Boolean;

    procedure NotifyConnectedObjectsBeforeOperation(const Operation: Integer; const Parameter: IUnknown = nil);
    procedure NotifyConnectedObjectsAfterOperation(const Operation: Integer; const Parameter: IUnknown = nil);
    procedure NotifyConnectedObjectsBeforePositionChange; override;
    procedure NotifyConnectedObjectsAfterPositionChange; override;

    procedure NotifyBeforeOperation(const Operation: Integer; const Parameter: IUnknown = nil); override;
    procedure NotifyAfterOperation(const Operation: Integer; const Parameter: IUnknown = nil); override;
    procedure NotifyBeforePositionChange; virtual;
    procedure NotifyAfterPositionChange; virtual;
    procedure NotifyBeforeActiveChange(const NewActive: Boolean); override;
    procedure NotifyAfterActiveChange(const ANewActive: Boolean); override;
    procedure NotifyValidateFieldValues; virtual;

   public
    constructor Create(const ARefLifeTime: Boolean); override;
    constructor Create(const AConnection: IUnknown; const ASql: TGMString; const ACursorType: TGMCursorType; const ARefLifeTime: Boolean = True); reintroduce; overload;
    destructor Destroy; override;

    procedure AssignFromObj(const Source: TObject); override; 
    function CanModify: Boolean; override;
    function PositionalInsert: Boolean;
    function IsEmpty: Boolean; virtual;
    function ODBCFetchSuccess: Boolean;

    { ---- IGMFreeCursor ---- }
    function GetBOF: Boolean; virtual; stdcall;
    function GetEOF: Boolean; virtual; stdcall;
    //function GetIsEmpty: Boolean; virtual; stdcall;
    procedure MoveToNext; virtual; stdcall;
    procedure MoveToPrevious; virtual; stdcall;
    procedure MoveToFirst; virtual; stdcall;
    procedure MoveToLast; virtual; stdcall;

    { ---- IGMGetSetPosition ---- }
    function GetPosition: PtrInt; virtual; stdcall;
    procedure SetPosition(const AValue: PtrInt); virtual; stdcall;
    property Position: PtrInt read GetPosition write SetPosition;

    { ---- Operations ---- }
    function CanExecuteOperation(const AOperation: Integer; const AParameter: IUnknown = nil): Boolean; override;
    function ExecuteOperation(const AOperation: Integer; const AParameter: IUnknown = nil): Boolean; override;

    procedure Edit; virtual;
    procedure Insert(const Parameter: IUnknown = nil); virtual;
    procedure RefreshRecord; virtual;
    procedure Delete; virtual;
    procedure CancelChanges; virtual;
    procedure Applychanges; virtual;
    procedure SetSimplestConfiguration; virtual;
    procedure LeaveModifyingState; virtual;

    { ---- IGMGetFieldIntfXxxx ---- }
    function GetIntfByName(const AFieldName: TGMString; const AIID: TGUID; out AIntf): HResult; overload; virtual; stdcall;
    function GetIntfByPosition(const AFieldPosition: PtrInt; const AIID: TGUID; out AIntf): HResult; overload; virtual; stdcall;

    { ---- IGMEnumerateItems ---- }
    procedure EnumerateItems(const ItemKind: LongInt; const TellEnumSink: IUnknown; const Parameter: Pointer = nil); override;

    { ---- IGMGetCount ---- }
    function ComputeRecordCount: SQLLEN; virtual;
    //function CountBySelectStatement: SQLLEN;
    function GetCount: PtrInt; virtual; stdcall;

    { ---- IGMSaveRestoreState ---- }
    function CaptureState: IUnknown; override;
    procedure RestoreState(const State: IUnknown); override;

    { ---- IGMLookupValues ---- }
    function LookupValues(const SQLCriteria: TGMString; const Values: IUnknown): Boolean; virtual; stdcall;

    { ---- IGMFindValues ---- }
    function PositionOfNearestValues(const Values: IGMGetIntfByPosition): LongInt; virtual; stdcall;
    function PositionOfValues(const Values: IUnknown; var FindPos: LongInt): Boolean; virtual; stdcall;
    function LocateValues(const Values: IUnknown): Boolean; virtual; stdcall;

    { ---- IGMNamedValueChange ---- }
    procedure AfterValueChange(const FieldName: TGMString); virtual;

    { ---- IGMGetSetAttributes ---- }
    function GetAttributes: Longword; virtual; stdcall;
    procedure SetAttributes(const AValue: Longword); virtual; stdcall;

    { ---- IGMAskXxxxx ---- }
    function AskBoolean(const AValueId: LongInt): LongInt; virtual; stdcall;
    function AskInteger(const AValueId: LongInt): LongInt; virtual; stdcall;

    // ---- IGMLoadStoreData ---- //
    procedure LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); stdcall;
    procedure StoreData(const Dest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); stdcall;

    function GetFieldByIndex(const AIndex: RGMUnionValue): TGMOdbcField; // virtual;
    function GetFieldValue(const AIndex: RGMUnionValue): RGMUnionValue; //virtual;
    procedure SetFieldValue(const AIndex: RGMUnionValue; const AValue: RGMUnionValue); // virtual;

    property FieldPosList: TGMObjArrayCollection read FFieldPosList;
    property FieldNameList: TGMObjArrayCollection read FFieldNameList;
    property ODBCFieldCount: SQLSMALLINT read GetODBCFieldCount;
    property FieldValue[const Index: RGMUnionValue]: RGMUnionValue read GetFieldValue write SetFieldValue; default;
    property BOF: Boolean read GetBOF;
    property EOF: Boolean read GetEOF;
    property Count: PtrInt read GetCount;
    property RowStatusArray: TRecordStausArray read FRowStatusArray;
    property FetchResult: RGMFetchResult read FFetchResult;

   //published
    property Attributes: TGMRecordsetAttributes read FAttributes write IDESetAttributes default cDfltRSAttributes;
    property UpdateStrategy: TUpdateStrategy read FUpdateStrategy write SetUpdateStrategy default cDfltUpdateStrategy;
    property PositionedUpdateSimulation: TPositionedUpdateSimulation read FPositionedUpdateSimulation write SetPositionedUpdateSimulation default cDfltPositionedUpdateSimulation;
    property CursorSensitivity: TCursorSensitivity read FCursorSensitivity write SetCursorSensitivity default cDfltCursorSensitivity;
    property CursorType: TGMCursorType read FCursorType write SetCursorType default cDfltCursorType;
    property KeysetSize: SQLUINTEGER read FKeysetSize write SetKeysetSize default cDfltKeysetSize;
    property MaxRecordsReturned: SQLUINTEGER read FMaxRecordsReturned write SetMaxRecordsReturned default cDfltMaxRecords;
    property MaxFieldDataSize: SQLUINTEGER read FMaxFieldDataSize write SetMaxFieldDataSize default cDfltMaxFieldDataSize;
//  property BlobCompressionType: Integer read FBlobCompressionType write SetBlobCompressionType default cDfltBlobCompressionType; // TGMCompressionType
    property RecordCountStrategies: TRecordCountStrategies read FRecordCountStrategies write FRecordCountStrategies default cDfltRecordCountStrategies;
    property CascadedContentsProperties: TGMCascadedContentsProperties read FCascadedContentsProperties write SetCascadedContentsProperties;
//  property MaxStringLengthOrigin: TGMMaxStringLengthOrigin read FMaxStringLengthOrigin write FMaxStringLengthOrigin;
//  property MaxStringLengthCharSizeFactor: Double read FMaxStringLengthCharSizeFactor write FMaxStringLengthCharSizeFactor;

    property OnBeforeOperation: TGMOdbcRecordsetOperationEvent read FOnBeforeOperation write FOnBeforeOperation;
    property OnAfterOperation: TGMOdbcRecordsetOperationEvent read FOnAfterOperation write FOnAfterOperation;
    property OnAfterFieldValueChange: TGMOdbcRecordsetFieldValueChangeEvent read FOnAfterFieldValueChange write FOnAfterFieldValueChange;
    property OnBeforePositionChange: TGMOdbcRecordsetEvent read FOnBeforePositionChange write FOnBeforePositionChange;
    property OnAfterPositionChange: TGMOdbcRecordsetEvent read FOnAfterPositionChange write FOnAfterPositionChange;
    property OnValidateFieldValues: TGMOdbcRecordsetEvent read FOnValidateFieldValues write FOnValidateFieldValues;
    property OnCreateField: TGMOdbcFieldCreateEvent read FOnCreateField write FOnCreateField;
  end;


  TGMOdbcRecordset = class(TGMOdbcRecordsetBase, IGMGetMasterSource, IGMGetSetMasterSource)
   protected
    FMasterSource: TGMRecordsetMasterSource;

    procedure IDESetMasterSource(const AValue: TGMRecordsetMasterSource);

    procedure AfterMasterActiveChange(const ANewActive: Boolean); virtual;
    procedure AfterMasterPositionChange; virtual;
    procedure AfterMasterOperation(const AOperation: LongInt; const AParameter: IUnknown = nil); virtual;
    procedure AfterMasterFieldValueChange(ASender: IUnknown; const AFieldName: TGMString); virtual;

   public
    constructor Create(const ARefLifeTime: Boolean); override;
    destructor Destroy; override;

    function GetMasterSource: IUnknown;
    procedure SetMasterSource(const AValue: IUnknown);

    procedure EnumerateItems(const ItemKind: LongInt; const TellEnumSink: IUnknown; const Parameter: Pointer = nil); override;
    function GetResolvedSQLStatement: TGMString; override;
//  procedure CheckSQLStatementText(const SQL: TGMString); override;
    procedure Insert(const Parameter: IUnknown = nil); override;

    procedure OpenMasterSource(const MethodName: TGMString);
    procedure AssignMasterParamValues; virtual;
    procedure AssignFieldValuesFromMasterSource; virtual;

   //published
    property SQL;
    property UseSQLEscapeSequences;
    property MasterSource: TGMRecordsetMasterSource read FMasterSource write IDESetMasterSource;
  end;


  TGMOdbcGroupSlaveRS = class(TGMOdbcRecordset)
   public
    constructor Create(const ARefLifeTime: Boolean); override;
    procedure ScheduleReExecution(const ColumnsStayValid: Boolean = True); override;
  end;


  TGMOdbcSchemaRecordset = class(TGMOdbcRecordsetBase, IGMSchemaProperties, IGMGetText)
   protected
    FSchemaData: TGMSchemaProperties;

    //procedure SetSchemaData(const Value: TGMSchemaProperties);
    procedure InternalExecute; override;
    procedure AfterSchemaDataChange(const Sender: TObject);
    //function GetText: TGMString; virtual; stdcall;
//  function GetODBCFieldCount: SQLSMALLINT; override;

   public
    constructor Create(const ARefLifeTime: Boolean); override; overload;
    destructor Destroy; override;
    //function Obj: TGMOdbcSchemaRecordset;

    //function GetPropertyIntf(const PropertyName: TGMString; const IID: TGUID; out Intf): HResult; override;
    //procedure SetSimplestConfiguration; override;
    procedure AssignFromObj(const ASource: TObject); override;

    property SchemaData: TGMSchemaProperties read FSchemaData implements IGMSchemaProperties, IGMGetText; // write SetSchemaData
  end;


  TODBCFieldClasses = array [TGMDBColumnDataType] of TGMOdbcFieldClass;

  TGMEnumDSNProc = procedure (const AValue: TGMString; const AObj: TObject = nil); //  of object;


var vODBCFieldCreateClasses: TODBCFieldClasses = (
      TGMOdbcField, // fdtBoolean
      TGMOdbcField, // fdtBoolean
      TGMOdbcField, // fdtInt8
      TGMOdbcField, // fdtUInt8
      TGMOdbcField, // fdtInt16
      TGMOdbcField, // fdtUInt16
      TGMOdbcField, // fdtInt32
      TGMOdbcField, // fdtUInt32
      TGMOdbcField, // fdtInt64
      TGMOdbcField, // fdtUInt64
      TGMOdbcField, // fdtSingle
      TGMOdbcField, // fdtDouble
      TGMOdbcField, // fdtNumeric
      TGMOdbcField, // fdtDate
      TGMOdbcField, // fdtTime
      TGMOdbcField, // fdtDateTime
      TGMOdbcField, // fdtAnsiString
      TGMOdbcField, // fdtUnicodeString
      TGMOdbcField, // fdtAnsiText
      TGMOdbcField, // fdtUnicodeText
      TGMOdbcField, // fdtBinary
      TGMOdbcField  // fdtGUID
     );


function ODBCCalculateValueBufferSize(const AFieldDataType: TGMDBColumnDataType; const ASizeInBytes: PtrInt; const ACallingRoutineName: TGMString): Integer;

function ODBCTypeFromFieldDataType(const ADataType: TGMDBColumnDataType; const ACallingRoutineName: TGMString): SQLSMALLINT;
function SQLTypeFromFieldDataType(const ADataType: TGMDBColumnDataType; const ACallingRoutineName: TGMString): SQLSMALLINT;

function FieldTypeFromODBCType(const AODBCDataType: SQLINTEGER;
//                             const ATypeName: TGMstring;
//                             const AOctetLength: SQLINTEGER;
                               const ASigned: Boolean;
                               const AObj: TObject = nil;
                               const ACallingRoutineName: TGMString = cDfltRoutineName): TGMDBColumnDataType;

procedure ODBCEnumDSN(const AEnvironment: TGMOdbcEnvironment;
                      const ADSNKind: TDSNKind;
                      const ADSNEnumProc: TGMEnumDSNProc;
                      const AObj: TObject;
                      const AFormatStr: TGMString);

procedure ODBCEnumUserDSN(const Environment: TGMOdbcEnvironment;
                          const DSNEnumProc: TGMEnumDSNProc;
                          const Obj: TObject = nil;
                          const FormatStr: TGMString = cDfltDSNFmtStr);

procedure ODBCEnumSystemDSN(const Environment: TGMOdbcEnvironment;
                            const DSNEnumProc: TGMEnumDSNProc;
                            const Obj: TObject = nil;
                            const FormatStr: TGMString = cDfltDSNFmtStr);

//function GMOdbcSelectSingleValue(const ODBCConnection: TGMOdbcConnection; const FieldName, TableName, SQLWhere: TGMString): RGMUnionValue; overload;
//function GMOdbcSelectSingleValue(const ODBCConnection: TGMOdbcConnection; const SelectedDields, ResultField, TableName, SQLWhere: TGMString): RGMUnionValue; overload;
function GMOdbcSelectSingleValue(const AOdbcConnection: IUnknown; const ASqlText: TGMString; const AResultField: RGMUnionValue): RGMUnionValue;

//procedure GMExploreODBCConnection(const ODBCConnection: TGMOdbcConnection);

function GMCursorOriginName(const ACursorOrigin: TCursorOrigin): TGMString;
function GMTransactionIsolationName(const ATransactionIsolation: TTransactionIsolation): TGMString;
function GMDriverDialogName(const ADriverDialog: TOdbcAllowDriverDialogs): TGMString;
function GMAccessModeName(const AAccessMode: TAccessMode): TGMString;
function GMCursorTypeName(const ACursorType: TGMCursorType): TGMString;
function GMCursorSensitivityName(const ACursorSensitivity: TCursorSensitivity): TGMString;
function GMUpdateStrategyName(const AUpdateStrategy: TUpdateStrategy): TGMString;
function GMUpdateSimulationName(const AUpdateSimulation: TPositionedUpdateSimulation): TGMString;
function GMCountStrategyName(const ACountStrategy: TRecordCountStrategy): TGMString;
function GMOdbcConnectionAttrName(const AConnectionAttribute: TOdbcConnectionAttribute): TGMString;
//function GMMaxStrLenOriginName(const AMaxStrLenOrigin: TGMMaxStringLengthOrigin): TGMString;


const

  //cStrOdbcSelectDSNDlg = 'ODBC Select DSN Dialog';

  cPrsConnectionString = 'ConnectionString';
  cPrsCursorOrigin = 'Cursor Origin';
  cPrsUsername = 'Username';
  cPrsPassword = 'Password';
  cPrsLoginTimeout = 'LoginTimeout';
  cPrsStatementTimeout = 'StatementTimeout';
  cPrsTransactionIsolation = 'TransactionIsolation';
  cStrAccessMode = 'AccessMode';
  cPrsDriverDialogs = 'DriverDialogs';
  //cStrAsyncOperations = 'AsynchronOperations';
  //cStrAutoCommitTrans = 'AutocommitTransactions';

  cPrsUseTimeoutValueFromConnection = 'UseTimeoutFromConnection';
  cPrsUseAsyncValueFromConnection = 'UseAsynchronFromConnection';
  cPrsAsynchronOperations = 'AsynchronOperations';
  cPrsUseCaseValueFromConnection = 'UseCatalogcaseFromConnection';
  cPrsCaseSensitiveCatalogNames = 'CaseSensitiveCatalogNames';

  cPrsReExecuteAfterSQLChange = 'ReexecuteAfterSQlChange';
  cPrsUseSQLEscapeSequences = 'UseSQLEscapeSequences';
  cPrsTimedReExecutionDelay = 'TimedReExecutionDelay';

  cPrsAttributes = 'Attributes';
  cPrsUpdateStrategy = 'UpdateStrategy';
  cPrsPositionedUpdateSimulation = 'PositionedUpdateSimulation';
  cPrsCursorSensitivity = 'CursorSensitivity';
  cPrsCursorType = 'CursorType';
  cPrsKeysetSize = 'KeysetSize';
  cPrsMaxRecordsReturned = 'MaximumRecordsReturned';
  cPrsMaxFieldDataSize = 'MaximumFieldDataSize';
//cPrsBlobCompressionType = 'BLOBCompressionKind';
//cPrsMaxStringLengthOrigin = 'MaxStringLengthOrigin';
//cPrsMaxStringLengthCharSizeFactor = 'MaxStringLengthCharSizeFactor';

  cPrsCountStrategies = 'CountStrategies';

  cSqlReplacements = #253#254#255;
  cSqlReplaceChars = #9#10#13;



resourcestring

  srDSN = 'DSN';
  srUser = 'User';
  srSystem = 'System';

  srcoUseODBCCursorsIfNeeded = 'Use ODBC cursors if needed';
  srcoUseODBCCursors = 'Use ODBC cursors';
  srcoUseDriverCursors = 'Use driver cursors';

  srReadUnCommitted = 'Read uncommitted';
  srReadCommitted = 'Read committed';
  srRepeatableRead = 'Repeatable read';
  srSerializable = 'Serializable';

  srNoDriverDialogs = 'No driver dialogs';
  srPromtIfIncomplete = 'Promt only if incomplete';
  srAlwaysPromt = 'Always Promt';
  srPromtReqiredOnly = 'Always promt for required properties only';

  srReadWrite = 'Read/Write';
  srReadOnly = 'Readonly';

  srUnidirectional = 'Unidirectional';
  srStatic = 'Static';
  srKeyset = 'Keyset';
  srDynamic = 'Virtual';

  srUnspecified = 'Unspecified';
  srInsensitive = 'Insensitive';
  srReflectChanges = 'Reflect changes made by others';

  srMinimalLock = 'Minimal lock';
  srCompareRecordVersionBeforeWrite = 'Compare record version before write';
  srCompareValuesBeforeWrite = 'Compare values before write';
  srUseDriverDefault = 'Use driver default value';

  srAllowNonUniqueUpdate = 'Allow Non-Unique Update';
  srTryUniqueUpdate = 'Try Unique Update';
  srGuaranteeUniqueUpdate = 'Guarantee Unique Update';

  srUseCountFromODBCDriver = 'Use count from ODBC driver';
  srUseSelectCountStatement = 'Use Select Count(*) statement';
  srCountByLogarithmicPositioning = 'Count by logarithmic positioning';

  srAsynchronOperations = 'Asynchron operations';
  srAutoCommitTransactions = 'Autocommit transactions';
  srCaseSensitiveCatalogNames = 'Casesensitive catalog names';
  srCompleteConnectionString = 'Complete connectionstring';

//RStrslcbCharLength = 'Derive from size in Characters';
//RStrslcbByteLength = 'Derive from size in Byte(s)';


//var

//gCursorOriginNames: array [TCursorOrigin] of TGMString = (RStrcoUseODBCCursorsIfNeeded, RStrcoUseODBCCursors, RStrcoUseDriverCursors);
//gTransactionIsolationNames: array [TTransactionIsolation] of TGMString = (RStrReadUnCommitted, RStrReadCommitted, RStrRepeatableRead, RStrSerializable);
//vDriverDialogNames: array [TOdbcAllowDriverDialogs] of TGMString = (RStrNoDriverDialogs, RStrPromtIfIncomplete, RStrAlwaysPromt, RStrPromtReqiredOnly);
//gAccessModeNames: array [TAccessMode] of TGMString = (RStrReadWrite, RStrReadOnly);
//gCursorTypeNames: array [TGMCursorType] of TGMString = (RStrUnidirectional, RStrStatic, RStrKeyset, RStrDynamic);
//gCursorSensitivityNames: array [TCursorSensitivity] of TGMString = (RStrUnspecified, RStrInsensitive, RStrReflectChanges);
//gUpdateStrategyNames: array [TUpdateStrategy] of TGMString = (RStrReadOnly, RStrMinimalLock, RStrCompareRecordVersionBeforeWrite, RStrCompareValuesBeforeWrite);
//gUpdateSimulationNames: array [TPositionedUpdateSimulation] of TGMString = (RStrAllowNonUniqueUpdate, RStrTryUniqueUpdate, RStrGuaranteeUniqueUpdate);
//gCountStrategyNames: array [TRecordCountStrategy] of TGMString = (RStrUseCountFromODBCDriver, RStrUseSelectCountStatement, RStrCountByLogarithmicPositioning);
//vOdbcConnectionAttrNames: array [TOdbcConnectionAttribute] of TGMString = (RStrAsynchronOperations, RStrAutoCommitTransactions, RStrCaseSensitiveCatalogNames, RStrCompleteConnectionString);



implementation


uses GMWinCrypt, GMCharCoding
     {$IFDEF JEDIAPI},jwaWinError, jwaWinCrypt{$ENDIF}
     ;


resourcestring

  srUnknownODBCFieldDataType = 'Unknown ODBC Field Data Type: %d';
  srNoValuesToLocate = 'No Values passed to Locate';
  srNoSortClauseForField = 'The Recordset is not sorted by Field: "%s"';
  srNoFieldChangedBeforeInsert = 'No field value has been set before inserting a new Record';
//RStrBlobDisplayText = '<binary data, size: %d bytes, stored size: %d bytes, compression ratio: %d%%>';
  srInvalidFormatParamCount = 'Invalid number of format parameters';
  srUnsupportedStreamOperation = 'Unsupported Stream Operation: %s';
  srUnsupportedSeekOffset = 'Unsupported Seek operation Offset: %d, Origin: %d';
  srUnknownBlobFieldPos = 'Unable to send BLOB data, no Field with ordinal position: %d';


const

  scAllocHandle = 'AllocHandle';
//cInvalidDataSize = -1;


{ ------------------------- }
{ ---- Global Routines ---- }
{ ------------------------- }

{procedure GMExploreODBCConnection(const ODBCConnection: TGMOdbcConnection);
begin
  if ODBCConnection <> nil then with ODBCConnection do GMExecuteSQL2(ConnectionString, Username, Password);
end;}

function GMCursorOriginName(const ACursorOrigin: TCursorOrigin): TGMString;
begin
  case ACursorOrigin of
   coUseODBCCursorsIfNeeded: Result := srcoUseODBCCursorsIfNeeded;
   coUseODBCCursors: Result := srcoUseODBCCursors;
   coUseDriverCursors: Result := srcoUseDriverCursors;
   else Result := '';
  end;
end;

function GMTransactionIsolationName(const ATransactionIsolation: TTransactionIsolation): TGMString;
begin
  case ATransactionIsolation of
   tiReadUncommitted: Result := srReadUnCommitted;
   tiReadCommitted: Result := srReadCommitted;
   tiRepeatableRead: Result := srRepeatableRead;
   tiSerializable: Result := srSerializable;
   else Result := '';
  end;
end;

function GMDriverDialogName(const ADriverDialog: TOdbcAllowDriverDialogs): TGMString;
begin
  case ADriverDialog of
   adpNoDriverDialogs: Result := srNoDriverDialogs;
   adpPromtIfIncomplete: Result := srPromtIfIncomplete;
   adpAlwaysPromt: Result := srAlwaysPromt;
   adpAlwaysPromtReqiredOnly: Result := srPromtReqiredOnly;
   else Result := '';
  end;
end;

function GMAccessModeName(const AAccessMode: TAccessMode): TGMString;
begin
  case AAccessMode of
   amReadWrite: Result := srReadWrite;
   amReadOnly: Result := srReadOnly;
   else Result := '';
  end;
end;

function GMCursorTypeName(const ACursorType: TGMCursorType): TGMString;
begin
  case ACursorType of
   ctUnidirectional: Result := srUnidirectional;
   ctStatic: Result := srStatic;
   ctKeyset: Result := srKeyset;
   ctDynamic: Result := srDynamic;
   else Result := '';
  end;
end;

function GMCursorSensitivityName(const ACursorSensitivity: TCursorSensitivity): TGMString;
begin
  case ACursorSensitivity of
   csUnspecified: Result := srUnspecified;
   csInsensitive: Result := srInsensitive;
   csReflectChanges: Result := srReflectChanges;
   csUseDriverDefault: Result := srUseDriverDefault;
   else Result := '';
  end;
end;

function GMUpdateStrategyName(const AUpdateStrategy: TUpdateStrategy): TGMString;
begin
  case AUpdateStrategy of
   usReadOnly: Result := srReadOnly;
   usMinimalLock: Result := srMinimalLock;
   usCompareRecordVersionBeforeWrite: Result := srCompareRecordVersionBeforeWrite;
   usCompareValuesBeforeWrite: Result := srCompareValuesBeforeWrite;
   usUseDriverDefault: Result := srUseDriverDefault;
   else Result := '';
  end;
end;

function GMUpdateSimulationName(const AUpdateSimulation: TPositionedUpdateSimulation): TGMString;
begin
  case AUpdateSimulation of
   pusAllowNonUniqueUpdate: Result := srAllowNonUniqueUpdate;
   pusTryUniqueUpdate: Result := srTryUniqueUpdate;
   pusGuaranteeUniqueUpdate: Result := srGuaranteeUniqueUpdate;
   pusUseDriverDefault: Result := srUseDriverDefault;
   else Result := '';
  end;
end;

//function GMMaxStrLenOriginName(const AMaxStrLenOrigin: TGMMaxStringLengthOrigin): TGMString;
//begin
//case AMaxStrLenOrigin of
// sloLengthInChars: Result := RStrslcbCharLength;
// sloLengthInBytes: Result := RStrslcbByteLength;
// else Result := '';
//end;
//end;

function GMCountStrategyName(const ACountStrategy: TRecordCountStrategy): TGMString;
begin
  case ACountStrategy of
   csUseCountFromODBCDriver: Result := srUseCountFromODBCDriver;
   csUseSelectCountStatement: Result := srUseSelectCountStatement;
   csCountByLogarithmicPositioning: Result := srCountByLogarithmicPositioning;
   else Result := '';
  end;
end;

function GMOdbcConnectionAttrName(const AConnectionAttribute: TOdbcConnectionAttribute): TGMString;
begin
  case AConnectionAttribute of
   caAsynchronOperations: Result := srAsynchronOperations;
   caAutoCommitTransactions: Result := srAutoCommitTransactions;
   caCaseSensitiveCatalogNames: Result := srCaseSensitiveCatalogNames;
   caCompleteConnectionString: Result := srCompleteConnectionString;
   else Result := '';
  end;
end;

function ODBCTypeFromFieldDataType(const ADataType: TGMDBColumnDataType; const ACallingRoutineName: TGMString): SQLSMALLINT;
//var FieldType: TGMDBColumnDataType;
begin
//FieldType := ADataType;
  case ADataType of
   fdtBoolean:        Result := SQL_C_BIT;
   fdtInt8:           Result := SQL_C_STINYINT;
   fdtUInt8:          Result := SQL_C_UTINYINT;
   fdtInt16:          Result := SQL_C_SSHORT;
   fdtUInt16:         Result := SQL_C_USHORT;
   fdtInt32:          Result := SQL_C_SLONG;
   fdtUInt32:         Result := SQL_C_ULONG;
   fdtInt64:          Result := SQL_C_SBIGINT;
   fdtUInt64:         Result := SQL_C_UBIGINT;
   fdtSingle:         Result := SQL_C_FLOAT;
   fdtDouble:         Result := SQL_C_DOUBLE;
   //fdtNumeric:        Result := SQL_C_NUMERIC;
   fdtNumeric:        Result := SQL_C_DOUBLE;
   fdtDate:           Result := SQL_C_TYPE_DATE;
   fdtTime:           Result := SQL_C_TYPE_TIME;
   fdtDateTime:       Result := SQL_C_TYPE_TIMESTAMP;
   fdtAnsiString:     Result := SQL_C_CHAR;
   fdtUnicodeString:  Result := SQL_C_WCHAR;
   fdtBinary:         Result := SQL_C_BINARY;
   fdtAnsiText:       Result := SQL_C_CHAR;
   fdtUnicodeText:    Result := SQL_C_WCHAR;
   fdtGUID:           Result := SQL_C_GUID;
   else raise EGMException.ObjError(MsgUnknownFieldDataType(Ord(ADataType)), nil, ACallingRoutineName);
  end;
end;

function SQLTypeFromFieldDataType(const ADataType: TGMDBColumnDataType; const ACallingRoutineName: TGMString): SQLSMALLINT;
//var FieldType: TGMDBColumnDataType;
begin
//FieldType := ADataType;
  case ADataType of
   fdtBoolean:            Result := SQL_BIT;
   fdtInt8, fdtUInt8:     Result := SQL_TINYINT;
   fdtInt16, fdtUInt16:   Result := SQL_SMALLINT;
   fdtInt32, fdtUInt32:   Result := SQL_INTEGER;
   fdtInt64, fdtUInt64:   Result := SQL_BIGINT;
   fdtSingle:             Result := SQL_REAL;
   fdtDouble:             Result := SQL_DOUBLE;
   //fdtNumeric:            Result := SQL_C_NUMERIC;
   fdtNumeric:            Result := SQL_DOUBLE;
   fdtDate:               Result := SQL_TYPE_DATE;
   fdtTime:               Result := SQL_TYPE_TIME;
   fdtDateTime:           Result := SQL_TYPE_TIMESTAMP;
   fdtAnsiString, fdtAnsiText:         Result := SQL_CHAR;
   fdtUnicodeString, fdtUnicodeText:   Result := SQL_WCHAR;
   fdtBinary:             Result := SQL_BINARY;
   fdtGUID:               Result := SQL_GUID;
   else raise EGMException.ObjError(MsgUnknownFieldDataType(Ord(ADataType)), nil, ACallingRoutineName);
  end;
end;


function ODBCCalculateValueBufferSize(const AFieldDataType: TGMDBColumnDataType; const ASizeInBytes: PtrInt; const ACallingRoutineName: TGMString): Integer;
begin
  case AFieldDataType of
   fdtBoolean:        Result := SizeOf(Byte);
   fdtInt8:           Result := SizeOf(Byte);
   fdtUInt8:          Result := SizeOf(Byte);
   fdtInt16:          Result := SizeOf(Smallint);
   fdtUInt16:         Result := SizeOf(Word);
   fdtInt32:          Result := SizeOf(LongInt);
   fdtUInt32:         Result := SizeOf(LongWord);
   fdtInt64:          Result := SizeOf(Int64);
   fdtUInt64:         Result := SizeOf(Int64);
   fdtSingle:         Result := SizeOf(Single);
   fdtDouble:         Result := SizeOf(Double);
   //fdtNumeric:        Result := SizeOf(SQL_NUMERIC_STRUCT);
   fdtNumeric:        Result := SizeOf(Double);
   fdtDate:           Result := SizeOf(SQL_DATE_STRUCT);
   fdtTime:           Result := SizeOf(SQL_TIME_STRUCT);
   fdtDateTime:       Result := SizeOf(SQL_TIMESTAMP_STRUCT);
   fdtAnsiString:     Result := ASizeInBytes + SizeOf(AnsiChar);
   fdtUnicodeString:  Result := ASizeInBytes + SizeOf(WideChar); // ASizeInBytes + SizeOf(WideChar);
   fdtBinary:         Result := 0;
   fdtAnsiText:       Result := 0;
   fdtUnicodeText:    Result := 0;
   fdtGUID:           Result := SizeOf(SQLGUID);
   else raise EGMException.ObjError(MsgUnknownFieldDataType(Ord(AFieldDataType)), nil, ACallingRoutineName);
  end;
end;

function FieldTypeFromODBCType(const AODBCDataType: SQLINTEGER;
                               const ASigned: Boolean;
                               const AObj: TObject = nil;
                               const ACallingRoutineName: TGMString = cDfltRoutineName): TGMDBColumnDataType;
begin
  case AODBCDataType of
   SQL_CHAR, SQL_VARCHAR:                        Result := fdtAnsiString;
   SQL_WCHAR, SQL_WVARCHAR:                      Result := fdtUnicodeString;
   SQL_DECIMAL, SQL_NUMERIC:                     Result := fdtNumeric;
   SQL_TINYINT:                                  if ASigned then Result := fdtInt8 else Result := fdtUInt8;
   SQL_SMALLINT:                                 if ASigned then Result := fdtInt16 else Result := fdtUInt16;
   SQL_INTEGER:                                  if ASigned then Result := fdtInt32 else Result := fdtUInt32;
   SQL_BIGINT:                                   if ASigned then Result := fdtInt64 else Result := fdtUInt64;
   SQL_FLOAT:                                    Result := fdtSingle;
   SQL_DOUBLE, SQL_REAL:                         Result := fdtDouble;
   SQL_BIT:                                      Result := fdtBoolean;
   SQL_BINARY, SQL_VARBINARY, SQL_LONGVARBINARY: Result := fdtBinary;
   SQL_TYPE_DATE, SQL_DATE:                      Result := fdtDate;
   SQL_TYPE_TIME, SQL_TIME:                      Result := fdtTime;
   SQL_TYPE_TIMESTAMP:                           Result := fdtDateTime;
   SQL_GUID:                                     Result := fdtGUID;
   SQL_LONGVARCHAR:                              Result := fdtAnsiText;
   SQL_WLONGVARCHAR:                             Result := fdtUnicodeText;
   else raise EGMException.ObjError(GMFormat(srUnknownODBCFieldDataType, [AODBCDataType]), AObj, ACallingRoutineName);
  end;
end;

procedure ODBCEnumDSN(const AEnvironment: TGMOdbcEnvironment; const ADSNKind: TDSNKind; const ADSNEnumProc: TGMEnumDSNProc; const AObj: TObject; const AFormatStr: TGMString);
var WasActive: Boolean; Name, Desc: TGMString; FetchKind: SQLUSMALLINT;
begin
  if (AEnvironment <> nil) and Assigned(ADSNEnumProc) and (AFormatStr <> '') then
   begin
    WasActive := AEnvironment.Active;
    AEnvironment.Open;
    try
     case ADSNKind of
      dsnUser:   FetchKind := SQL_FETCH_FIRST_USER;
      dsnSystem: FetchKind := SQL_FETCH_FIRST_SYSTEM;
      else raise EGMException.ObjError(MsgUnknownValue('TDSNKind', Ord(ADSNKind)), nil, {$I %CurrentRoutine%});
     end;

     if ODBCFetchDSN(AEnvironment.Handle, FetchKind, Name, Desc) then
      repeat
       case GMTokenCount(AFormatStr, '%s', '') of
        1: ADSNEnumProc(GMFormat(AFormatStr, [Name]), AObj);
        2: ADSNEnumProc(GMFormat(AFormatStr, [Name, Desc]), AObj);
        else raise EGMException.ObjError(srInvalidFormatParamCount, nil, {$I %CurrentRoutine%});
       end;
      until not ODBCFetchDSN(AEnvironment.Handle, SQL_FETCH_NEXT, Name, Desc);
    finally
     if not WasActive then AEnvironment.Close;
    end;
   end;
end;

procedure ODBCEnumUserDSN(const Environment: TGMOdbcEnvironment; const DSNEnumProc: TGMEnumDSNProc; const Obj: TObject; const FormatStr: TGMString);
begin
  ODBCEnumDSN(Environment, dsnUser, DSNEnumProc, Obj, srUser + ' ' + srDSN + ': ' + FormatStr);
end;

procedure ODBCEnumSystemDSN(const Environment: TGMOdbcEnvironment; const DSNEnumProc: TGMEnumDSNProc; const Obj: TObject; const FormatStr: TGMString);
begin
  ODBCEnumDSN(Environment, dsnSystem, DSNEnumProc, Obj, srSystem + ' ' + srDSN + ': ' + FormatStr);
end;

function GMOdbcSelectSingleValue(const AOdbcConnection: IUnknown; const ASqlText: TGMString; const AResultField: RGMUnionValue): RGMUnionValue;
var rs: TGMOdbcRecordsetBase;
begin
  if (AOdbcConnection = nil) or (Length(ASqlText) <= 0) then Exit(uvtUnassigned);

  rs := TGMOdbcRecordsetBase.Create(AOdbcConnection, ASqlText, ctUnidirectional, False);
  try
   //rs.SQL.SQLText := ASqlText;
   rs.SetSimplestConfiguration;
   rs.Open;
   if rs.EOF then Result := uvtUnassigned else Result := rs[AResultField];
  finally
   rs.Free;
  end;
end;

function TransactionIsolationsToInt(const AValue: TTransactionIsolations): LongInt;
var i: TTransactionIsolation;
begin
  Result := 0;
  for i:=Low(i) to High(i) do if i in AValue then Result := Result or (1 shl Ord(i));
end;

function TransactionIsolationsFromInt(const AValue: LongInt): TTransactionIsolations;
var i: TTransactionIsolation;
begin
  Result := [];
  for i:=Low(i) to High(i) do if AValue and (1 shl Ord(i)) <> 0 then Include(Result, i);
end;


function OdbcCnAttributesToInt(const AValue: TOdbcConnectionAttributes): LongInt;
var i: TOdbcConnectionAttribute;
begin
  Result := 0;
  for i:=Low(i) to High(i) do if i in AValue then Result := Result or (1 shl Ord(i));
end;

function OdbcCnAttributesFromInt(const AValue: LongInt): TOdbcConnectionAttributes;
var i: TOdbcConnectionAttribute;
begin
  Result := [];
  for i:=Low(i) to High(i) do if AValue and (1 shl Ord(i)) <> 0 then Include(Result, i);
end;


function RecordCountStrategiesToInt(const AValue: TRecordCountStrategies): LongInt;
var i: TRecordCountStrategy;
begin
  Result := 0;
  for i:=Low(i) to High(i) do if i in AValue then Result := Result or (1 shl Ord(i));
end;

function RecordCountStrategiesFromInt(const AValue: LongInt): TRecordCountStrategies;
var i: TRecordCountStrategy;
begin
  Result := [];
  for i:=Low(i) to High(i) do if AValue and (1 shl Ord(i)) <> 0 then Include(Result, i);
end;


{ ---------------------------------- }
{ ---- TGMOdbcConnectProperties ---- }
{ ---------------------------------- }

constructor TGMOdbcConnectProperties.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  SetDefaultValues;
end;

constructor TGMOdbcConnectProperties.CreateFromObj(const ASource: TObject; const ARefLifeTime: Boolean);
begin
  Create(ARefLifeTime);
  if ASource <> nil then AssignFromObj(ASource);
end;

constructor TGMOdbcConnectProperties.CreateFromIntf(const ASource: IUnknown; const ACryptCtrlData: PGMCryptCtrlData; const ARefLifeTime: Boolean);
var ValueStg: IGMValueStorage; CnProps: IGMOdbcConnectProperties;
begin
  Create(ARefLifeTime);
  if GMQueryInterface(ASource, IGMValueStorage, ValueStg) then LoadData(ValueStg, ACryptCtrlData);
  if GMQueryInterface(ASource, IGMOdbcConnectProperties, CnProps) then AssignFromObj(CnProps.Obj);
end;

constructor TGMOdbcConnectProperties.CreateOwned(const AOwner: TObject; const ARefLifeTime: Boolean);
begin
  Create(ARefLifeTime);
  AddOwner(AOwner);
end;

function TGMOdbcConnectProperties.GetName: TGMString; stdcall;
begin
  Result := GMExtractNameFromConnectionString(ConnectionString);
end;

function TGMOdbcConnectProperties.DatabaseName: TGMString;
var cnStrParser: IGMValueStorage;
begin
  cnStrParser := TGMConnectionStringStorage.Create(ConnectionString, True);

  Result := cnStrParser.ReadString(cStrCnStrDatabase);
  if Result = '' then Result := cnStrParser.ReadString(cStrCnStrDBQ);
  if Result = '' then Result := cnStrParser.ReadString('FILENAME');
  if Result = '' then Result := GMStripRight(cnStrParser.ReadString(cStrCnDir), cDirSep);
  if Result = '' then Result := GMStripRight(cnStrParser.ReadString(cStrCnDefaultDir), cDirSep);

  if Length(Result) > 0 then Result := GMExtractFileName(Result);
end;

function TGMOdbcConnectProperties.Obj: TGMOdbcConnectProperties;
begin
  Result := Self;
end;

function TGMOdbcConnectProperties.FindOwner(const AOwner: TObject; var AIdx: LongInt): Boolean;
var i: LongInt;
begin
  for i:=Low(FOwners) to High(FOwners) do if FOwners[i] = AOwner then begin AIdx := i; Exit(True); end;
  //AIdx := cInvalidItemIdx;
  Result := False;
end;

function TGMOdbcConnectProperties.AddOwner(const AOwner: TObject): TObject;
var idx: LongInt;
begin
  Result := AOwner;
  if FindOwner(AOwner, idx) then Exit;
  SetLength(FOwners, Length(FOwners)+1);
  FOwners[High(FOwners)] := AOwner;
end;

procedure TGMOdbcConnectProperties.RemoveOwner(const AOwner: TObject);
var i, foundIdx: LongInt;
begin
  if FindOwner(AOwner, foundIdx) then
   begin
    for i:=foundIdx to High(FOwners)-1 do FOwners[i] := FOwners[i+1];
    SetLength(FOwners, Length(FOwners)-1);
   end;
end;

//procedure TGMOdbcConnectProperties.CheckIsInactive(const ACallingName: TGMString);
//var I: LongInt;
//begin
//for i:=Low(FOwners) to High(FOwners) do GMCheckObjIsInActive(FOwners[i], ACallingName);
// //if FOwners[i] is TGMActivatableObject then TGMActivatableObject(FOwners[i]).CheckIsInactive(ACallingName);
//end;

//procedure TGMOdbcConnectProperties.SetAccessMode(const AValue: TAccessMode);
//begin
//if AValue <> AccessMode then
// begin
//  CheckIsInactive('AccessMode ' + RStrProperty);
//  FAccessMode := AValue;
// end;
//end;

//procedure TGMOdbcConnectProperties.SetTimeoutForStatements(const AValue: SQLUINTEGER);
//begin
//if AValue <> TimeoutForStatements then
// begin
//  CheckIsInactive('TimeoutForStatements ' + RStrProperty);
//  FTimeoutForStatements := AValue;
// end;
//end;

//procedure TGMOdbcConnectProperties.SetTimeoutForLogin(const AValue: SQLUINTEGER);
//begin
//if AValue <> TimeoutForLogin then
// begin
//  CheckIsInactive('TimeoutForLogin ' + RStrProperty);
//  FTimeoutForLogin := AValue;
// end;
//end;

//procedure TGMOdbcConnectProperties.SetConnectionString(const AValue: TGMString);
//begin
//if AValue <> ConnectionString then
// begin
//  CheckIsInactive('ConnectionString ' + RStrProperty);
//  FConnectionString := AValue;
// end;
//end;

//procedure TGMOdbcConnectProperties.SetCursorOrigin(const AValue: TCursorOrigin);
//begin
//if AValue <> CursorOrigin then
// begin
//  CheckIsInactive('CursorOrigin ' + RStrProperty);
//  FCursorOrigin := AValue;
// end;
//end;

procedure TGMOdbcConnectProperties.SetTransactionIsolation(const AValue: TTransactionIsolations);
var i: LongInt;
begin
  if AValue <> TransactionIsolation then
   begin
    for i:=Low(FOwners) to High(FOwners) do
      if FOwners[i] is TGMOdbcConnection then TGMOdbcConnection(FOwners[i]).SetODBCTransactionIsolation(AValue);
    FTransactionIsolation := AValue;
   end;
end;

procedure TGMOdbcConnectProperties.SetAttributes(const AValue: TOdbcConnectionAttributes);
var i: LongInt;
begin
  for i:=Low(FOwners) to High(FOwners) do
   if FOwners[i] is TGMOdbcConnection then
    with TGMOdbcConnection(FOwners[i]) do
     begin
      if (caAutoCommitTransactions in AValue) <> (caAutoCommitTransactions in Properties.Obj.Attributes) then SetODBCAutoCommitTransactions(caAutoCommitTransactions in AValue);
      //if (caAllowDriverDialogs in AValue) <> (caAllowDriverDialogs in FAttributes) then SetODBCDriverDialogPrntWnd(caAllowDriverDialogs in AValue);
      if (caCaseSensitiveCatalogNames in AValue) <> (caCaseSensitiveCatalogNames in Properties.Obj.Attributes) then SetODBCCaseSensitiveCatalogNames(caCaseSensitiveCatalogNames in AValue);
      if (caAsynchronOperations in AValue) <> (caAsynchronOperations in Properties.Obj.Attributes) then CheckIsInactive('caAsynchronOperations Attribute');
     end;
  FAttributes := AValue;
end;

procedure TGMOdbcConnectProperties.SetDefaultValues;
begin
  FAccessMode := cDfltAccessMode;
  //FAsynchronOperations := cDfltAsyncOperations;
  //FAutoCommitTransactions := cDfltAutoCommitTrans;
  FUsername := cDfltUsername;
  FPassword := '';
  FConnectionString := '';
  FTimeoutForStatements := cDfltStatementTimeout;
  FTimeoutForLogin := cDfltLoginTimeout;
  FCursorOrigin := cDfltCursorOrigin;
  FTransactionIsolation := cSqlDefaultTransactionIsolation;
  FAttributes := cDfltConnectionAttributes;
  FAllowDriverDialogs := cDfltDriverDialogs;
end;

procedure TGMOdbcConnectProperties.AssignFromIntf(const ASource: IUnknown); stdcall;
begin
  AssignFromObj(GMObjFromIntf(ASource));
end;

procedure TGMOdbcConnectProperties.AssignToIntf(const ADest: IUnknown); stdcall;
begin
  AssignToObj(GMObjFromIntf(ADest));
end;

procedure TGMOdbcConnectProperties.AssignFromObj(const ASource: TObject); stdcall;
begin
  if ASource is TGMOdbcConnectProperties then
   begin
    AccessMode := TGMOdbcConnectProperties(ASource).AccessMode;
    //AsynchronOperations := TGMOdbcConnectProperties(ASource).AsynchronOperations;
    //AutoCommitTransactions := TGMOdbcConnectProperties(ASource).AutoCommitTransactions;
    Username := TGMOdbcConnectProperties(ASource).Username;
    Password := TGMOdbcConnectProperties(ASource).Password;
    ConnectionString := TGMOdbcConnectProperties(ASource).ConnectionString;
    TimeoutForStatements := TGMOdbcConnectProperties(ASource).TimeoutForStatements;
    TimeoutForLogin := TGMOdbcConnectProperties(ASource).TimeoutForLogin;
    CursorOrigin := TGMOdbcConnectProperties(ASource).CursorOrigin;
    TransactionIsolation := TGMOdbcConnectProperties(ASource).TransactionIsolation;
    Attributes := TGMOdbcConnectProperties(ASource).Attributes;
    AllowDriverDialogs := TGMOdbcConnectProperties(ASource).AllowDriverDialogs;
   end else
  if (ASource is TGMOdbcConnection) and (TGMOdbcConnection(ASource).Properties <> nil) then
     AssignFromObj(TGMOdbcConnection(ASource).Properties.Obj);
end;

procedure TGMOdbcConnectProperties.AssignToObj(const ADest: TObject); stdcall;
begin
  if ADest is TGMOdbcConnectProperties then TGMOdbcConnectProperties(ADest).AssignFromObj(Self)
  else
  if (ADest is TGMOdbcConnection) and (TGMOdbcConnection(ADest).Properties <> nil) then TGMOdbcConnection(ADest).Properties.Obj.AssignFromObj(Self);
end;

procedure TGMOdbcConnectProperties.LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); stdcall;
begin
  if ASource <> nil then
   begin
    AccessMode := TAccessMode(GMBoundedInt(ASource.ReadInteger(cStrAccessMode, Ord(cDfltAccessMode)), Ord(Low(TAccessMode)), Ord(High(TAccessMode))));
    if ACryptCtrlData <> nil then
     begin
      UserName := GMDecryptStringW(GMDecodeBase16Str(ASource.ReadString(cPrsUsername, cDfltUserName)), ACryptCtrlData.KeyData, CALG_MD5, CALG_RC4, Self);
      Password := GMDecryptStringW(GMDecodeBase16Str(ASource.ReadString(cPrsPassword, cDfltPassword)), ACryptCtrlData.KeyData, CALG_MD5, CALG_RC4, Self);
     end;

    ConnectionString := ASource.ReadString(cPrsConnectionString);
    TimeoutForLogin := ASource.ReadInteger(cPrsLoginTimeout, cDfltLoginTimeout);
    TimeoutForStatements := ASource.ReadInteger(cPrsStatementTimeout, cDfltStatementTimeout);
    CursorOrigin := TCursorOrigin(GMBoundedInt(ASource.ReadInteger(cPrsCursorOrigin, Ord(cDfltCursorOrigin)), Ord(Low(TCursorOrigin)), Ord(High(TCursorOrigin))));
    AllowDriverDialogs := TOdbcAllowDriverDialogs(GMBoundedInt(ASource.ReadInteger(cPrsDriverDialogs, Ord(cDfltDriverDialogs)), Ord(Low(TOdbcAllowDriverDialogs)), Ord(High(TOdbcAllowDriverDialogs))));
    //AsynchronOperations := ASource.ReadBoolean(cStrAsyncOperations, cDfltAsyncOperations);
    //AutoCommitTransactions := ASource.ReadBoolean(cStrAutoCommitTrans, cDfltAutoCommitTrans);
    TransactionIsolation := TransactionIsolationsFromInt(ASource.ReadInteger(cPrsTransactionIsolation, TransactionIsolationsToInt(cSqlDefaultTransactionIsolation)));
    Attributes := OdbcCnAttributesFromInt(ASource.ReadInteger(cPrsAttributes, OdbcCnAttributesToInt(cDfltConnectionAttributes)));
   end;
end;

procedure TGMOdbcConnectProperties.StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); stdcall;
begin
  if ADest <> nil then
   begin
    GMStoreInteger(ADest, cStrAccessMode, Ord(AccessMode), Ord(cDfltAccessMode));
    if ACryptCtrlData <> nil then
     begin
      GMStoreString(ADest, cPrsUsername, GMEncodeBase16Str(GMEncryptStringW(UserName, ACryptCtrlData.KeyData, CALG_MD5, CALG_RC4, Self)), cDfltUserName);
      GMStoreString(ADest, cPrsPassword, GMEncodeBase16Str(GMEncryptStringW(Password, ACryptCtrlData.KeyData, CALG_MD5, CALG_RC4, Self)), cDfltPassword);
     end;
    GMStoreString(ADest, cPrsConnectionString, ConnectionString);
    GMStoreInteger(ADest, cPrsLoginTimeout, TimeoutForLogin, cDfltLoginTimeout);
    GMStoreInteger(ADest, cPrsStatementTimeout, TimeoutForStatements, cDfltStatementTimeout);
    GMStoreInteger(ADest, cPrsCursorOrigin, Ord(CursorOrigin), Ord(cDfltCursorOrigin));
    GMStoreInteger(ADest, cPrsDriverDialogs, Ord(AllowDriverDialogs), Ord(cDfltDriverDialogs));
    //if AsynchronOperations <> cDfltAsyncOperations then ADest.WriteBoolean(cStrAsyncOperations, AsynchronOperations);
    //if AutoCommitTransactions <> cDfltAutoCommitTrans then ADest.WriteBoolean(cStrAutoCommitTrans, AutoCommitTransactions);
    GMStoreInteger(ADest, cPrsTransactionIsolation, TransactionIsolationsToInt(TransactionIsolation), TransactionIsolationsToInt(cSqlDefaultTransactionIsolation));
    GMStoreInteger(ADest, cPrsAttributes, OdbcCnAttributesToInt(Attributes), OdbcCnAttributesToInt(cDfltConnectionAttributes));
   end;
end;


{ ----------------------------- }
{ ---- TOdbcHandleAllocObj ---- }
{ ----------------------------- }

constructor TOdbcHandleAllocObj.Create(const ArefLifeTime: Boolean);
begin
  inherited Create(ArefLifeTime);
  FCriticalSection := TGMCriticalSection.Create(True);
end;

procedure TOdbcHandleAllocObj.ReleaseHandle;
begin
  if GetHandleAllocated then
   begin
    SQLFreeHandle(HandleType, Handle);
    FHandle := 0;
   end;
end;


{ ------------------------------ }
{ ---- TGMOdbcTransactedObj ---- }
{ ------------------------------ }

function TGMOdbcTransactedObj.GetTransactionSupport: TTransactionSupport;
begin
  Result := txnAll;
end;

function TGMOdbcTransactedObj.GetTransactionLevel: LongInt; 
begin
  Result := FTransactionLevel;
end;

procedure TGMOdbcTransactedObj.BeginTransaction;
begin
  CheckIsActive('BeginTransaction ' + RStrMethod);
  Inc(FTransactionLevel);
end;

procedure TGMOdbcTransactedObj.CommitTransaction;
begin
  CheckIsActive('CommitTransaction ' + RStrMethod);
  ODBCCheck(SQLEndTran(HandleType, Handle, SQL_COMMIT), Self, 'SQLEndTran(SQL_COMMIT)');
  Dec(FTransactionLevel);
end;

procedure TGMOdbcTransactedObj.RollbackTransaction;
begin
  CheckIsActive('RollbackTransaction ' + RStrMethod);
  ODBCCheck(SQLEndTran(HandleType, Handle, SQL_ROLLBACK), Self, 'SQLEndTran(SQL_ROLLBACK)');
  Dec(FTransactionLevel);
end;


{ ---------------------------- }
{ ---- TGMOdbcEnvironment ---- }
{ ---------------------------- }

constructor TGMOdbcEnvironment.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  FODBCVersion := cDfltODBCVersion;
  FConnectionPooling := cDfltConnectionPooling;
  FConnectionPoolSelectStrategy := cDfltCPSelectStartegy;
end;

constructor TGMOdbcEnvironment.Create(const AOdbcVersion: TOdbcVersion;
  const AConnectionPooling: TOdbcConnectionPooling;
  const AConnectionPoolSelectStrategy: TOdbcConnectionPoolSelectStrategy;
  const ARefLifeTime: Boolean);
begin
  Create(ARefLifeTime);
  FODBCVersion := AOdbcVersion;
  FConnectionPooling := AConnectionPooling;
  FConnectionPoolSelectStrategy := AConnectionPoolSelectStrategy;
end;

procedure TGMOdbcEnvironment.AssignFromObj(const Source: TObject);
begin
  if Source is TGMOdbcEnvironment then
   begin
    Close;
    ODBCVersion := TGMOdbcEnvironment(Source).ODBCVersion;
    ConnectionPooling := TGMOdbcEnvironment(Source).ConnectionPooling;
    ConnectionPoolSelectStrategy := TGMOdbcEnvironment(Source).ConnectionPoolSelectStrategy;
   end;
end;

function TGMOdbcEnvironment.GetHandleType: LongWord;
begin
  Result := SQL_HANDLE_ENV;
end;

procedure TGMOdbcEnvironment.AllocHandle;
begin
  if not GetHandleAllocated then
   begin
    if ConnectionPooling <> TOdbcConnectionPooling(SQL_CP_DEFAULT) then SetODBCConnectionPooling(ConnectionPooling);
    ODBCCheck(SQLAllocHandle(HandleType, SQL_NULL_HANDLE, FHandle), Self, 'SQLAllocHandle');
    SetODBCODBCVersion(ODBCVersion);
    SetODBCConnectionPoolSelectStrategy(ConnectionPoolSelectStrategy);
   end;
end;


{ ---- Property Set on ODBC API Level ---- }

procedure TGMOdbcEnvironment.SetODBCConnectionPooling(const AValue: TOdbcConnectionPooling);
var Pooling: PtrUInt;
begin
  case AValue of
   cpOff:                   Pooling := SQL_CP_OFF;
   cpOnePoolPerDriver:      Pooling := SQL_CP_ONE_PER_DRIVER;
   cpOnePoolPerEnvironment: Pooling := SQL_CP_ONE_PER_HENV;
   else raise EGMException.ObjError(MsgUnknownValue('TOdbcConnectionPooling', Ord(AValue)), Self, {$I %CurrentRoutine%});
  end;
  ODBCCheck(SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, SQLPOINTER(Pooling), 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcEnvironment.SetODBCConnectionPoolSelectStrategy(const AValue: TOdbcConnectionPoolSelectStrategy);
var CPSelectStragegy: PtrUInt;
begin
  if Active then
   begin
    case AValue of
     ssExactMatch:   CPSelectStragegy := SQL_CP_STRICT_MATCH;
     ssRelaxedMatch: CPSelectStragegy := SQL_CP_RELAXED_MATCH;
     else raise EGMException.ObjError(MsgUnknownValue('TOdbcConnectionPoolSelectStrategy', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    ODBCCheck(SQLSetEnvAttr(Handle, SQL_ATTR_CP_MATCH, SQLPOINTER(CPSelectStragegy), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcEnvironment.SetODBCODBCVersion(const AValue: TOdbcVersion);
var ODBCVer: PtrUInt;
begin
  if Active then
   begin
    case AValue of
     odbc2x: ODBCVer := SQL_OV_ODBC2;
     odbc3x: ODBCVer := SQL_OV_ODBC3;
     else raise EGMException.ObjError(MsgUnknownValue('TOdbcVersion', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    ODBCCheck(SQLSetEnvAttr(Handle, SQL_ATTR_ODBC_VERSION, SQLPOINTER(ODBCVer), 0), Self, {$I %CurrentRoutine%});
   end;
end;


{ ---- Property Get/Set ---- }

procedure TGMOdbcEnvironment.SetODBCVersion(const AValue: TOdbcVersion);
begin
  if AValue <> ODBCVersion then
   begin
    CheckIsInactive('ODBCVersion ' + RStrProperty);
    FODBCVersion := AValue;
   end;
end;

procedure TGMOdbcEnvironment.SetConnectionPooling(const AValue: TOdbcConnectionPooling);
begin
  if AValue <> ConnectionPooling then
   begin
    CheckIsInactive('ConnectionPooling ' + RStrProperty);
    FConnectionPooling := AValue;
   end;
end;

procedure TGMOdbcEnvironment.SetConnectionPoolSelectStrategy(const AValue: TOdbcConnectionPoolSelectStrategy);
begin
  if AValue <> ConnectionPoolSelectStrategy then
   begin
    SetODBCConnectionPoolSelectStrategy(AValue);
    FConnectionPoolSelectStrategy := AValue;
   end;
end;


{ ----------------------------------- }
{ ---- TOdbcComponentSubProperty ---- }
{ ----------------------------------- }

constructor TOdbcComponentSubProperty.Create(const AOwner: TGMActivatableObject);
begin
  inherited Create;
  FOwner := AOwner;
end;

function TOdbcComponentSubProperty.ODBCConnection: TGMOdbcConnection;
begin
  Result := FOwner as TGMOdbcConnection;
  //Assert(Result <> nil, 'Result <> nil');
end;

function TOdbcComponentSubProperty.ODBCStatement: TGMOdbcStatementBase;
begin
  Result := FOwner as TGMOdbcStatementBase;
  //Assert(Result <> nil, 'Result <> nil');
end;


{ ------------------------------ }
{ ---- TOdbcTraceProperties ---- }
{ ------------------------------ }

constructor TOdbcTraceProperties.Create(const AOwner: TGMActivatableObject);
begin
  inherited Create(AOwner);
  FEnableTracing := cDfltEnableTracing;
  FTraceFilename := SQL_OPT_TRACE_FILE_DEFAULT;
end;

function TOdbcTraceProperties.IsEqualTo(const AValue: TOdbcTraceProperties): Boolean;
begin
  if AValue = nil then Result := True else
   Result := (EnableTracing = AValue.EnableTracing) and (TraceFilename = AValue.TraceFilename);
end;

procedure TOdbcTraceProperties.AssignFromObj(const Source: TObject);
begin
  if Source is TOdbcTraceProperties then
   begin
    EnableTracing := TOdbcTraceProperties(Source).EnableTracing;
    TraceFilename := TOdbcTraceProperties(Source).TraceFilename;
   end;
  //else inherited AssignFromObj(Source);
end;

procedure TOdbcTraceProperties.SetODBCEnableTracing(const AValue: Boolean);
var trace: PtrUInt;
begin
  if ODBCConnection.Active then
   begin
    if AValue then trace := SQL_OPT_TRACE_ON else trace := SQL_OPT_TRACE_OFF;
    ODBCCheck(SQLSetConnectAttr(ODBCConnection.Handle, SQL_ATTR_TRACE, SQLPOINTER(trace), 0), ODBCConnection, {$I %CurrentRoutine%});
   end;
end;

procedure TOdbcTraceProperties.SetEnableTracing(const AValue: Boolean);
begin
  if AValue <> EnableTracing then
   begin
    SetODBCEnableTracing(AValue);
    FEnableTracing := AValue;
   end;
end;

procedure TOdbcTraceProperties.SetODBCTraceFilename(const AValue: TGMString);
begin
  if ODBCConnection.Active then
   ODBCCheck(SQLSetConnectAttr(ODBCConnection.Handle, SQL_ATTR_TRACEFILE, SQLPOINTER(AValue), Length(AValue)), ODBCConnection, {$I %CurrentRoutine%});
end;

procedure TOdbcTraceProperties.SetTraceFilename(const AValue: TGMString);
begin
  if AValue <> TraceFilename then
   begin
    SetODBCTraceFilename(AValue);
    FTraceFilename := AValue;
   end;
end;

procedure TOdbcTraceProperties.SetODBCValues;
begin
  if EnableTracing <> Boolean(SQL_OPT_TRACE_DEFAULT) then
   begin
    SetODBCEnableTracing(EnableTracing);
    if TraceFilename <> '' then SetODBCTraceFilename(TraceFilename);
   end; 
end;


{ ---------------------------------- }
{ ---- TOdbcTranslateProperties ---- }
{ ---------------------------------- }

constructor TOdbcTranslateProperties.Create(const AOwner: TOdbcHandleAllocObj);
begin
  inherited Create(AOwner);
  FTranslationAttributes := cDfltTranslateOptions;
end;

function TOdbcTranslateProperties.IsEqualTo(const AValue: TOdbcTranslateProperties): Boolean;
begin
  if AValue = nil then Result := True else
   Result := (TranslationDLLFilename = AValue.TranslationDLLFilename) and (TranslationAttributes = AValue.TranslationAttributes);
end;

procedure TOdbcTranslateProperties.AssignFromObj(const Source: TObject);
begin
  if Source is TOdbcTranslateProperties then
   begin
    TranslationDLLFilename := TOdbcTranslateProperties(Source).TranslationDLLFilename;
    TranslationAttributes := TOdbcTranslateProperties(Source).TranslationAttributes;
   end;
  //else inherited AssignFromObj(Source);
end;

procedure TOdbcTranslateProperties.SetODBCTranslationDLLFilename(const AValue: TGMString);
begin
  if ODBCConnection.Active then
   ODBCCheck(SQLSetConnectAttr(ODBCConnection.Handle, SQL_ATTR_TRANSLATE_LIB, SQLPOINTER(AValue), Length(AValue)), ODBCConnection, {$I %CurrentRoutine%});
end;

procedure TOdbcTranslateProperties.SetTranslationDLLFilename(const AValue: TGMString);
begin
  if AValue <> TranslationDLLFilename then
   begin
    SetODBCTranslationDLLFilename(AValue);
    FTranslationDLLFilename := AValue;
   end;
end;

procedure TOdbcTranslateProperties.SetODBCTranslationAttributes(const AValue: PtrUInt);
begin
  if ODBCConnection.Active then
   ODBCCheck(SQLSetConnectAttr(ODBCConnection.Handle, SQL_ATTR_TRANSLATE_OPTION, SQLPOINTER(AValue), 0), ODBCConnection, {$I %CurrentRoutine%});
end;

procedure TOdbcTranslateProperties.SetTranslationAttributes(const AValue: SQLUINTEGER);
begin
  if AValue <> TranslationAttributes then
   begin
    SetODBCTranslationAttributes(AValue);
    FTranslationAttributes := AValue;
   end;
end;

procedure TOdbcTranslateProperties.SetODBCValues;
begin
  if TranslationDLLFilename <> '' then SetODBCTranslationDLLFilename(TranslationDLLFilename);
  if TranslationAttributes <> SQL_DEFAULT_TRANSLATE_OPTIONS then SetODBCTranslationAttributes(TranslationAttributes);
end;


{ --------------------------- }
{ ---- TGMOdbcConnection ---- }
{ --------------------------- }

constructor TGMOdbcConnection.Create(const ArefLifeTime: Boolean);
begin
  inherited;
  ObjectConnectedTo.OnBeforeIntfSourceChange := OnBeforeIntfSourceChange;

  FProperties := TGMOdbcConnectProperties.CreateOwned(Self, True);
  GMCheckQueryInterface(FProperties, IGMLoadStoreData, FLoadStore, 'TGMOdbcConnection.Create');
//GMCheckQueryInterface(FProperties, IGMGetName, FName, 'TGMOdbcConnection.Create');

  FTraceProperties := TOdbcTraceProperties.Create(Self);
  FTranslateProperties := TOdbcTranslateProperties.Create(Self);

  FNetworkPacketSize := cDfltNetPacketSize;
end;

constructor TGMOdbcConnection.Create(const AOdbcEnvironment: IUnknown; const AConnectionString: TGMString; const ARefLifeTime: Boolean);
begin
  Create(ARefLifeTime);
  if AOdbcEnvironment <> nil then ODBCEnvironmentIntf := AOdbcEnvironment;
  FProperties.Obj.ConnectionString := AConnectionString;
end;

destructor TGMOdbcConnection.Destroy;
begin
  inherited Destroy;
  GMFreeAndNil(FTraceProperties);
  GMFreeAndNil(FTranslateProperties);
  FProperties.Obj.RemoveOwner(Self);
end;

//function TGMOdbcConnection.Obj: TGMOdbcConnection;
//begin
//  Result := Self;
//end;

function TGMOdbcConnection.GetName: TGMString; stdcall;
var propName: IGMGetName;
begin
  if GMQueryInterface(FProperties, IGMGetName, propName) then Result := propName.Name else Result := '';
end;

procedure TGMOdbcConnection.SetSharedProperties(const AProperties: IGMOdbcConnectProperties);
//var objInfo: IGMObjInfo;
begin
  //if GMQueryInterface(AProperties, IGMObjInfo, objInfo) and (objInfo.Instance is TGMOdbcConnectProperties) then
  if AProperties <> nil then
   begin
    if FProperties <> nil then
      begin FProperties.Obj.RemoveOwner(Self); FProperties := nil; end;

    AProperties.Obj.AddOwner(Self);
    FProperties := AProperties;
   end;
end;

//procedure TGMOdbcConnection.AssignFromObj(const Source: TObject);
//begin
//  if Source is TGMOdbcConnection then
//   begin
//    Close;
//
//    if TGMOdbcConnection(Source).ODBCEnvironment <> nil then
//     ODBCEnvironment := TGMOdbcConnection(Source).ODBCEnvironment
//    else
//     ODBCEnvironmentIntf := TGMOdbcConnection(Source).ODBCEnvironmentIntf;
//
//    AccessMode := TGMOdbcConnection(Source).AccessMode;
//    //AsynchronOperations := TGMOdbcConnection(Source).AsynchronOperations;
//    //AutoCommitTransactions := TGMOdbcConnection(Source).AutoCommitTransactions;
//    //AllowDriverDialogs := TGMOdbcConnection(Source).AllowDriverDialogs;
//    ConnectionString := TGMOdbcConnection(Source).ConnectionString;
//    DataSourceCatalog := TGMOdbcConnection(Source).DataSourceCatalog;
//    Username := TGMOdbcConnection(Source).Username;
//    Password := TGMOdbcConnection(Source).Password;
//    TimeoutForStatements := TGMOdbcConnection(Source).TimeoutForStatements;
//    TimeoutForLogin := TGMOdbcConnection(Source).TimeoutForLogin;
//    //CaseSensitiveCatalogNames := TGMOdbcConnection(Source).CaseSensitiveCatalogNames;
//    CursorOrigin := TGMOdbcConnection(Source).CursorOrigin;
//    NetworkPacketSize := TGMOdbcConnection(Source).NetworkPacketSize;
//    TransactionIsolation := TGMOdbcConnection(Source).TransactionIsolation;
//    TraceProperties := TGMOdbcConnection(Source).TraceProperties;
//    TranslateProperties := TGMOdbcConnection(Source).TranslateProperties;
//    Attributes := TGMOdbcConnection(Source).Attributes;
//   end;
//end;

function TGMOdbcConnection.GetHandleType: LongWord;
begin
  Result := SQL_HANDLE_DBC;
end;

function TGMOdbcConnection.ConnetionAlive: Boolean;
var dead: SQLUINTEGER; //GMStrLen: SQLINTEGER;
begin
  if not Active then Result := False else
   begin
    ODBCCheck(SQLGetConnectAttr(Handle, SQL_ATTR_CONNECTION_DEAD, @dead, SQL_IS_UINTEGER, nil), Self, 'ConnetionAlive');
    case dead of
     SQL_CD_TRUE:  Result := False;
     SQL_CD_FALSE: Result := True;
     else raise EGMException.ObjError(MsgUnknownPropVal('ConnetionAlive', dead), Self, 'ConnetionAlive');
    end;
   end;                                 
end;

function TGMOdbcConnection.SqlIdentifierQuoteChar: TGMString;
var len: SQLSMALLINT;
begin
  if not Active then Exit(''); // cSqlIdQuoteCh
  //CheckIsActive({$I %CurrentRoutine%});
  len := 0;
  ODBCCheck(SQLGetInfo(Handle, SQL_IDENTIFIER_QUOTE_CHAR, nil, 0, @len), Self, {$I %CurrentRoutine%});
  SetLength(Result, (len div SizeOf(TGMChar)));
  if Length(Result) > 0 then
   begin
    ODBCCheck(SQLGetInfo(Handle, SQL_IDENTIFIER_QUOTE_CHAR, PGMChar(Result), (Length(Result)+1) * SizeOf(TGMChar), @len), Self, {$I %CurrentRoutine%});
    Result := GMStrip(Result, cWhiteSpace);
   end;
end;

function TGMOdbcConnection.SqlDateTimeFormatStr: TGMString;
begin
  Result := cODBCDateTimeFormatStrMS;
end;

procedure TGMOdbcConnection.AllocHandle;
var mousePtrWait: IUnknown; envHandle: IGMGetHandle; envHandleType: IGMGetHandleType; connectionString, completedCnStr: TGMString;
    completedCnStrLen: SQLSMALLINT; dlgPrntWnd: HWnd;
begin
  if not GetHandleAllocated then
   begin
    if ODBCEnvironmentIntf = nil then raise EGMException.ObjError(GMFormat(RStrMissingPropVal, ['ODBCEnvironment']), Self, scAllocHandle);

    mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);

    GMSetIntfActive(ODBCEnvironmentIntf, True, {$I %CurrentRoutine%});
    GMCheckQueryInterface(ODBCEnvironmentIntf, IGMGetHandle, envHandle, {$I %CurrentRoutine%});
    GMCheckQueryInterface(ODBCEnvironmentIntf, IGMGetHandleType, envHandleType, {$I %CurrentRoutine%});

    ODBCCheck(SQLAllocHandle(HandleType, envHandle.Handle, FHandle), Self, 'SQLAllocHandle', envHandleType.HandleType, envHandle.Handle);

    if Properties.Obj.AllowDriverDialogs = adpNoDriverDialogs then dlgPrntWnd := 0 else dlgPrntWnd := GMModalDlgParentWnd;

    SetODBCDriverDialogPrntWnd(dlgPrntWnd);
    if Properties.Obj.TransactionIsolation <> [] then SetODBCTransactionIsolation(Properties.Obj.TransactionIsolation);

    if Properties.Obj.AccessMode <> TAccessMode(SQL_MODE_DEFAULT) then SetODBCAccessMode(Properties.Obj.AccessMode);
    if Properties.Obj.CursorOrigin <> TCursorOrigin(SQL_CUR_DEFAULT) then SetODBCCursorOrigin(Properties.Obj.CursorOrigin);
    if Properties.Obj.TimeoutForStatements <> SQL_DEFAULT_TIMEOUT then SetODBCTimeoutForStatements(Properties.Obj.TimeoutForStatements);
    if Properties.Obj.TimeoutForLogin <> SQL_DEFAULT_TIMEOUT then SetODBCTimeoutForLogin(Properties.Obj.TimeoutForLogin);
    if DataSourceCatalog <> '' then SetODBCDataSorceCatalog(DataSourceCatalog);
    if FNetworkPacketSize <> cDfltNetPacketSize then SetODBCNetworkPacketSize(FNetworkPacketSize); // <-- Must be FNetworkPacketSize here

    if caAsynchronOperations in Properties.Obj.Attributes <> Boolean(SQL_ASYNC_ENABLE_DEFAULT) then SetODBCAsynchronOperations(caAsynchronOperations in Properties.Obj.Attributes);
    if caAutoCommitTransactions in Properties.Obj.Attributes <> Boolean(SQL_AUTOCOMMIT_DEFAULT) then SetODBCAutoCommitTransactions(caAutoCommitTransactions in Properties.Obj.Attributes);
    if caCaseSensitiveCatalogNames in Properties.Obj.Attributes <> Boolean(SQL_METADATA_ID_DEFAULT) then SetODBCCaseSensitiveCatalogNames(caCaseSensitiveCatalogNames in Properties.Obj.Attributes);

    TraceProperties.SetODBCValues;

    connectionString := Properties.Obj.connectionString;

    if (Properties.Obj.Username <> '') then connectionString := GMAddOrReplaceValueInConnectionString(connectionString, cStrCnStrUserName, Properties.Obj.Username);
    if (Properties.Obj.Password <> '') then connectionString := GMAddOrReplaceValueInConnectionString(connectionString, cStrCnStrPassword, Properties.Obj.Password);

//    if (Properties.Obj.Username <> '') or (Properties.Obj.Password <> '') then
//     ODBCCheck(SQLConnect(Handle, SQLPCHAR(GMExtractNameFromConnectionString(Properties.Obj.connectionString)), SQL_NTS, SQLPCHAR(Properties.Obj.Username), Length(Properties.Obj.Username), SQLPCHAR(Properties.Obj.Password), Length(Properties.Obj.Password)), Self, 'SQLConnect')
//    else
//     begin

     SetLength(completedCnStr, 8192);
     completedCnStrLen := 0;
     ODBCCheck(SQLDriverConnect(Handle, dlgPrntWnd, PGMChar(connectionString), Length(connectionString), PGMChar(completedCnStr),
                                Length(completedCnStr), @completedCnStrLen, Ord(Properties.Obj.AllowDriverDialogs)), Self, 'SQLDriverConnect');
//     end;                    // cDriverCompletion[caAllowDriverDialogs in Attributes]

    FInternalConnected := True;
    // Set FConnectionString member here, setting connectionString property would trigger CheckIsInaktive
    if caCompleteConnectionString in Properties.Obj.Attributes then Properties.Obj.FConnectionString := PGMChar(completedCnStr);
    TranslateProperties.SetODBCValues;
   end;
end;

procedure TGMOdbcConnection.ReleaseHandle;
//var mousePtrWait: IUnknown;
begin
//mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);

  if InternalConnected then
   begin
    if not (caAutoCommitTransactions in Properties.Obj.Attributes) then RollbackTransaction;
    SQLDisconnect(Handle);
    //ODBCCheck(SQLDisconnect(Handle), Self, 'SQLDisconnect');
    FInternalConnected := False;
   end;

  inherited ReleaseHandle;
end;

function TGMOdbcConnection.ExecuteSQL(const ASQL: TGMString): SQLLEN;
var ODBCStatement: TGMOdbcStatementBase;
begin
  if Length(ASQL) <= 0 then Result := 0 else
   begin
    ODBCStatement := TGMOdbcStatementBase.Create(Self, ASQL, False);
    try
//   ODBCStatement.ODBCConnection := Self;
//   ODBCStatement.SQL.SQLText := ASQL;
     ODBCStatement.Execute;
     Result := ODBCStatement.AffectedRecordCount;
//�     if PAffectedRecordCount <> nil then PAffectedRecordCount^ := ODBCStatement.AffectedRecordCount;
     //OdbcStatement.Close;
     //OdbcStatement.ODBCConnection := nil;
    finally
     ODBCStatement.Free;
    end;
   end;
end;

function TGMOdbcConnection.AskBoolean(const AValueId: LongInt): LongInt; stdcall;
begin
  case AValueId of
   Ord(bvCanModify): Result := GMBooleanAskResult(Properties.Obj.AccessMode <> amReadOnly);
   else Result := Ord(barUnknown);
  end;
end;


{ ---- Property set on OSBC API Level ---- }

procedure TGMOdbcConnection.SetODBCTimeoutForStatements(const AValue: SQLULEN);
begin
  if GetHandleAllocated then
     ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_CONNECTION_TIMEOUT, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcConnection.SetODBCTimeoutForLogin(const AValue: SQLULEN);
begin
  if GetHandleAllocated then
     ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_LOGIN_TIMEOUT, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcConnection.SetODBCDataSorceCatalog(const AValue: TGMString);
begin
  if GetHandleAllocated then
     ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_CURRENT_CATALOG, SQLPOINTER(AValue), Length(AValue)), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcConnection.SetODBCAccessMode(const AValue: TAccessMode);
var accMode: SQLULEN;
begin
  if Active then
   begin
    case AValue of
     amReadWrite: accMode := SQL_MODE_READ_WRITE;
     amReadOnly:  accMode := SQL_MODE_READ_ONLY;
     else raise EGMException.ObjError(MsgUnknownValue('TAccessMode', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_ACCESS_MODE, SQLPOINTER(accMode), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcConnection.SetODBCCursorOrigin(const AValue: TCursorOrigin);
var curOrg: SQLULEN;
begin
  if Active then
   begin
    case AValue of
     coUseODBCCursorsIfNeeded: curOrg := SQL_CUR_USE_IF_NEEDED;
     coUseODBCCursors:         curOrg := SQL_CUR_USE_ODBC;
     coUseDriverCursors:       curOrg := SQL_CUR_USE_DRIVER;
     else raise EGMException.ObjError(MsgUnknownValue('TCursorOrigin', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_ODBC_CURSORS, SQLPOINTER(curOrg), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcConnection.SetODBCAsynchronOperations(const AValue: Boolean);
var asyncOp: SQLULEN;
begin
  if Active then
   begin
    if AValue then asyncOp := SQL_ASYNC_ENABLE_ON else asyncOp := SQL_ASYNC_ENABLE_OFF;
    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_ASYNC_ENABLE, SQLPOINTER(asyncOp), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcConnection.SetODBCAutoCommitTransactions(const AValue: Boolean);
var autoCommit: SQLLEN;
begin
  if Active then
   begin
    if AValue then autoCommit := SQL_AUTOCOMMIT_ON else autoCommit := SQL_AUTOCOMMIT_OFF;
    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_AUTOCOMMIT, SQLPOINTER(autoCommit), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcConnection.SetODBCDriverDialogPrntWnd(const AValue: HWnd);
//var WndHandle: HWnd;
begin
//if Active then
// begin
//  if AValue then WndHandle := vGMTopWindow else WndHandle := 0;
    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_QUIET_MODE, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
// end;
end;

procedure TGMOdbcConnection.SetODBCCaseSensitiveCatalogNames(const AValue: Boolean);
var CatalogCase: SQLLEN;
begin
  if Active then
   begin
    if AValue then CatalogCase := SQL_FALSE else CatalogCase := SQL_TRUE;
    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_METADATA_ID, SQLPOINTER(CatalogCase), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcConnection.SetODBCNetworkPacketSize(const AValue: SQLULEN);
begin
  //if Active then
   //try
    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_PACKET_SIZE, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
   //except
    //if not GMComponentIsLoading(Self) then raise;
   //end;
end;

procedure TGMOdbcConnection.SetODBCTransactionIsolation(const AValue: TTransactionIsolations);
var txnIsolation: PtrUInt;
begin
  if Active then
   begin
    txnIsolation := 0;

    if tiReadUncommitted in AValue then txnIsolation := txnIsolation or SQL_TRANSACTION_READ_UNCOMMITTED;
    if tiReadCommitted in AValue then txnIsolation := txnIsolation or SQL_TRANSACTION_READ_COMMITTED;
    if tiRepeatableRead in AValue then txnIsolation := txnIsolation or SQL_TRANSACTION_REPEATABLE_READ;
    if tiSerializable in AValue then txnIsolation := txnIsolation or SQL_TRANSACTION_SERIALIZABLE;

    ODBCCheck(SQLSetConnectAttr(Handle, SQL_ATTR_TXN_ISOLATION, SQLPOINTER(txnIsolation), 0), Self, {$I %CurrentRoutine%});
   end;
end;


{ ---- property get/set ---- }

procedure TGMOdbcConnection.OnBeforeIntfSourceChange(const AOldSource, ANewSource: IUnknown);
begin
  if AOldSource <> ANewSource then CheckIsInactive('ODBCEnvironment ' + RStrProperty);
end;

function TGMOdbcConnection.GetODBCEnvironmentIntf: IUnknown;
begin
  Result := ObjectConnectedTo.InterfaceSource;
end;

procedure TGMOdbcConnection.SetODBCEnvironmentIntf(const AValue: IUnknown);
begin
  ObjectConnectedTo.InterfaceSource := AValue; // <- will be checked vai OnBeforeIntfSourceChange
  {if AValue <> ODBCEnvironmentIntf then
   begin
    CheckIsInactive('ODBCEnvironment ' + RStrProperty);
    ObjectConnectedTo.InterfaceSource := AValue;
   end;}
end;

function TGMOdbcConnection.GetODBCEnvironment: TGMOdbcEnvironment;
begin
  Result := GMObjFromIntf(ObjectConnectedTo.InterfaceSource) as TGMOdbcEnvironment;
end;

procedure TGMOdbcConnection.SetODBCEnvironment(const AValue: TGMOdbcEnvironment);
begin
  ObjectConnectedTo.InterfaceSource := GMObjAsIntf(AValue); // <- will be checked vai OnBeforeIntfSourceChange
  {if AValue <> ODBCEnvironment then
   begin
    CheckIsInactive('ODBCEnvironment ' + RStrProperty);
    ObjectConnectedTo.InterfaceSourceObject := AValue;
   end;}
end;

procedure TGMOdbcConnection.SetDataSourceCatalog(const AValue: TGMString);
begin
  if AValue <> DataSourceCatalog then
   begin
    CheckIsInactive('DataSourceCatalog ' + RStrProperty);
    FDataSourceCatalog := AValue;
   end;
end;

function TGMOdbcConnection.GetTransactionSupport: TTransactionSupport;
var TxnSupport: SQLUSMALLINT; //Len: SQLSMALLINT;
begin
  CheckIsActive('TransactionSupport ' + RStrProperty);
  ODBCCheck(SQLGetInfo(Handle, SQL_TXN_CAPABLE, @TxnSupport, SizeOf(TxnSupport), nil), Self, {$I %CurrentRoutine%});
  case TxnSupport of
   SQL_TC_NONE:       Result := txnNone;
   SQL_TC_DML:        Result := txnDMLOnly;
   SQL_TC_DDL_COMMIT: Result := txnDDLCommit;
   SQL_TC_DDL_IGNORE: Result := txnDDLIgnore;
   SQL_TC_ALL:        Result := txnAll;
   else raise EGMException.ObjError(MsgUnknownValue('SQL_TXN_CAPABLE', TxnSupport), Self, {$I %CurrentRoutine%});
  end;
end;

function TGMOdbcConnection.GetNetworkPacketSize: SQLUINTEGER;
//var GMStrLen: SQLINTEGER;
begin
  if not Active then Result := FNetworkPacketSize else
   if not ODBCSucceeded(SQLGetConnectAttr(Handle, SQL_ATTR_PACKET_SIZE, @Result, SQL_IS_UINTEGER, nil)) then Result := 0;
end;

procedure TGMOdbcConnection.SetNetworkPacketSize(const AValue: SQLUINTEGER);
begin
  if AValue <> NetworkPacketSize then
   begin
    SetODBCNetworkPacketSize(AValue);
    FNetworkPacketSize := AValue;
   end;
end;

{procedure TGMOdbcConnection.SetAsynchronOperations(const AValue: Boolean);
begin
  if AValue <> AsynchronOperations then
   begin
    CheckIsInactive('AsynchronOperations ' + RStrProperty);
    FAsynchronOperations := AValue;
   end;
end;}

{procedure TGMOdbcConnection.SetAutoCommitTransactions(const AValue: Boolean);
begin
  if AValue <> AutoCommitTransactions then
   begin
    SetODBCAutoCommitTransactions(AValue);
    FAutoCommitTransactions := AValue;
   end;
end;}

{procedure TGMOdbcConnection.SetAllowDriverDialogs(const AValue: Boolean);
begin
  if AValue <> AllowDriverDialogs then
   begin
    SetODBCDriverDialogPrntWnd(AValue);
    FAllowDriverDialogs := AValue;
   end;
end;}

{procedure TGMOdbcConnection.SetCaseSensitiveCatalogNames(const AValue: Boolean);
begin
  if AValue <> CaseSensitiveCatalogNames then
   begin
    SetODBCCaseSensitiveCatalogNames(AValue);
    FCaseSensitiveCatalogNames := AValue;
   end;
end;}

procedure TGMOdbcConnection.SetTraceProperties(const AValue: TOdbcTraceProperties);
begin
  if not TraceProperties.IsEqualTo(AValue) then TraceProperties.AssignFromObj(AValue);
end;

procedure TGMOdbcConnection.SetTranslateProperties(const AValue: TOdbcTranslateProperties);
begin
  if not TranslateProperties.IsEqualTo(AValue) then TranslateProperties.AssignFromObj(AValue);
end;


{ ----------------------------- }
{ ---- TOdbcBlobStreamBase ---- }
{ ----------------------------- }

constructor TOdbcBlobStreamBase.Create(const AMode: DWORD; const AOwner: TObject; const AStatementHandle: SQLHANDLE; const ARefLifeTime: Boolean);
begin
  inherited Create(AMode, '', ARefLifeTime);
  FOwner := AOwner;
  FStatementHandle := AStatementHandle;
end;


{ ----------------------------- }
{ ---- TOdbcBlobReadStream ---- }
{ ----------------------------- }

constructor TOdbcBlobReadStream.Create(const AOwner: TObject; const ADataType: TGMDBColumnDataType; const AColumnPosition: LongInt;
                                       const AStatementHandle: SQLHANDLE; const ATotalSize: Int64; const AStartData: AnsiString;
                                       const ARefLifeTime: Boolean);
begin
  inherited Create(STGM_READ, AOwner, AStatementHandle, ARefLifeTime);
  FColumnPosition := AColumnPosition;
  FSize := ATotalSize; // cInvalidDataSize;
  FDataType := ADataType;
  FStartData := AStartData;
end;

procedure TOdbcBlobReadStream.InternalRead(pv: Pointer; cb: LongWord; var pcbRead: LongWord);
var n: SQLLEN; startLen: Int64; // pos: Int64; // ret: SQLRETURN;
begin
  n := 0; startLen := 0; // pos := FPosition;

  if FPosition < Length(FStartData) then
   begin
    startLen := Min(Length(FStartData) - FPosition, cb);
    Move((PAnsiChar(FStartData) + FPosition)^, pv^, startLen);
    Dec(cb, startLen);
    pv := GMAddPtr(pv, startLen);
//  Inc(pos, startLen);
   end;

  //
  // SQL Server: SQLGetData returns in DataLenthIndicator the ammount of data that was present before the call was made.
  //

  if (FPosition + startLen < FSize) and (cb > 0) then // ret := SQL_SUCCESS else
// begin
//  ret := SQLGetData(FStatementHandle, FColumnPosition, SQL_C_BINARY, pv, cb, @n);
    OdbcCheck(SQLGetData(FStatementHandle, FColumnPosition, SQL_C_BINARY, pv, cb, @n), FOwner, {$I %CurrentRoutine%});
// end;

  if @pcbRead <> nil then pcbRead := Max(0, Min(cb + startLen, FSize - FPosition));

//if @pcbRead <> nil then
//  if ret = SQL_SUCCESS_WITH_INFO then pcbRead := cb else pcbRead := Max(0, FSize - FPosition);

//if FPosition < FSize then
// begin
//  OdbcCheck(SQLGetData(FStatementHandle, FColumnPosition, SQL_C_BINARY, pv, cb, @n), FOwner, {$I %CurrentRoutine%});
////  if FSize = cInvalidDataSize then FSize := n;
////  n := GMBoundedInt(cb, 0, FSize - FPosition);
//  //Inc(FPosition, Result);
// end;
end;

procedure TOdbcBlobReadStream.InternalWrite(pv: Pointer; cb: LongWord; var pcbWritten: LongWord);
begin
  raise EGMException.ObjError(GMFormat(srUnsupportedStreamOperation, [{$I %CurrentRoutine%}]), FOwner);
end;

//function TOdbcBlobReadStream.Seek(dlibMove: Int64; dwOrigin: LongInt; out libNewPosition: Int64): HResult;
//begin
//  if (Offset <> 0) or (Origin <> soFromCurrent) then raise EGMException.ObjError(GMFormat(RStrUnsupportedSeekOffset, [Offset, Origin]), FOwner, {$I %CurrentRoutine%});
//  Result := FPosition;
//end;


{ ------------------------------ }
{ ---- TOdbcBlobWriteStream ---- }
{ ------------------------------ }

procedure TOdbcBlobWriteStream.InternalRead(pv: Pointer; cb: LongWord; var pcbRead: LongWord);
begin
  raise EGMException.ObjError(GMFormat(srUnsupportedStreamOperation, [{$I %CurrentRoutine%}]), FOwner);
end;

procedure TOdbcBlobWriteStream.InternalWrite(pv: Pointer; cb: LongWord; var pcbWritten: LongWord);
begin
  OdbcCheck(SQLPutData(FStatementHandle, pv, cb), FOwner, {$I %CurrentRoutine%});
  if @pcbWritten <> nil then pcbWritten := cb;
end;

function TOdbcBlobWriteStream.Seek(dlibMove: Int64; dwOrigin: LongInt; libNewPosition: PInt64): HResult;
{$IFDEF FPC}{$push}{$WARN 5033 off : Function result variable does not seem to be initialized}{$ENDIF}
begin
//Result := E_NOTIMPL;
  {if (Offset <> 0) or (Origin <> soFromCurrent) then} raise EGMException.ObjError(GMFormat(srUnsupportedSeekOffset, [dlibMove, dwOrigin]), FOwner, {$I %CurrentRoutine%});
  //Result := FPosition;
end;
{$IFDEF FPC}{$pop}{$ENDIF}


{ ------------------------------- }
{ ---- TODBCFieldValueBuffer ---- }
{ ------------------------------- }

constructor TODBCFieldStateValueBuffer.Create(const AOwner: TObject;
                                              const ADataType: TGMDBColumnDataType;
                                              const AZeroInit: Boolean;
                                              const AFreeMemoryOnDestroy: Boolean;
                                              const ARefLifeTime: Boolean);
begin
  inherited Create(AOwner, ADataType, AZeroInit, AFreeMemoryOnDestroy, ARefLifeTime);
  DataLength := SQL_NULL_DATA;
end;

function TODBCFieldStateValueBuffer.CalculateBufferSize: LongInt;
begin
  Result := ODBCCalculateValueBufferSize(DataType, 0, {$I %CurrentRoutine%});
end;

function TODBCFieldStateValueBuffer.OwnerRecordset: TGMOdbcRecordsetBase;
begin
  Result := Owner as TGMOdbcRecordsetBase;
end;

function TODBCFieldStateValueBuffer.IsNull: Boolean;
begin
  Result := DataLength = SQL_NULL_DATA;
end;

//procedure TODBCFieldStateValueBuffer.InternalSetSize(ANewSize: Int64);
//begin
//  inherited InternalSetSize(ANewSize);
//  //if DataSize = 0 then DataLength := SQL_NULL_DATA;
//end;

function TODBCFieldStateValueBuffer.WriteAt(ulOffset: Int64; pv: Pointer; cb: LongInt; pcbWritten: PLongint): HResult;
begin
  Result := inherited WriteAt(ulOffset, pv, cb, pcbWritten);
  DataLength := DataSize;
end;

function TODBCFieldStateValueBuffer.AskInteger(const AValueId: LongInt): LongInt;
begin
  //Result := inherited AskInteger(AValueId);

  //if Result = CInvalidIntValue then
   case AValueId of
    Ord(ivDataLength): Result := DataLength;
    else Result := cInvalidIntValue;
   end;
end;

procedure TODBCFieldStateValueBuffer.Invalidate(const AResetOffset, ASetToNULL: Boolean);
begin
  inherited Invalidate(AResetOffset, ASetToNULL);
  if IsStreamedFieldDataType(DataType) then Clear;
  if ASetToNULL then DataLength := SQL_NULL_DATA;
//FFirstDataRead := False;
end;

//procedure TODBCFieldStateValueBuffer.ReadFirstData;
//begin
//end;

function TODBCFieldStateValueBuffer.IsBookmarkColumn: Boolean;
begin
  Result := False;
end;

procedure TODBCFieldStateValueBuffer.AssignFromIntf(const Source: IUnknown);
begin
  // DataLength may indicate NULL Value which skips data copy in inherited AssignFromIntf
  DataLength := GMCheckAskInteger(Source, Ord(ivDataLength), {$I %CurrentRoutine%});
  inherited AssignFromIntf(Source);
  // WriteAt will set DataLength := DataSize which is wrong in case of TGMString/Memo Data Types
  DataLength := GMCheckAskInteger(Source, Ord(ivDataLength), {$I %CurrentRoutine%});
end;

function TODBCFieldStateValueBuffer.GetDataLength: PtrInt;
begin
  Result := FDataLength;
end;

procedure TODBCFieldStateValueBuffer.SetDataLength(const AValue: PtrInt);
begin
  FDataLength := AValue;
end;

//procedure TODBCFieldStateValueBuffer.AccessBufferContents(const AAccessMode: TGMValueBufferAccessMode; const ANewSize: LongInt);
//begin
////Assert(IsStreamedFieldDataType(DataType));
//if not IsStreamedFieldDataType(DataType) then FetchData(False) else
// case AAccessMode of
//  baRead: FetchData(False);
//  baWrite:
//   begin
//    SetSize(ANewSize);
//    // Once we are writing there is no need to
//    // fetch anything until Apply or Cancel Changes.
//    // Otherwise it is questionalbe how we did get here ...
////    FFirstDataRead := True;
//    DataFetched := True;
//    Modified := True;
//    InvalidateDisplayText;
//   end;
// end;
//end;

function TODBCFieldStateValueBuffer.InternalBuildDisplayText: TGMString;
//function PercentCompressionRatio(const DataLength, StoredDataLength: Integer): Integer;
//begin
//  if DataLength = 0 then Result := 0 else Result := Round((1 - (StoredDataLength / DataLength)) * 100);
//end;
begin
  if not OwnerRecordset.ODBCFetchSuccess and (OwnerRecordset.State <> rsInserting) then
   begin
    if (OwnerRecordset.CursorType <> ctUniDirectional) and not OwnerRecordset.IsEmpty then
     Result := '<' + GMStringJoin(ODBCReturnCodeAsString(OwnerRecordset.FetchResult.ResultCode), ': ', OwnerRecordset.FetchResult.ErrorText) + '>'
    else
     Result := '';
   end else
  //if IsNull then Result := ''
  //else
   case DataType of
    fdtBinary:
     if IsBookmarkColumn then
      case DataSize of
       SizeOf(Word):     Result := GMIntToStr(Word(Memory^));
       SizeOf(LongWord): Result := GMIntToStr(LongWord(Memory^));
       SizeOf(Int64):    Result := GMIntToStr(Int64(Memory^));
       else Result := GMEncodeBase16(Memory, DataSize);
      end
     else
      Result := inherited InternalBuildDisplayText;
//    Result := GMFormat(RStrBlobDisplayText, [DataLength, FStoredDataLength, PercentCompressionRatio(DataLength, FStoredDataLength)]);

    else Result := inherited InternalBuildDisplayText;
   end;
end;

function TODBCFieldStateValueBuffer.InternalGetUnionValue: RGMUnionValue;
  function GetOdbcAnsiString: AnsiString;
  var strLen: Integer; pChA: PAnsichar;
  begin
    if (Memory = nil) or (DataLength <= 0) and (DataSize <= 0) then Exit('');
    strLen := Max(0, Min(DataLength, DataSize - 1));
    if (strLen > 0) and (Owner is TGMOdbcRecordsetBase) and (raStripTrailingBlanks in TGMOdbcRecordsetBase(Owner).Attributes) then
     begin
      pChA := GMStrCRLScanA(PAnsiChar(Memory) + strLen - 1, ' ', strLen);
      if pChA = nil then strLen := 0 else strLen := pChA - PAnsiChar(Memory) + 1;
     end;
    SetString(Result, PAnsiChar(Memory), strLen);
  end;

  function GetOdbcUnicodeString: UnicodeString;
  var strLen: Integer; pChW: PWideChar;
  begin
    if (Memory = nil) or (DataLength <= 0) and (DataSize <= 0) then Exit('');
    strLen := Max(0, Min(DataLength div SizeOf(WideChar), (DataSize div SizeOf(WideChar)) - 1));
    if (strLen > 0) and (Owner is TGMOdbcRecordsetBase) and (raStripTrailingBlanks in TGMOdbcRecordsetBase(Owner).Attributes) then
     begin
      pChW := GMStrCRLScanW(PWideChar(Memory) + strLen - 1, ' ', strLen);
      if pChW = nil then strLen := 0 else strLen := pChW - PWideChar(Memory) + 1;
     end;
    SetString(Result, PWideChar(Memory), strLen);
  end;

  function GetOdbcAnsiText: AnsiString;
  var retCode: SQLRETURN;
  begin
    SetLength(Result, cGetDataBufSizeInChars);
    retCode := SQLGetData(OwnerRecordset.Handle, FColumnPosition, SQL_C_CHAR, PAnsiChar(Result), Length(Result)+1, @FDataLength);
    if retcode < 0 then ODBCCheck(retcode, Self, 'SQLGetData', OwnerRecordset.HandleType, OwnerRecordset.Handle);
    if FDataLength < 0 then SetLength(Result, 0) else
      if FDataLength < Length(Result) then SetLength(Result, FDataLength);
  end;

  function GetOdbcUnicodeText: UnicodeString;
  var retCode: SQLRETURN;
  begin
    SetLength(Result, cGetDataBufSizeInChars);
    retCode := SQLGetData(OwnerRecordset.Handle, FColumnPosition, SQL_C_WCHAR, PWideChar(Result), (Length(Result)+1) * SizeOf(WideChar), @FDataLength);
    if retcode < 0 then ODBCCheck(retcode, Self, 'SQLGetData', OwnerRecordset.HandleType, OwnerRecordset.Handle);
    if FDataLength < 0 then SetLength(Result, 0) else
      if FDataLength div SizeOf(WideChar) < Length(Result) then SetLength(Result, FDataLength div SizeOf(WideChar));
  end;
begin
  case DataType of
   //fdtNumeric:          Result := CurrencyFromODBCNumeric(SQL_NUMERIC_STRUCT(Memory^));
   fdtDate:             Result := DateTimeFromODBCDate(PSQL_DATE_STRUCT(Memory));
   fdtTime:             Result := DateTimeFromODBCTime(PSQL_TIME_STRUCT(Memory));
   fdtDateTime:         Result := DateTimeFromODBCTimestamp(PSQL_TIMESTAMP_STRUCT(Memory));
   fdtAnsiString:       Result := GetOdbcAnsiString;
   fdtUnicodeString:    result := GetOdbcUnicodeString;
   fdtAnsiText:         Result := GetOdbcAnsiText;
   fdtUnicodeText:      Result := GetOdbcUnicodeText;
   else Result := inherited InternalGetUnionValue;
  end;
end;

procedure TODBCFieldStateValueBuffer.InternalSetNullValue;
begin
  if IsStreamedFieldDataType(DataType) then Clear(False); // else DataLength := SQL_NULL_DATA;
  DataLength := SQL_NULL_DATA;
  inherited InternalSetNullValue;
end;

procedure TODBCFieldStateValueBuffer.InternalSetUnionValue(const AValue: RGMUnionValue);
begin
  DataLength := MemoryBuffer.SizeInBytes;
  case DataType of
   fdtDate:             ODBCDateFromDateTime(AValue, PSQL_DATE_STRUCT(Memory));
   fdtTime:             ODBCTimeFromDateTime(AValue, PSQL_TIME_STRUCT(Memory));
   fdtDateTime:         ODBCTimestampFromDateTime(AValue, PSQL_TIMESTAMP_STRUCT(Memory));
   else inherited InternalSetUnionValue(AValue);
  end;
end;

//procedure TODBCFieldStateValueBuffer.SetUnionValue(const AValue: RGMUnionValue);
//begin
//inherited SetValue(AValue);
////FFirstDataRead := True;
//end;


{ ------------------------------- }
{ ---- TODBCFieldValueBuffer ---- }
{ ------------------------------- }

//constructor TODBCFieldValueBuffer.CreateFieldBuffer(const AOwner: TObject;
//                                                  const ADataType: TGMDBColumnDataType;
//                                                  const AColumnPosition: LongInt;
//                                                  const AFieldName: TGMString;
//                                                  const ASizeInBytes: Cardinal;
//                                                  const AMaxStrLength: Cardinal;
//                                                  const AStatementHandle: THandle);
////const cFirstDataSize: array [Boolean] of Integer = (0, SizeOf(TGMCompressedBlobHeaderData));
//begin
//inherited CreateFieldBuffer(AOwner, ADataType, AColumnPosition, AFieldName, ASizeInBytes, AMaxStrLength, AStatementHandle);
////FFirstDataSize := cFirstDataSize[ADataType = fdtBinary]; // <- Don't compress Memos
//end;

destructor TODBCFieldValueBuffer.Destroy;
begin
  try inherited Destroy; except end; // Realloc -> BindBuffer -> may raise -> never raise in destructor, memory leak!
end;

function TODBCFieldValueBuffer.IsFixedBufferSize: Boolean;
begin
  Result := not IsStreamedFieldDataType(DataType) or IsBookmarkColumn;
end;

//procedure TODBCFieldValueBuffer.Invalidate(const AResetOffset: Boolean);
//begin
//  inherited Invalidate(AResetOffset);
//  if IsStreamedFieldDataType(DataType) and not IsBookmarkColumn then Clear(AResetOffset);
//end;

procedure TODBCFieldValueBuffer.OnAfterRealloc(const Sender: TObject);
begin
  if FBufferBound and (MemoryBuffer <> nil) then BindBuffer(True);
end;

function TODBCFieldValueBuffer.CalculateBufferSize: LongInt;
begin
  if (StatementHandle = cOdbcInvalidHandle) or (FColumnPosition = cInvalidColumnPos) then Result := 0
  else
  if IsBookmarkColumn then
     Result := SizeInBytes
   else
     Result := ODBCCalculateValueBufferSize(DataType, SizeInBytes, {$I %CurrentRoutine%});
end;

function TODBCFieldValueBuffer.IsBookmarkColumn: Boolean;
begin
  Result := FColumnPosition = cOdbcBookmarkColPos;
end;

procedure TODBCFieldValueBuffer.SetupDataLengthForUpdate;
begin
  //BindBlobBuffer(True);
  FSavedDataLength := DataLength;
  if not Modified then DataLength := SQL_COLUMN_IGNORE
  else
  if IsStreamedFieldDataType(DataType) then
   if FValueReadStream <> nil then
     DataLength := OdbcDataLenAtExec(GMIStreamSize(FValueReadStream))
    else
     DataLength := OdbcDataLenAtExec(DataLength);
end;

procedure TODBCFieldValueBuffer.RestoreDataLength;
begin
  DataLength := FSavedDataLength;
  //BindBlobBuffer(False);
end;

//procedure TODBCFieldValueBuffer.InvalidateAndReadFirstData(const AResetOffset: Boolean);
//begin
//Invalidate(AResetOffset);
////  ReadFirstData;
//end;

//procedure TODBCFieldValueBuffer.ReadFirstData;
//var fetchSize: LongInt;
////
//// Why this?
////
//// The ODBC fetch operations doesn't return the length of BLOB Data.
////
//// SQLGetData is the only way to know about the Size and NULL state of BLOB Data.
//// The smallest ammount to transfer is 1 Byte. And if there is a first byte of data
//// we must keep it, since SQLGetData gives Data only once.
////
//begin
//  if IsStreamedFieldDataType(DataType) and not IsBookmarkColumn and not DataFetched then
//   begin
//    if not OwnerRecordset.ODBCFetchSuccess then Clear else
//     begin
//      // MS-Access Memo fields makes Access Violation when FFirstDataSize <> SizeOf(TGMChar)
//      case DataType of
//       fdtAnsiText, fdtUnicodeText: fetchSize := GMCharSizeInBytes(DataType);
//       fdtBinary: fetchSize := 1;
//       else fetchSize := 0;
//      end;
//
//      if fetchSize = 0 then Exit;
//
//      SetSize(fetchSize);
//      ODBCCheck(SQLGetData(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), Memory,
//                DataSize, @FDataLength), Owner, {$I %CurrentRoutine%});
//
//      if IsNull then Clear(False);
////       begin Clear(False); DataFetched := True; end;
////     else
////       Inc(FDataLength, DataSize); // <- The size of the fetched data has already been subtracted from FDataLength
//     end;
//
//    DataFetched := True;
////  FFirstDataRead := True;
//   end;
//end;

//procedure TODBCFieldValueBuffer.FetchBlobData;
//var DataLen: LongInt;
//procedure UnCompressData;
////var DecompressorStream, SourceStream, DestStream: IStream; mousePtrWait: IUnknown;
//begin
//  Assert(False, 'ToDo: TODBCFieldValueBuffer.UnCompressData');
//  {Clear;
//  SourceStream := TOdbcBlobReadStream.Create(Owner, DataType, ColumnPosition, StatementHandle);
//  try
//   DestStream := TGMLockBytesStream.Create(Self);
//   try
//    DecompressorStream := TGMDecompressorStream.Create(SourceStream);
//    try
//     mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);
//     GMCopyStream(DecompressorStream, DestStream);
//    finally DecompressorStream.Free; end;
//   finally DestStream := nil; end;
//  finally SourceStream := nil; end;
//  InvalidateDisplayText;
//  //OwnerRecordset.AfterValueChange(FieldName);}
//end;
//begin
//if IsStreamedFieldDataType(DataType) and not DataFetched then
// begin
//  ReadFirstData; // <- May set: DataFetched := True;
//  if not DataFetched then
//   begin
//    if DataIsCompressed then UnCompressData else
//     begin
//      SetSize(DataLength + GMCharSizeInBytes(DataType)); DataLen := 0;
//      SQLGetData(StatementHandle, ColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), GMAddPtr(Memory, FFirstDataSize), DataSize - FFirstDataSize, @DataLen);
//      //Assert(SQLCode = SQL_SUCCESS);
//     end;
//    DataFetched := True;
//    OwnerRecordset.AfterValueChange(FieldName);
//   end;
// end;
//end;

//procedure TODBCFieldValueBuffer.InternalFetchData(const AForDisplayText: Boolean = False);
//var termChPos, chSize: Integer; pChA: PAnsichar; pChW: PWideChar;
//begin
//  if not OwnerRecordset.ODBCFetchSuccess then DataFetched := True
//  else
//  if not IsStreamedFieldDataType(DataType) or IsBookmarkColumn then
//   begin
////  if IsNull then SetSize(0) else
//    if IsStringFieldDataType(DataType) and not IsNull
//       and (Owner is TGMOdbcRecordsetBase) and (raStripTrailingBlanks in TGMOdbcRecordsetBase(Owner).Attributes)
//       and (Memory <> nil) and (DataLength >= 0) and (DataSize > 0) then
//     begin
//      //
//      // Some ODBC drivers deliver strings padded right with blanks, strip right blanks here.
//      //
//      chSize := GMCharSizeInBytes(DataType); // <- GMCharSizeInBytes should not return 0 when IsStringFieldDataType = True
//
//      if chSize > 0 then // <- avoid division by zero
//       begin
//        //
//        // DataLength is in bytes!
//        //
//        termChPos := Min(DataLength div chSize, (DataSize div chSize) - 1);
//
//        case DataType of
//         fdtAnsiString:
//          begin
//   //         while (termChPos >= 0) and ((PAnsiChar(Memory) + termChPos)^ = ' ') do Dec(termChPos);
//           if termChPos > 0 then
//            begin
//             pChA := GMStrCRLScanA(PAnsiChar(Memory) + termChPos - 1, ' ', termChPos);
//             if pChA = nil then termChPos := 0 else termChPos := pChA - PAnsiChar(Memory) + 1;
//            end;
//
//           (PAnsiChar(Memory) + termChPos)^ := #0;
//          end;
//
//         fdtUnicodeString:
//          begin
//   //         while (termChPos >= 0) and ((PWideChar(Memory) + termChPos)^ = ' ') do Dec(termChPos);
////         wVal := PWideChar(Memory);
//
//           if termChPos > 0 then
//            begin
//             pChW := GMStrCRLScanW(PWideChar(Memory) + termChPos - 1, ' ', termChPos);
//             if pChW = nil then termChPos := 0 else termChPos := pChW - PWideChar(Memory) + 1;
//            end;
//
//           (PWideChar(Memory) + termChPos)^ := #0;
//          end;
//        end;
//       end;
//     end;
//
//    DataFetched := True;
//   end
//  else
////  if AForDisplayText and (DataType = fdtBinary) then ReadFirstData else FetchBlobData;
//    ReadFirstData;
//end;

function TODBCFieldValueBuffer.CreateValueStream(const AMode: DWORD): ISequentialStream;
var firstData: AnsiString;
begin
  if not IsStreamedFieldDataType(DataType) then Result := inherited CreateValueStream(AMode) else
   case AMode of
    STGM_WRITE: Result := TOdbcBlobWriteStream.Create(STGM_WRITE, Owner, StatementHandle);
    STGM_READ: begin
                //
                // Don't pass Memory and DataSize here, memory may be reallocated after additional writes.
                // So better copy contents with data read by ReadFirstData to an AnsiString.
                //
                SetLength(firstData, DataSize);
                Move(Memory^, PAnsiChar(firstData)^, Length(firstData));
                Result := TOdbcBlobReadStream.Create(Owner, DataType, FColumnPosition, StatementHandle, DataLength, firstData);
               end;
   end;
end;

procedure TODBCFieldValueBuffer.StoreBlobData;
var dstStream: ISequentialStream;
begin
  if IsStreamedFieldDataType(DataType) and (FValueReadStream <> nil) then
   begin
    dstStream := TOdbcBlobWriteStream.Create(STGM_WRITE, Owner, StatementHandle);
    GMCopyIStream(FValueReadStream, dstStream);
   end;
end;

//procedure TODBCFieldValueBuffer.BindBlobBuffer(const ABind: Boolean);
//begin
//  if IsStreamedFieldDataType(DataType) then
//   if ABind then
//    ODBCCheck(SQLBindCol(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), @FColumnPosition, SizeOf(FColumnPosition), @FDataLength), Owner, {$I %CurrentRoutine%})
//   else
//    ODBCCheck(SQLBindCol(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), nil, 0, @FDataLength), Owner, {$I %CurrentRoutine%});
//end;

procedure TODBCFieldValueBuffer.BindBuffer(const ABind: Boolean);
begin
  if not ABind then
    ODBCCheck(SQLBindCol(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), nil, 0, @FDataLength), Owner, {$I %CurrentRoutine%})
  else
   if IsStreamedFieldDataType(DataType) then // or IsBookmarkColumn then // <- Blob Fields never have their Buffer Bound!
     ODBCCheck(SQLBindCol(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), nil, 0, @FDataLength), Owner, {$I %CurrentRoutine%})
   else
     ODBCCheck(SQLBindCol(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), Memory, PtrInt(MemoryBuffer.SizeInBytes) - Offset, @FDataLength), Owner, {$I %CurrentRoutine%});

//
//// begin
////  if ABind then ODBCCheck(SQLBindCol(StatementHandle, ColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), nil, 0, @FDataLength), Owner, {$I %CurrentRoutine%});
//// end
////else
//   begin
//    if not ABind then
//     ODBCCheck(SQLBindCol(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), nil, 0, @FDataLength), Owner, {$I %CurrentRoutine%})
//    else
//     ODBCCheck(SQLBindCol(StatementHandle, FColumnPosition, ODBCTypeFromFieldDataType(DataType, {$I %CurrentRoutine%}), Memory, PtrInt(MemoryBuffer.SizeInBytes) - Offset, @FDataLength), Owner, {$I %CurrentRoutine%});
//
//    FBufferBound := ABind;
//   end;
end;

//procedure TODBCFieldValueBuffer.AssignFromIntf(const ASource: IUnknown);
//begin
//inherited AssignFromIntf(ASource);
//if DataFetched then FFirstDataRead := True;
//end;


{ ---------------------- }
{ ---- TGMOdbcField ---- }
{ ---------------------- }

{constructor TGMOdbcField.Create(const ARecordset: TObject; const ACreateData: RGMFieldCreateData);
begin
  inherited Create(ARecordset, ACreateData);
end;

destructor TGMOdbcField.Destroy;
begin
  inherited Destroy;
end;}

function TGMOdbcField.ValueBufferCreateClass: TGMFieldValueBufferClass;
begin
  Result := TODBCFieldValueBuffer;
end;

function TGMOdbcField.ODBCValueBuffer(const AValueBufferInstance: EGMValueBufferInstance): TODBCFieldValueBuffer;
begin
  Result := ValueBuffer(AValueBufferInstance) as TODBCFieldValueBuffer;
end;

procedure TGMOdbcField.BindBuffer(const ABind: Boolean);
begin
  ODBCValueBuffer(vbiValue).BindBuffer(ABind);
end;

//function TGMOdbcField.AccessValueBuffer(const AAccessMode: LongInt; const AIID: TGUID; out Intf; const AValueBufferInstance: LongInt): HResult;
//begin
//GMCheckIntRange(cStrBufAccessTypeName, AAccessMode, Ord(Low(TGMValueBufferAccessMode)), Ord(High(TGMValueBufferAccessMode)), Owner, {$I %CurrentRoutine%});
//GMCheckIntRange(cStrValBufInstTypeName, AValueBufferInstance, Ord(Low(EGMValueBufferInstance)), Ord(High(EGMValueBufferInstance)), Owner, {$I %CurrentRoutine%});
//
//// Just check if we would get the desired interface
//Result := CQIResult[ValueBuffer(EGMValueBufferInstance(AValueBufferInstance)).GetInterface(AIID, Intf)];
//IUnknown(Intf) := nil;
//if Result = S_OK then
// begin
//  if AAccessMode = Ord(baWrite) then
//   begin
//    EditRecordset;
//    CheckUpdatableState({$I %CurrentRoutine%});
//   end;
//  ODBCValueBuffer(EGMValueBufferInstance(AValueBufferInstance)).AccessBufferContents(TGMValueBufferAccessMode(AAccessMode));
//  // Buffers may have been swapped, so get the Interface again
//  Assert(ValueBuffer(EGMValueBufferInstance(AValueBufferInstance)).GetInterface(AIID, Intf));
// end;
//end;

procedure TGMOdbcField.SwapBufferMap;
begin
  ODBCValueBuffer(vbiValue).FBufferBound := False;
  inherited SwapBufferMap;
  ODBCValueBuffer(vbiValue).BindBuffer(True);
end;

procedure TGMOdbcField.SetupDataLengthForUpdate;
begin
  ODBCValueBuffer(vbiValue).SetupDataLengthForUpdate;
end;

procedure TGMOdbcField.RestoreDataLength;
begin
  ODBCValueBuffer(vbiValue).RestoreDataLength;
end;

//procedure TGMOdbcField.AfterActiveChange(const ANewActive: Boolean);
//begin
//inherited AfterActiveChange(ANewActive);
//if ANewActive then
// begin
////  ODBCValueBuffer(vbiValue).BindBuffer(ANewActive);
////  ODBCValueBuffer(vbiValue).InvalidateAndReadFirstData(True);
//  ODBCValueBuffer(vbiValue).Invalidate(True);
// end;
//end;

//procedure TGMOdbcField.AfterPositionChange;
//begin
//// No inherited call here
////ODBCValueBuffer(vbiValue).InvalidateAndReadFirstData(True);
//ODBCValueBuffer(vbiValue).Invalidate(True);
//end;

procedure TGMOdbcField.StoreBlobData;
begin
//if IsStreamedFieldDataType(DataType) then ODBCValueBuffer(vbiValue).StoreBlobData(CreateData.BlobCompressionType);
  if IsStreamedFieldDataType(DataType) then ODBCValueBuffer(vbiValue).StoreBlobData;
end;

//procedure TGMOdbcField.AfterOperation(const AOperation: Integer; const AParameter: IUnknown = nil);
//begin
//inherited AfterOperation(AOperation, AParameter);
//case AOperation of
//// Ord(roCancelChanges), Ord(roEdit), Ord(roInsert): inherited AfterOperation(AOperation, AParameter);
//// Ord(roRefreshCurrent): ODBCValueBuffer(vbiValue).Invalidate(True); // InvalidateAndReadFirstData(True);
//
// Ord(roApplyChanges):
//  begin
//   if IsStreamedFieldDataType(DataType) and not ODBCValueBuffer(vbiValue).DataFetched then
////    begin
//     // Applying Changes has the fetching semantics of a cursor move
//     // If we hadn't fetched all of the Data we must start all over again
////     ODBCValueBuffer(vbiValue).FFirstDataRead := False;
//     ODBCValueBuffer(vbiValue).ReadFirstData;
////    end;
//   //ValueBuffer(vbiValue).Modified := False;
//  end;
//end;
//end;

//procedure TGMOdbcField.AfterOperation(const AOperation: Integer; const AParameter: IUnknown = nil);
//begin
//case AOperation of
// Ord(roCancelChanges), Ord(roEdit), Ord(roInsert): inherited AfterOperation(AOperation, AParameter);
// Ord(roRefreshCurrent): ODBCValueBuffer(vbiValue).Invalidate(True); // InvalidateAndReadFirstData(True);
//
// Ord(roApplyChanges):
//  begin
//   inherited AfterOperation(AOperation, AParameter);
//   if IsStreamedFieldDataType(DataType) and not ODBCValueBuffer(vbiValue).DataFetched then
////    begin
//     // Applying Changes has the fetching semantics of a cursor move
//     // If we hadn't fetched all of the Data we must start all over again
////     ODBCValueBuffer(vbiValue).FFirstDataRead := False;
//     ODBCValueBuffer(vbiValue).ReadFirstData;
////    end;
//   //ValueBuffer(vbiValue).Modified := False;
//  end;
//end;
//end;

{ --------------------------------- }
{ ---- TStmtCNAwareSubProperty ---- }
{ --------------------------------- }

constructor TStmtCNAwareSubProperty.Create(const AOwner: TGMActivatableObject);
begin
  inherited Create(AOwner);
  FUseValueFromConnection := cDfltUseConnectionValue;
end;

function TStmtCNAwareSubProperty.IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean;
begin
  if AValue = nil then Result := True else
   Result := (UseValueFromConnection = AValue.UseValueFromConnection);
end;

procedure TStmtCNAwareSubProperty.AssignFromObj(const ASource: TObject);
begin
  if ASource is TStmtCNAwareSubProperty then
   UseValueFromConnection := TStmtCNAwareSubProperty(ASource).UseValueFromConnection;
  //else inherited AssignFromObj(ASource);
end;

procedure TStmtCNAwareSubProperty.SetUseValueFromConnection(const AValue: Boolean);
begin
  if AValue <> UseValueFromConnection then
   begin
    FUseValueFromConnection := AValue; // <-- Before SetODBCValue !!!
    ValueFromConnectionChanged;
   end;
end;

procedure TStmtCNAwareSubProperty.LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
end;

procedure TStmtCNAwareSubProperty.StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
end;


{ -------------------------------- }
{ ---- TStmtTimeoutProperties ---- }
{ -------------------------------- }

constructor TStmtTimeoutProperties.Create(const AOwner: TGMActivatableObject = nil);
begin
  inherited Create(AOwner);
  FTimeoutForStatements := cDfltStatementTimeout;
end;

function TStmtTimeoutProperties.IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean;
begin
  Result := inherited IsEqualTo(AValue);
  if AValue is TStmtTimeoutProperties then Result := Result and (TimeoutForStatements = TStmtTimeoutProperties(AValue).TimeoutForStatements);
end;

procedure TStmtTimeoutProperties.AssignFromObj(const Source: TObject);
begin
  if Source is TStmtTimeoutProperties then TimeoutForStatements := TStmtTimeoutProperties(Source).TimeoutForStatements;
  //inherited AssignFromObj(Source);
end;

procedure TStmtTimeoutProperties.SetODBCTimeoutForStatements(const AValue: PtrUInt);
begin
  if (ODBCStatement <> nil) and ODBCStatement.Active then
   ODBCCheck(SQLSetStmtAttr(ODBCStatement.Handle, SQL_ATTR_QUERY_TIMEOUT, SQLPOINTER(AValue), 0), ODBCStatement, {$I %CurrentRoutine%});
end;

function TStmtTimeoutProperties.GetTimeoutForStatements: SQLUINTEGER;
begin
  if UseValueFromConnection and (ODBCStatement <> nil) and (ODBCStatement.ODBCConnection <> nil) then
   Result := ODBCStatement.ODBCConnection.Properties.Obj.TimeoutForStatements
  else
   Result := FTimeoutForStatements;
end;

procedure TStmtTimeoutProperties.SetTimeoutForStatements(const AValue: SQLUINTEGER);
begin
  if AValue <> TimeoutForStatements then
   begin
    if ODBCStatement <> nil then
     begin
      ODBCStatement.CheckIsInactive('TimeoutForStatements ' + RStrProperty);
      if UseValueFromConnection and (ODBCStatement.ODBCConnection <> nil) then ODBCStatement.ODBCConnection.Properties.Obj.TimeoutForStatements := AValue;
     end;
    SetODBCTimeoutForStatements(AValue);
    FTimeoutForStatements := AValue;
   end;
end;

procedure TStmtTimeoutProperties.SetODBCValue;
begin
  if (TimeoutForStatements <> SQL_DEFAULT_TIMEOUT) and not UseValueFromConnection then SetODBCTimeoutForStatements(TimeoutForStatements);
end;

procedure TStmtTimeoutProperties.ValueFromConnectionChanged;
begin
  SetTimeoutForStatements(TimeoutForStatements);
end;

procedure TStmtTimeoutProperties.LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  inherited LoadData(ASource, ACryptCtrlData);

  if ASource <> nil then
   begin
    UseValueFromConnection := ASource.ReadBoolean(cPrsUseTimeoutValueFromConnection, cDfltUseConnectionValue);
    TimeoutForStatements := ASource.ReadInteger(cPrsStatementTimeout, cDfltStatementTimeout);
   end;
end;

procedure TStmtTimeoutProperties.StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  inherited StoreData(ADest, ACryptCtrlData);

  if ADest <> nil then
   begin
    GMStoreBoolean(ADest, cPrsUseTimeoutValueFromConnection, UseValueFromConnection, cDfltUseConnectionValue);
    GMStoreInteger(ADest, cPrsStatementTimeout, TimeoutForStatements, cDfltStatementTimeout);
   end;
end;


{ --------------------------------------- }
{ ---- TStmtAsyncOperationProperties ---- }
{ --------------------------------------- }

constructor TStmtAsyncOperationProperties.Create(const AOwner: TGMActivatableObject = nil);
begin
  inherited Create(AOwner);
  FAsynchronOperations := cDfltAsyncOperations;
end;

function TStmtAsyncOperationProperties.IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean;
begin
  Result := inherited IsEqualTo(AValue);
  if AValue is TStmtAsyncOperationProperties then Result := Result and (AsynchronOperations = TStmtAsyncOperationProperties(AValue).AsynchronOperations);
end;

procedure TStmtAsyncOperationProperties.AssignFromObj(const Source: TObject);
begin
  if Source is TStmtAsyncOperationProperties then AsynchronOperations := TStmtAsyncOperationProperties(Source).AsynchronOperations;
  //inherited AssignFromObj(Source);
end;

function TStmtAsyncOperationProperties.GetAsynchronOperations: Boolean;
begin
  if UseValueFromConnection and (ODBCStatement <> nil) and (ODBCStatement.ODBCConnection <> nil) then
   Result := caAsynchronOperations in ODBCStatement.ODBCConnection.Properties.Obj.Attributes
  else
   Result := FAsynchronOperations;
end;

procedure TStmtAsyncOperationProperties.SetAsynchronOperations(const AValue: Boolean);
begin
  if AValue <> AsynchronOperations then
   begin
    if UseValueFromConnection and (ODBCStatement <> nil) and (ODBCStatement.ODBCConnection <> nil) then
     with ODBCStatement.ODBCConnection.Properties.Obj do
      if AValue then
       Attributes := Attributes + [caAsynchronOperations]
      else
       Attributes := Attributes - [caAsynchronOperations];

    SetODBCAsynchronOperations(AValue);
    FAsynchronOperations := AValue;
   end;
end;

procedure TStmtAsyncOperationProperties.SetODBCAsynchronOperations(const AValue: Boolean);
var AsyncOp: PtrUInt;
begin
  if (ODBCStatement <> nil) and ODBCStatement.Active then
   begin
    if AValue then AsyncOp := SQL_ASYNC_ENABLE_ON else AsyncOp := SQL_ASYNC_ENABLE_OFF;
    ODBCCheck(SQLSetStmtAttr(ODBCStatement.Handle, SQL_ATTR_ASYNC_ENABLE, SQLPOINTER(AsyncOp), 0), ODBCStatement, {$I %CurrentRoutine%});
   end;
end;

procedure TStmtAsyncOperationProperties.SetODBCValue;
begin
  if (AsynchronOperations <> Boolean(SQL_ASYNC_ENABLE_DEFAULT)) and not UseValueFromConnection then SetODBCAsynchronOperations(AsynchronOperations);
end;

procedure TStmtAsyncOperationProperties.ValueFromConnectionChanged;
begin
  SetAsynchronOperations(AsynchronOperations);
end;

procedure TStmtAsyncOperationProperties.LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  inherited LoadData(Source, ACryptCtrlData);

  if Source <> nil then
   begin
    UseValueFromConnection := Source.ReadBoolean(cPrsUseAsyncValueFromConnection, cDfltUseConnectionValue);
    AsynchronOperations := Source.ReadBoolean(cPrsAsynchronOperations, cDfltAsyncOperations);
   end;
end;

procedure TStmtAsyncOperationProperties.StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  inherited StoreData(ADest, ACryptCtrlData);

  if ADest <> nil then
   begin
    GMStoreBoolean(ADest, cPrsUseAsyncValueFromConnection, UseValueFromConnection, cDfltUseConnectionValue);
    GMStoreBoolean(ADest, cPrsAsynchronOperations, AsynchronOperations, cDfltAsyncOperations);
   end;
end;


{ ---------------------------------------- }
{ ---- TStmtCatalogNameCaseProperties ---- }
{ ---------------------------------------- }

constructor TStmtCatalogNameCaseProperties.Create(const AOwner: TGMActivatableObject = nil);
begin
  inherited Create(AOwner);
  FCaseSensitiveCatalogNames := cDfltCaseSensitiveCatalogNames;
end;

function TStmtCatalogNameCaseProperties.IsEqualTo(const AValue: TStmtCNAwareSubProperty): Boolean;
begin
  Result := inherited IsEqualTo(AValue);
  if AValue is TStmtCatalogNameCaseProperties then Result := Result and (CaseSensitiveCatalogNames = TStmtCatalogNameCaseProperties(AValue).CaseSensitiveCatalogNames);
end;

procedure TStmtCatalogNameCaseProperties.AssignFromObj(const Source: TObject);
begin
  if Source is TStmtCatalogNameCaseProperties then CaseSensitiveCatalogNames := TStmtCatalogNameCaseProperties(Source).CaseSensitiveCatalogNames;
  //inherited AssignFromObj(Source);
end;

function TStmtCatalogNameCaseProperties.GetCaseSensitiveCatalogNames: Boolean;
begin
  if UseValueFromConnection and (ODBCStatement <> nil) and (ODBCStatement.ODBCConnection <> nil) then
   Result := caCaseSensitiveCatalogNames in ODBCStatement.ODBCConnection.Properties.Obj.Attributes
  else
   Result := FCaseSensitiveCatalogNames;
end;

procedure TStmtCatalogNameCaseProperties.SetCaseSensitiveCatalogNames(const AValue: Boolean);
begin
  if AValue <> CaseSensitiveCatalogNames then
   begin
    if UseValueFromConnection and (ODBCStatement <> nil) and (ODBCStatement.ODBCConnection <> nil) then
     with ODBCStatement.ODBCConnection.Properties.Obj do
      if AValue then
       Attributes := Attributes + [caCaseSensitiveCatalogNames]
      else
       Attributes := Attributes - [caCaseSensitiveCatalogNames];

    SetODBCCaseSensitiveCatalogNames(AValue);
    FCaseSensitiveCatalogNames := AValue;
   end;
end;

procedure TStmtCatalogNameCaseProperties.SetODBCCaseSensitiveCatalogNames(const AValue: Boolean);
var catalogCase: PtrUInt;
begin
  if (ODBCStatement <> nil) and ODBCStatement.Active then
   begin
    if AValue then catalogCase := SQL_FALSE else catalogCase := SQL_TRUE;
    ODBCCheck(SQLSetStmtAttr(ODBCStatement.Handle, SQL_ATTR_METADATA_ID, SQLPOINTER(catalogCase), 0), ODBCStatement, {$I %CurrentRoutine%});
   end;
end;

procedure TStmtCatalogNameCaseProperties.SetODBCValue;
begin
  if (CaseSensitiveCatalogNames <> Boolean(SQL_METADATA_ID_DEFAULT)) and not UseValueFromConnection then SetODBCCaseSensitiveCatalogNames(CaseSensitiveCatalogNames);
end;

procedure TStmtCatalogNameCaseProperties.ValueFromConnectionChanged;
begin
  SetCaseSensitiveCatalogNames(CaseSensitiveCatalogNames);
end;

procedure TStmtCatalogNameCaseProperties.LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  inherited LoadData(Source, ACryptCtrlData);

  if Source <> nil then
   begin
    UseValueFromConnection := Source.ReadBoolean(cPrsUseCaseValueFromConnection, cDfltUseConnectionValue);
    CaseSensitiveCatalogNames := Source.ReadBoolean(cPrsCaseSensitiveCatalogNames, cDfltCaseSensitiveCatalogNames);
   end;
end;

procedure TStmtCatalogNameCaseProperties.StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  inherited StoreData(ADest, ACryptCtrlData);
  if ADest <> nil then
   begin
    GMStoreBoolean(ADest, cPrsUseCaseValueFromConnection, UseValueFromConnection, cDfltUseConnectionValue);
    GMStoreBoolean(ADest, cPrsCaseSensitiveCatalogNames, CaseSensitiveCatalogNames, cDfltCaseSensitiveCatalogNames);
   end;
end;


{ ------------------------------------ }
{ ---- TGMOdbcStatementProperties ---- }
{ ------------------------------------ }

constructor TGMOdbcStatementProperties.Create(const AAssignSQL: Boolean; const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);

  FAssignSQL := AAssignSQL;

  FTimeoutProperties := TStmtTimeoutProperties.Create;
  FAsyncOperationProperties := TStmtAsyncOperationProperties.Create;
  FCatalogNameCaseProperties := TStmtCatalogNameCaseProperties.Create;

  SetDefaultValues;
end;

constructor TGMOdbcStatementProperties.Create(const ASource: TObject; const AAssignSQL: Boolean; const ARefLifeTime: Boolean);
begin
  Create(AAssignSQL, ARefLifeTime);
  if ASource <> nil then AssignFromObj(ASource);
end;

constructor TGMOdbcStatementProperties.Create(const ASource: IGMValueStorage;
    const ACryptCtrlData: PGMCryptCtrlData; const AAssignSQL: Boolean; const ARefLifeTime: Boolean);
begin
  Create(AAssignSQL, ARefLifeTime);
  if ASource <> nil then LoadData(ASource, ACryptCtrlData);
end;

destructor TGMOdbcStatementProperties.Destroy;
begin
  GMFreeAndNil(FTimeoutProperties);
  GMFreeAndNil(FAsyncOperationProperties);
  GMFreeAndNil(FCatalogNameCaseProperties);
  inherited Destroy;
end;

procedure TGMOdbcStatementProperties.SetDefaultValues;
begin
  FTimedReExecutionDelay := cDfltReExecutionDelay;
  FReExecuteAfterSQLChange := cDfltReExecuteAfterSQLChange;
  FUseSQLEscapeSequences := cDfltUseSQLEscapeSequences;
end;

procedure TGMOdbcStatementProperties.SetTimeoutProperties(const AValue: TStmtTimeoutProperties);
begin
  TimeoutProperties.AssignFromObj(AValue);
end;

procedure TGMOdbcStatementProperties.SetAsyncOperationProperties(const AValue: TStmtAsyncOperationProperties);
begin
  AsyncOperationProperties.AssignFromObj(AValue);
end;

procedure TGMOdbcStatementProperties.SetCatalogNameCaseProperties(const AValue: TStmtCatalogNameCaseProperties);
begin
  CatalogNameCaseProperties.AssignFromObj(AValue);
end;

procedure TGMOdbcStatementProperties.AssignFromIntf(const ASource: IUnknown);
begin
  AssignFromObj(GMObjFromIntf(ASource));
end;

procedure TGMOdbcStatementProperties.AssignToIntf(const ADest: IUnknown);
begin
  AssignToObj(GMObjFromIntf(ADest));
end;

procedure TGMOdbcStatementProperties.AssignFromObj(const ASource: TObject);
begin
  if ASource is TGMOdbcStatementProperties then
   begin
    if AssignSQL then SQL := TGMOdbcStatementProperties(ASource).SQL;
    ReExecuteAfterSQLChange := TGMOdbcStatementProperties(ASource).ReExecuteAfterSQLChange;
    UseSQLEscapeSequences := TGMOdbcStatementProperties(ASource).UseSQLEscapeSequences;
    TimedReExecutionDelay := TGMOdbcStatementProperties(ASource).TimedReExecutionDelay;
    TimeoutProperties := TGMOdbcStatementProperties(ASource).TimeoutProperties;
    AsyncOperationProperties := TGMOdbcStatementProperties(ASource).AsyncOperationProperties;
    CatalogNameCaseProperties := TGMOdbcStatementProperties(ASource).CatalogNameCaseProperties;
   end else
  if ASource is TGMOdbcStatementBase then
   begin
    if AssignSQL then SQL := TGMOdbcStatementBase(ASource).SQL.SQLText;
    ReExecuteAfterSQLChange := TGMOdbcStatementBase(ASource).SQL.ReExecuteAfterSQLChange;
    UseSQLEscapeSequences := TGMOdbcStatementBase(ASource).UseSQLEscapeSequences;
    TimedReExecutionDelay := TGMOdbcStatementBase(ASource).TimedReExecutionDelay;
    TimeoutProperties := TGMOdbcStatementBase(ASource).TimeoutProperties;
    AsyncOperationProperties := TGMOdbcStatementBase(ASource).AsynchronOperationProperties;
    CatalogNameCaseProperties := TGMOdbcStatementBase(ASource).CatalogNameCaseProperties;
   end;
end;

procedure TGMOdbcStatementProperties.AssignToObj(const ADest: TObject);
begin
  if ADest is TGMOdbcStatementBase then
   begin
    if AssignSQL then TGMOdbcStatementBase(ADest).SQL.SQLText := SQL;
    TGMOdbcStatementBase(ADest).SQL.ReExecuteAfterSQLChange := ReExecuteAfterSQLChange;
    TGMOdbcStatementBase(ADest).UseSQLEscapeSequences := UseSQLEscapeSequences;
    TGMOdbcStatementBase(ADest).TimedReExecutionDelay := TimedReExecutionDelay;
    TGMOdbcStatementBase(ADest).TimeoutProperties := TimeoutProperties;
    TGMOdbcStatementBase(ADest).AsynchronOperationProperties := AsyncOperationProperties;
    TGMOdbcStatementBase(ADest).CatalogNameCaseProperties := CatalogNameCaseProperties;
   end
end;

procedure TGMOdbcStatementProperties.LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  if ASource <> nil then
   begin
    if AssignSQL then SQL := GMReplaceChars(ASource.ReadString(cStrSQL), cSqlReplacements, cSqlReplaceChars);
    ReExecuteAfterSQLChange := ASource.ReadBoolean(cPrsReExecuteAfterSQLChange, cDfltReExecuteAfterSQLChange);
    UseSQLEscapeSequences := ASource.ReadBoolean(cPrsUseSQLEscapeSequences, cDfltUseSQLEscapeSequences);
    TimedReExecutionDelay := ASource.ReadInteger(cPrsTimedReExecutionDelay, cDfltReExecutionDelay);
    TimeoutProperties.LoadData(ASource, ACryptCtrlData);
    AsyncOperationProperties.LoadData(ASource, ACryptCtrlData);
    CatalogNameCaseProperties.LoadData(ASource, ACryptCtrlData);
   end;
end;

procedure TGMOdbcStatementProperties.StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
begin
  if ADest <> nil then
   begin
//  if AssignSQL and (SQL <> cDfltReadString) then ADest.WriteString(cStrSQL, GMReplaceChars(SQL, cSqlReplaceChars, cSqlReplacements));
    if AssignSQL then GMStoreString(ADest, cStrSQL, SQL);
    GMStoreBoolean(ADest, cPrsReExecuteAfterSQLChange, ReExecuteAfterSQLChange, cDfltReExecuteAfterSQLChange);
    GMStoreBoolean(ADest, cPrsUseSQLEscapeSequences, UseSQLEscapeSequences, cDfltUseSQLEscapeSequences);
    GMStoreInteger(ADest, cPrsTimedReExecutionDelay, TimedReExecutionDelay, cDfltReExecutionDelay);
    TimeoutProperties.StoreData(ADest, ACryptCtrlData);
    AsyncOperationProperties.StoreData(ADest, ACryptCtrlData);
    CatalogNameCaseProperties.StoreData(ADest, ACryptCtrlData);
   end;
end;


{ ------------------------------ }
{ ---- TGMOdbcStatementBase ---- }
{ ------------------------------ }

constructor TGMOdbcStatementBase.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  FTimeoutProperties := TStmtTimeoutProperties.Create(Self);
  FAsyncOperationProperties := TStmtAsyncOperationProperties.Create(Self);
  FCatalogNameCaseProperties := TStmtCatalogNameCaseProperties.Create(Self);
end;

destructor TGMOdbcStatementBase.Destroy;
begin
  inherited Destroy;
  GMFreeAndNil(FCatalogNameCaseProperties);
  GMFreeAndNil(FAsyncOperationProperties);
  GMFreeAndNil(FTimeoutProperties);
end;

procedure TGMOdbcStatementBase.AssignFromObj(const Source: TObject);
begin
//inherited AssignFromObj(Source);
  if Source is TGMOdbcStatementBase then
   begin
    ConnectionIntf := TGMSqlStatementBase(Source).ConnectionIntf;
//  if TGMOdbcStatementBase(Source).ODBCConnection <> nil then ODBCConnection := TGMOdbcStatementBase(Source).ODBCConnection;
    UseSQLEscapeSequences := TGMOdbcStatementBase(Source).UseSQLEscapeSequences;
    TimeoutProperties := TGMOdbcStatementBase(Source).TimeoutProperties;
    AsynchronOperationProperties := TGMOdbcStatementBase(Source).AsynchronOperationProperties;
    CatalogNameCaseProperties := TGMOdbcStatementBase(Source).CatalogNameCaseProperties;
   end; 
end;

function TGMOdbcStatementBase.GetHandleType: LongWord;
begin
  Result := SQL_HANDLE_STMT;
end;

procedure TGMOdbcStatementBase.AllocHandle;
var mousePtrWait: IUNknown; PIConnHandle: IGMGetHandle; PIConnHandleType: IGMGetHandleType;
begin
  mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);
  if not GetHandleAllocated then
   begin
    if ConnectionIntf = nil then raise EGMException.ObjError(GMFormat(RStrMissingPropVal, ['ODBCConnection']), Self, scAllocHandle);

    GMSetIntfActive(ConnectionIntf, True, {$I %CurrentRoutine%});
    GMCheckQueryInterface(ConnectionIntf, IGMGetHandle, PIConnHandle, {$I %CurrentRoutine%});
    GMCheckQueryInterface(ConnectionIntf, IGMGetHandleType, PIConnHandleType, {$I %CurrentRoutine%});

    ODBCCheck(SQLAllocHandle(HandleType, PIConnHandle.Handle, FHandle), Self, 'SQLAllocHandle', PIConnHandleType.HandleType, PIConnHandle.Handle);

    SetODBCAttributes;
   end;

  inherited AllocHandle;
end;

procedure TGMOdbcStatementBase.ReleaseHandle;
begin
  ReExecutionTimer.Stop; // <- do this first!

  if GetHandleAllocated then
   begin
    SQLFreeHandle(HandleType, Handle);
    FHandle := 0;
   end;

  inherited ReleaseHandle;
end;

procedure TGMOdbcStatementBase.APIExecuteSQL(const ASQLText: TGMString);
var sqlResult: SQLRETURN;
begin
  sqlResult := SQLExecDirect(Handle, SQLPCHAR(ASQLText), Length(ASQLText));
  if sqlResult <> SQL_NO_DATA then
   try
    ODBCCheck(sqlResult, Self, {$I %CurrentRoutine%});
   except
    on ex: TObject do
     begin
      GMSetObjText(ex, GMGetObjText(ex) + c2NewLine + 'SQL:' + cNewLine + '=====' + cNewLine + ASQLText);
      raise;
     end;
   end;
end;

procedure TGMOdbcStatementBase.SetODBCAttributes;
begin
  if GetHandleAllocated then
   begin
    TimeoutProperties.SetODBCValue;
    AsynchronOperationProperties.SetODBCValue;
    CatalogNameCaseProperties.SetODBCValue;
   end;
end;

procedure TGMOdbcStatementBase.CancelExecution;
begin
  if StillExecuting then ODBCCheck(SQLCancel(Handle), Self, {$I %CurrentRoutine%});
end;

function TGMOdbcStatementBase.StillExecuting: Boolean;
begin
  Result := SQLExecDirect(Handle, nil, 0) = SQL_STILL_EXECUTING;
end;

function TGMOdbcStatementBase.AffectedRecordCount: SQLLEN;
{$IFDEF FPC}{$push}{$WARN 5060 off : Function result variable does not seem to be initialized}{$ENDIF}
begin
  if not ODBCSucceeded(SQLRowCount(Handle, Result)) then Result := cGMUnknownCount;
end;
{$IFDEF FPC}{$pop}{$ENDIF}


{ ---- properties ---- }

procedure TGMOdbcStatementBase.SetUseSQLEscapeSequences(const AValue: Boolean);
begin
  if AValue <> UseSQLEscapeSequences then
   begin
    CheckIsInactive('UseSQLEscapeSequences ' + RStrProperty);
    FUseSQLEscapeSequences := AValue;
   end;
end;

procedure TGMOdbcStatementBase.SetTimeoutProperties(const AValue: TStmtTimeoutProperties);
begin
  if not TimeoutProperties.IsEqualTo(AValue) then TimeoutProperties.AssignFromObj(AValue);
end;

procedure TGMOdbcStatementBase.SetAsyncProperties(const AValue: TStmtAsyncOperationProperties);
begin
  if not AsynchronOperationProperties.IsEqualTo(AValue) then AsynchronOperationProperties.AssignFromObj(AValue);
end;

procedure TGMOdbcStatementBase.SetCatalogNameCaseProperties(const AValue: TStmtCatalogNameCaseProperties);
begin
  if not CatalogNameCaseProperties.IsEqualTo(AValue) then CatalogNameCaseProperties.AssignFromObj(AValue);
end;

function TGMOdbcStatementBase.GetOdbcConnection: TGMOdbcConnection;
begin
//Result := ObjectConnectedTo.InterfaceSourceObject as TGMOdbcConnection;
  Result := GMObjFromIntf(ObjectConnectedTo.InterfaceSource) as TGMOdbcConnection;
end;

procedure TGMOdbcStatementBase.SetODBCConnection(const AValue: TGMOdbcConnection);
begin
//ObjectConnectedTo.InterfaceSourceObject := AValue; // <- will be checked vai OnBeforeIntfSourceChange
  ObjectConnectedTo.InterfaceSource := GMObjAsIntf(AValue);

  {if AValue <> ODBCConnection then
   begin
    CheckIsInactive('ODBCConnection ' + RStrProperty);
    ObjectConnectedTo.InterfaceSourceObject := AValue;
   end;}
end;


{ ------------------------- }
{ ---- TOdbcFieldState ---- }
{ ------------------------- }

function TOdbcFieldState.ValueBufferCreateClass: TGMValueBufferClass;
begin
  Result := TODBCFieldStateValueBuffer;
end;


{ ------------------------------- }
{ ---- TGMOdbcRecordsetState ---- }
{ ------------------------------- }

function TGMOdbcRecordsetState.FieldStateCreateClass: TGMFieldStateCreateClass;
begin
  Result := TOdbcFieldState;
end;


{ ------------------------------------ }
{ ---- TGMOdbcRecordsetProperties ---- }
{ ------------------------------------ }

procedure TGMOdbcRecordsetProperties.SetDefaultValues;
begin
  inherited SetDefaultValues;

  FAttributes := cDfltRSAttributes;
  FUpdateStrategy := cDfltUpdateStrategy;
  FPositionedUpdateSimulation := cDfltPositionedUpdateSimulation;
  FCursorSensitivity := cDfltCursorSensitivity;
  FCursorType := cDfltCursorType;
  FKeysetSize := cDfltKeysetSize;
  FMaxRecordsReturned := cDfltMaxRecords;
  FMaxFieldDataSize := cDfltMaxFieldDataSize;
//FBlobCompressionType := cDfltBlobCompressionType;
  FRecordCountStrategies := cDfltRecordCountStrategies;
//FMaxStringLengthOrigin := cDfltMaxStringLengthOrigin;
//FMaxStringLengthCharSizeFactor := cDfltMaxStringLengthCharSizeFactor;
end;

function TGMOdbcRecordsetProperties.Obj: TGMOdbcRecordsetProperties;
begin
  Result := Self;
end;

procedure TGMOdbcRecordsetProperties.AssignFromObj(const ASource: TObject);
begin
  inherited AssignFromObj(ASource);

  if ASource is TGMOdbcRecordsetProperties then
   begin
    Attributes := TGMOdbcRecordsetProperties(ASource).Attributes;
    UpdateStrategy := TGMOdbcRecordsetProperties(ASource).UpdateStrategy;
    PositionedUpdateSimulation := TGMOdbcRecordsetProperties(ASource).PositionedUpdateSimulation;
    CursorSensitivity := TGMOdbcRecordsetProperties(ASource).CursorSensitivity;
    CursorType := TGMOdbcRecordsetProperties(ASource).CursorType;
    KeysetSize := TGMOdbcRecordsetProperties(ASource).KeysetSize;
    MaxRecordsReturned := TGMOdbcRecordsetProperties(ASource).MaxRecordsReturned;
    MaxFieldDataSize := TGMOdbcRecordsetProperties(ASource).MaxFieldDataSize;
//  BlobCompressionType := TGMOdbcRecordsetProperties(ASource).BlobCompressionType;
    RecordCountStrategies := TGMOdbcRecordsetProperties(ASource).RecordCountStrategies;
//  MaxStringLengthOrigin := TGMOdbcRecordsetProperties(ASource).MaxStringLengthOrigin;
//  MaxStringLengthCharSizeFactor := TGMOdbcRecordsetProperties(ASource).MaxStringLengthCharSizeFactor;
   end else
  if ASource is TGMOdbcRecordsetBase then
   begin
    Attributes := TGMOdbcRecordsetBase(ASource).Attributes;
    UpdateStrategy := TGMOdbcRecordsetBase(ASource).UpdateStrategy;
    PositionedUpdateSimulation := TGMOdbcRecordsetBase(ASource).PositionedUpdateSimulation;
    CursorSensitivity := TGMOdbcRecordsetBase(ASource).CursorSensitivity;
    CursorType := TGMOdbcRecordsetBase(ASource).CursorType;
    KeysetSize := TGMOdbcRecordsetBase(ASource).KeysetSize;
    MaxRecordsReturned := TGMOdbcRecordsetBase(ASource).MaxRecordsReturned;
    MaxFieldDataSize := TGMOdbcRecordsetBase(ASource).MaxFieldDataSize;
//  BlobCompressionType := TGMOdbcRecordsetBase(ASource).BlobCompressionType;
    RecordCountStrategies := TGMOdbcRecordsetBase(ASource).RecordCountStrategies;
//  MaxStringLengthOrigin := TGMOdbcRecordsetBase(ASource).MaxStringLengthOrigin;
//  MaxStringLengthCharSizeFactor := TGMOdbcRecordsetBase(ASource).MaxStringLengthCharSizeFactor;
   end;
end;

procedure TGMOdbcRecordsetProperties.AssignToObj(const ADest: TObject);
begin
  inherited AssignToObj(ADest);

  if ADest is TGMOdbcRecordsetBase then
   begin
    TGMOdbcRecordsetBase(ADest).Attributes := Attributes;
    TGMOdbcRecordsetBase(ADest).UpdateStrategy := UpdateStrategy;
    TGMOdbcRecordsetBase(ADest).PositionedUpdateSimulation := PositionedUpdateSimulation;
    TGMOdbcRecordsetBase(ADest).CursorSensitivity := CursorSensitivity;
    TGMOdbcRecordsetBase(ADest).CursorType := CursorType;
    TGMOdbcRecordsetBase(ADest).KeysetSize := KeysetSize;
    TGMOdbcRecordsetBase(ADest).MaxRecordsReturned := MaxRecordsReturned;
    TGMOdbcRecordsetBase(ADest).MaxFieldDataSize := MaxFieldDataSize;
//  TGMOdbcRecordsetBase(ADest).BlobCompressionType := BlobCompressionType;
    TGMOdbcRecordsetBase(ADest).RecordCountStrategies := RecordCountStrategies;
//  TGMOdbcRecordsetBase(ADest).MaxStringLengthOrigin := MaxStringLengthOrigin;
//  TGMOdbcRecordsetBase(ADest).MaxStringLengthCharSizeFactor := MaxStringLengthCharSizeFactor;
   end;
end;

procedure TGMOdbcRecordsetProperties.LoadData(const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
//var PIBinaryStorage: IGMBinaryStorage;
begin
  inherited LoadData(ASource, ACryptCtrlData);

  if ASource <> nil then
   begin
    UpdateStrategy := TUpdateStrategy(GMBoundedInt(ASource.ReadInteger(cPrsUpdateStrategy, Ord(cDfltUpdateStrategy)), Ord(Low(TUpdateStrategy)), Ord(High(TUpdateStrategy))));
    PositionedUpdateSimulation := TPositionedUpdateSimulation(GMBoundedInt(ASource.ReadInteger(cPrsPositionedUpdateSimulation, Ord(cDfltPositionedUpdateSimulation)), Ord(Low(TPositionedUpdateSimulation)), Ord(High(TPositionedUpdateSimulation))));
    CursorSensitivity := TCursorSensitivity(GMBoundedInt(ASource.ReadInteger(cPrsCursorSensitivity, Ord(cDfltCursorSensitivity)), Ord(Low(TCursorSensitivity)), Ord(High(TCursorSensitivity))));
    CursorType := TGMCursorType(GMBoundedInt(ASource.ReadInteger(cPrsCursorType, Ord(cDfltCursorType)), Ord(Low(TGMCursorType)), Ord(High(TGMCursorType))));
    KeysetSize := ASource.ReadInteger(cPrsKeysetSize, cDfltKeysetSize);
    MaxRecordsReturned := ASource.ReadInteger(cPrsMaxRecordsReturned, cDfltMaxRecords);
    MaxFieldDataSize := ASource.ReadInteger(cPrsMaxFieldDataSize, cDfltMaxFieldDataSize);
//  BlobCompressionType := TGMCompressionType(GMBoundedInt(ASource.ReadInteger(cPrsBlobCompressionType, Ord(cDfltBlobCompressionType)), Ord(Low(TGMCompressionType)), Ord(High(TGMCompressionType))));
//  BlobCompressionType := ASource.ReadInteger(cPrsBlobCompressionType, cDfltBlobCompressionType);

    //FAttributes := cDfltRSAttributes;
    Attributes := RecordsetAttributesFromInt(ASource.ReadInteger(cPrsAttributes, RecordsetAttributesToInt(cDfltRSAttributes)));
    //PIBinaryStorage.ReadBinary(cStrAttributes, FAttributes, SizeOf(FAttributes), False);

    //FRecordCountStrategies := cDfltRecordCountStrategies;
    RecordCountStrategies := RecordCountStrategiesFromInt(ASource.ReadInteger(cPrsCountStrategies, RecordCountStrategiesToInt(cDfltRecordCountStrategies)));
    //PIBinaryStorage.ReadBinary(cStrCountStrategies, FRecordCountStrategies, SizeOf(FRecordCountStrategies), False);

//  MaxStringLengthOrigin := TGMMaxStringLengthOrigin(GMBoundedInt(ASource.ReadInteger(cPrsMaxStringLengthOrigin, Ord(cDfltMaxStringLengthOrigin)), Ord(Low(MaxStringLengthOrigin)), Ord(High(MaxStringLengthOrigin))));
//  MaxStringLengthCharSizeFactor := ASource.ReadDouble(cPrsMaxStringLengthCharSizeFactor, cDfltMaxStringLengthCharSizeFactor);
   end;
end;

procedure TGMOdbcRecordsetProperties.StoreData(const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData);
//var PIBinaryStorage: IGMBinaryStorage;
begin
  inherited StoreData(ADest, ACryptCtrlData);

  if ADest <> nil then
   begin
    GMStoreInteger(ADest, cPrsUpdateStrategy, Ord(UpdateStrategy), Ord(cDfltUpdateStrategy));
    GMStoreInteger(ADest, cPrsPositionedUpdateSimulation, Ord(PositionedUpdateSimulation), Ord(cDfltPositionedUpdateSimulation));
    GMStoreInteger(ADest, cPrsCursorSensitivity, Ord(CursorSensitivity), Ord(cDfltCursorSensitivity));
    GMStoreInteger(ADest, cPrsCursorType, Ord(CursorType), Ord(cDfltCursorType));
    GMStoreInteger(ADest, cPrsKeysetSize, KeysetSize, cDfltKeysetSize);
    GMStoreInteger(ADest, cPrsMaxRecordsReturned, MaxRecordsReturned, cDfltMaxRecords);
    GMStoreInteger(ADest, cPrsMaxFieldDataSize, MaxFieldDataSize, cDfltMaxFieldDataSize);
//  GMStoreInteger(ADest, cPrsBlobCompressionType, Ord(BlobCompressionType), Ord(cDfltBlobCompressionType));

    //GMCheckQueryInterface(ADest, IGMBinaryStorage, PIBinaryStorage, {$I %CurrentRoutine%});

    GMStoreInteger(ADest, cPrsAttributes, RecordsetAttributesToInt(Attributes), RecordsetAttributesToInt(cDfltRSAttributes));
    //if Attributes <> cDfltRSAttributes then PIBinaryStorage.WriteBinary(cStrAttributes, FAttributes, SizeOf(FAttributes));

    GMStoreInteger(ADest, cPrsCountStrategies, RecordCountStrategiesToInt(RecordCountStrategies), RecordCountStrategiesToInt(cDfltRecordCountStrategies));
    //if RecordCountStrategies <> cDfltRecordCountStrategies then PIBinaryStorage.WriteBinary(cStrCountStrategies, FRecordCountStrategies, SizeOf(FRecordCountStrategies));
//  GMStoreInteger(ADest, cPrsMaxStringLengthOrigin, Ord(MaxStringLengthOrigin), Ord(cDfltMaxStringLengthOrigin));
//  GMStoreDouble(ADest, cPrsMaxStringLengthCharSizeFactor, MaxStringLengthCharSizeFactor, cDfltMaxStringLengthCharSizeFactor);
   end;
end;


{ ------------------------------ }
{ ---- TGMOdbcRecordsetBase ---- }
{ ------------------------------ }

constructor TGMOdbcRecordsetBase.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  FFieldPosList := TGMObjArrayCollection.Create(True, False, True, GMCompareByPosition);
  FFieldNameList := TGMObjArrayCollection.Create(False, True, True, GMCompareByName);
  FCascadedContentsProperties := TGMCascadedContentsProperties.Create(Self);

  FUpdateStrategy := cDfltUpdateStrategy;
  FPositionedUpdateSimulation := cDfltPositionedUpdateSimulation;
  FCursorSensitivity := cDfltCursorSensitivity;
  FCursorType := cDfltCursorType;
  FKeysetSize := cDfltKeysetSize;
  FMaxRecordsReturned := cDfltMaxRecords;
  FMaxFieldDataSize := cDfltMaxFieldDataSize;
  FUseSQLEscapeSequences := cDfltUseSQLEscapeSequences;
  FAttributes := cDfltRSAttributes;
//FBlobCompressionType := cDfltBlobCompressionType;
  FRecordCountStrategies := cDfltRecordCountStrategies;
//FMaxStringLengthOrigin := cDfltMaxStringLengthOrigin;
//FMaxStringLengthCharSizeFactor := cDfltMaxStringLengthCharSizeFactor;

  ResetMembers;

  CreateConnectionPoint(IGMOperationNotifications);
  CreateConnectionPoint(IGMPositionChangeNotifications);
  CreateConnectionPoint(IGMNamedValueChange);
  CreateConnectionPoint(IGMValidateValues);
end;

constructor TGMOdbcRecordsetBase.Create(const AConnection: IUnknown; const ASql: TGMString; const ACursorType: TGMCursorType; const ARefLifeTime: Boolean);
begin
  inherited Create(AConnection, ASql, ARefLifeTime);
  FCursorType := ACursorType;
end;

destructor TGMOdbcRecordsetBase.Destroy;
begin
  inherited Destroy;
  GMFreeAndNil(FFieldNameList);
  GMFreeAndNil(FFieldPosList);
  GMFreeAndNil(FCascadedContentsProperties);
end;

function TGMOdbcRecordsetBase.ActivationPropertyCreateClass: TGMActivationPropertyClass;
begin
  Result := TGMActivationStoredProperties;
end;

procedure TGMOdbcRecordsetBase.AssignFromObj(const Source: TObject);
begin
  inherited AssignFromObj(Source);
  if Source is TGMOdbcRecordsetBase then
   begin
    Attributes := TGMOdbcRecordsetBase(Source).Attributes;
    UpdateStrategy := TGMOdbcRecordsetBase(Source).UpdateStrategy;
    PositionedUpdateSimulation := TGMOdbcRecordsetBase(Source).PositionedUpdateSimulation;
    CursorSensitivity := TGMOdbcRecordsetBase(Source).CursorSensitivity;
    CursorType := TGMOdbcRecordsetBase(Source).CursorType;
    KeysetSize := TGMOdbcRecordsetBase(Source).KeysetSize;
    MaxRecordsReturned := TGMOdbcRecordsetBase(Source).MaxRecordsReturned;
    MaxFieldDataSize := TGMOdbcRecordsetBase(Source).MaxFieldDataSize;
//  BlobCompressionType := TGMOdbcRecordsetBase(Source).BlobCompressionType;
    RecordCountStrategies := TGMOdbcRecordsetBase(Source).RecordCountStrategies;
    CascadedContentsProperties := TGMOdbcRecordsetBase(Source).CascadedContentsProperties;
   end;
end;

procedure TGMOdbcRecordsetBase.SetODBCAttributes;
begin
  if GetHandleAllocated then
   begin
    inherited SetODBCAttributes;
    //if Ord(UpdateStrategy) <> (SQL_CONCUR_DEFAULT - 1) then
       SetODBCUpdateStrategy(UpdateStrategy);

    //if (raUseScrollableCursor in Attributes) <> Boolean(SQL_SCROLLABLE_DEFAULT) then SetODBCCursorScrollable(raUseScrollableCursor in Attributes);
    //if Ord(CursorSensitivity) <> SQL_UNSPECIFIED then
       SetODBCCursorSensitivity(CursorSensitivity);

    if Ord(CursorType) <> SQL_CURSOR_TYPE_DEFAULT then SetODBCCursorType(CursorType);
    if KeysetSize <> SQL_DEFAULT_KEYSET_SIZE then SetODBCKeysetSize(KeysetSize);
    if MaxRecordsReturned <> SQL_DEFAULT_MAX_RECORDS then SetODBCMaxRecordsReturned(MaxRecordsReturned);
    if MaxFieldDataSize <> SQL_DEFAULT_MAX_DATA_SIZE then SetODBCMaxFieldDataSize(MaxFieldDataSize);
    if UseSQLEscapeSequences <> not Boolean(SQL_NOSCAN_DEFAULT) then SetODBCUseSQLEscapeSequences(UseSQLEscapeSequences);
    if (raBookmarksEnabled in Attributes) <> Boolean(SQL_UB_DEFAULT) then SetODBCBookmarksEnabled(raBookmarksEnabled in Attributes);

    //if Ord(PositionedUpdateSimulation) <> SQL_SC_DEFAULT then
       SetODBCPositionedUpdateSimulation(PositionedUpdateSimulation);

    SetODBCRowArraySize(cRowArraySize);
    SetODBCRowStatusPtr(@FRowStatusArray);
   end;
end;

procedure TGMOdbcRecordsetBase.SetSimplestConfiguration;
begin
  Attributes := cSimplestRecordsetAttributes;
  CursorType := ctUnidirectional;
  UpdateStrategy := usReadOnly; // <- MS Access unidirectional only allowed readonly
                                //    Readonly speeds up reading records for some drivers because they use an internal chache for readonly cursors
  //CursorSensitivity := csUnspecified;
end;

procedure TGMOdbcRecordsetBase.AllocHandle;
begin
  inherited AllocHandle;
  if InternalExecuted then
   begin
    CreateFields;
    FState := rsBrowsing;
   end;
end;

procedure TGMOdbcRecordsetBase.ResetMembers;
var i: Integer;
begin
  inherited ResetMembers;
  FRecordCount := cGMUnknownCount;
  FPosition := 0;
  FFetchResult.ResultCode := SQL_SUCCESS;
  FFetchResult.ErrorText := '';
  for i:=Low(RowStatusArray) to High(RowStatusArray) do FRowStatusArray[i] := SQL_ROW_SUCCESS;
end;

procedure TGMOdbcRecordsetBase.ReleaseHandle;
//var mousePtrWait: IUnknown;
begin
  ReExecutionTimer.Stop;
  ClearFieldLists;

  if InternalExecuted then
   begin
//  mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);
     //ODBCCheck(SQLCloseCursor(Handle), Self, {$I %CurrentRoutine%}); <-- no Exception here, may be called from destructor
    SQLCloseCursor(Handle);
    FInternalExecuted := False;
   end;

  inherited ReleaseHandle;
end;


{ ---- Fields ---- }

function TGMOdbcRecordsetBase.GetODBCFieldCount: SQLSMALLINT;
{$IFDEF FPC}{$push}{$WARN 5060 off : Function result variable does not seem to be initialized}{$ENDIF}
begin
  CheckIsActive('ODBCFieldCount ' + RStrProperty);
  ODBCCheck(SQLNumResultCols(Handle, Result), Self, 'SQLNumResultCols');
end;
{$IFDEF FPC}{$pop}{$ENDIF}

procedure TGMOdbcRecordsetBase.ClearFieldLists;
begin
  FieldNameList.Clear;
  FieldPosList.Clear;
end;

procedure TGMOdbcRecordsetBase.CreateFields;
var i: Integer; fieldCreateData: RGMFieldCreateData; bookMarkField: TObject;

  function AssignFieldCreateData(const AColumnPos: Integer): RGMFieldCreateData;
  var nameLen, odbcDataType, decDigits, nullable: SQLSMALLINT; sizeInChars: SQLULEN;
  begin
    Result := Default(RGMFieldCreateData);
    Result.Position := AColumnPos;
    SetLength(Result.Name, 2048);

    nullable := SQL_NULLABLE_UNKNOWN;

    OdbcCheck(SQLDescribeCol(Handle, AColumnPos, PGMChar(Result.Name), Length(Result.Name)+1, nameLen, odbcDataType, sizeInChars, decDigits, nullable), Self, 'SQLDescribeCol');
    if nameLen < Length(Result.Name) then Setlength(Result.Name, nameLen);

    if (Length(Result.Name) <= 0) then
     begin
      if AColumnPos = cOdbcBookmarkColPos then Result.Name := 'Bookmark' else Result.Name := 'UnnamedCol' + GMIntToStr(AColumnPos);
     end;

    Result.IsSigned := ODBCIsSignedField(Handle, AColumnPos, Self, {$I %CurrentRoutine%});

    if AColumnPos = cOdbcBookmarkColPos then odbcDataType := SQL_C_VARBOOKMARK;
    Result.DataType := FieldTypeFromODBCType(odbcDataType, Result.IsSigned, Self, {$I %CurrentRoutine%});

    case nullable of
     SQL_NO_NULLS:         Result.AllowNullValues := nvNullValuesNotAllowed;
     SQL_NULLABLE:         Result.AllowNullValues := nvNullValuesAllowed;
     SQL_NULLABLE_UNKNOWN: Result.AllowNullValues := nvNullableUnknown;
     else Result.AllowNullValues := nvNullableUnknown;
     //else raise EGMException.ObjError(MsgUnknownPropVal('NullValuesAllowed', nullable), Self, {$I %CurrentRoutine%});
    end;

    if IsIntegerFieldDataType(Result.DataType) then
      Result.IsAutoincrementing := ODBCFieldIsAutoIncrementing(Handle, AColumnPos, Self, {$I %CurrentRoutine%})
    else
      Result.IsAutoincrementing := False;

    if IsIntegerFieldDataType(Result.DataType) and Result.IsAutoIncrementing then Result.Updatable := False
       else Result.Updatable := ODBCFieldUpdatable(Handle, AColumnPos, Self, {$I %CurrentRoutine%});

//  Result.BlobCompressionType := Ord(ABlobCompressionType);
    Result.SizeInBytes := 0;
    Result.MaxStrLength := 0;
    case Result.DataType of
     fdtAnsiString, fdtUnicodeString:
      begin
//     Result.SizeInBytes := sizeInChars * GMCharSizeInBytes(Result.DataType);
       if not ODBCSucceeded(SQLColAttribute(Handle, AColumnPos, SQL_DESC_OCTET_LENGTH, nil, 0, nil, @Result.SizeInBytes)) then Result.SizeInBytes := 0;
       Result.MaxStrLength := sizeInChars; // Round(sizeInChars * MaxStringLengthCharSizeFactor);
      end;

     fdtBinary: if AColumnPos = cOdbcBookmarkColPos then Result.SizeInBytes := sizeInChars;
    end;

//  case GMCharSizeInBytes(Result.DataType) of
//   0: begin Result.SizeInBytes := 0; Result.MaxStrLength := 0; end;
//   else
//    begin
////     Result.SizeInBytes := sizeInChars * GMCharSizeInBytes(Result.DataType);
//     if not ODBCSucceeded(SQLColAttribute(Handle, AColumnPos, SQL_DESC_OCTET_LENGTH, nil, 0, nil, @Result.SizeInBytes)) then Result.SizeInBytes := 0;
//     Result.MaxStrLength := sizeInChars; // Round(sizeInChars * MaxStringLengthCharSizeFactor);
//    end;
//  end
  end;

begin
  if Active then
   begin
    ClearFieldLists;

    if raBookmarksEnabled in Attributes then
     begin
      bookMarkField := FieldPosList.Add(vODBCFieldCreateClasses[FieldTypeFromODBCType(SQL_C_VARBOOKMARK, False, Self, {$I %CurrentRoutine%})].Create(Self, AssignFieldCreateData(cOdbcBookmarkColPos))); // , ctNone
      if raExposeBookmarkColumn in Attributes then FieldNameList.Add(bookMarkField);
     end;

    for i:=1 to ODBCFieldCount do
     try
      fieldCreateData := AssignFieldCreateData(i); // , BlobCompressionType
      if Assigned(OnCreateField) then OnCreateField(fieldCreateData.Name, fieldCreateData);
      FieldNameList.Add(FieldPosList.Add(vODBCFieldCreateClasses[fieldCreateData.DataType].Create(Self, fieldCreateData)));
     except end;
   end;
end;

function TGMOdbcRecordsetBase.GetIntfByName(const AFieldName: TGMString; const AIID: TGUID; out AIntf): HResult; stdcall;
var searchName: IGMGetName; foundField: TObject;
begin
  searchName := TGMNameObj.Create(AFieldName, True);
  if FieldNameList.Find(searchName, foundField) then
    Result := CQIResult[foundField.GetInterface(AIID, AIntf)]
  else
    Result := GMHResultFromWin32(ERROR_FILE_NOT_FOUND); // ERROR_INVALID_NAME
end;

function TGMOdbcRecordsetBase.GetIntfByPosition(const AFieldPosition: PtrInt; const AIID: TGUID; out AIntf): HResult; stdcall;
var searchPosition: IGMGetPosition; foundField: TObject;
begin
  searchPosition := TGMPositionObj.Create(AFieldPosition, True);
  if FieldPosList.Find(searchPosition, foundField) then
    Result := CQIResult[foundField.GetInterface(AIID, AIntf)]
  else
    Result := GMHResultFromWin32(ERROR_INVALID_INDEX);
end;

function TGMOdbcRecordsetBase.GetFieldByIndex(const AIndex: RGMUnionValue): TGMOdbcField;
const cBookmarkColOffs: array [Boolean] of PtrInt = (-1 , 0);
var fieldPos: PtrInt; fieldName: TGMString; foundField: TObject;
begin
  case AIndex.ValueType of
   uvtInt16, uvtInt32, uvtInt64, uvtDouble:
    begin
     fieldPos := AIndex + cBookmarkColOffs[raBookmarksEnabled in Attributes];
     Result := FieldPosList[fieldPos] as TGMOdbcField;
    end;

   uvtString:
    begin
     fieldName := AIndex;
     if FSearchName = nil then FSearchName := TGMNameObj.Create('', True); // <- Avoid repeated memory allocation
     FSearchName.Name := fieldName;
     if FieldNameList.Find(FSearchName, foundField) then
       Result := foundField as TGMOdbcField
     else
       raise EGMException.ObjError(GMFormat(RStrFieldNotFound, [fieldName, GMGetObjName(Self)]), Self, {$I %CurrentRoutine%})
    end;

   else raise EGMException.ObjError(GMFormat(RStrUnsupportedIdxType, [AIndex.ValueTypeName]), Self, {$I %CurrentRoutine%});
  end;
end;

function TGMOdbcRecordsetBase.GetFieldValue(const AIndex: RGMUnionValue): RGMUnionValue;
var field: TGMOdbcField;
begin
  //Result := GMGetFieldValueImpl(Self, AIndex);
  CheckIsActive({$I %CurrentRoutine%});
  field := GetFieldByIndex(AIndex);
  Result := field.Value;
end;

procedure TGMOdbcRecordsetBase.SetFieldValue(const AIndex: RGMUnionValue; const AValue: RGMUnionValue);
var field: TGMOdbcField;
begin
  //GMSetFieldValueImpl(Self, AIndex, AValue);
  CheckIsActive({$I %CurrentRoutine%});
  field := GetFieldByIndex(AIndex);
  field.Value := AValue;
end;


{ ---- Lookup / locate ---- }

function TGMOdbcRecordsetBase.LookupValues(const SQLCriteria: TGMString; const Values: IUnknown): Boolean; stdcall;
begin
  Result := GMLookupValues(Self, Values, SQLCriteria, False);
end;

function TGMOdbcRecordsetBase.PositionOfNearestValues(const Values: IGMGetIntfByPosition): LongInt; stdcall;
var PICount: IGMGetCount;
  function Compare(const RecPos: Integer): TGMCompareResult;
  var i: Integer; PIValue: IGMGetUnionValue; PIName: IGMGetName;
  begin
    Result := crALessThanB;
    Position := RecPos;
    for i:=0 to PICount.Count-1 do
     begin
      GMCheckGetIntfByPosition(Values, i, IGMGetUnionValue, PIValue, RStrValue, {$I %CurrentRoutine%});
      GMCheckGetIntfByPosition(Values, i, IGMGetName, PIName, RStrValue, {$I %CurrentRoutine%});
      Result := GMCompareUnionValues(PIValue.Value, FieldValue[PIName.Name], GMCheckAskBoolean(PIValue, Ord(bvMatchCase), {$I %CurrentRoutine%}));
      if Result <> crAEqualToB then Break;
     end;
  end;

  function FindInRange(const L, R: Integer): Integer;
  var M: Integer; CmpRes: TGMCompareResult;
  begin
    if L >= R then Result := L else
     begin
      M := (L + R) shr 1;
      CmpRes := Compare(M);
      case CmpRes of
       crAEqualToB: Result := M;
       crALessThanB: Result := FindInRange(L, M);
       crAGreaterThanB: if L = M then Result := R else Result := FindInRange(M, R);
       else raise EGMException.ObjError(MsgUnknownValue(cStrTCompareResult, Ord(CmpRes)), Self, {$I %CurrentRoutine%});
      end;
     end;
  end;
begin
  GMCheckQueryInterface(Values, IGMGetCount, PICount, {$I %CurrentRoutine%});
  Result := FindInRange(1, Count);
end;

procedure TGMOdbcRecordsetBase.CheckLocateValues(const Values: IGMGetIntfByPosition);
var SortClause, SortPart: TGMString; i, Pos1, Pos2: PtrInt; PICount: IGMGetCount; PIName: IGMGetName;
begin
  GMCheckQueryInterface(Values, IGMGetCount, PICount, {$I %CurrentRoutine%});

  if PICount.Count = 0 then raise EGMException.ObjError(srNoValuesToLocate, Self, {$I %CurrentRoutine%});

  SortClause := SQL.SQLOrderBy; Pos1 := 1;
  for i:=0 to PICount.Count-1 do
   begin
    GMCheckGetIntfByPosition(Values, i, IGMGetName, PIName, RStrValue, {$I %CurrentRoutine%});
    SortPart := GMExtractNextFieldName(Pos1, SortClause); Pos2 := 1;
    if not GMFindToken(SortPart, PIName.Name, Pos2, cSqlSeparators, True, True) then
     raise EGMException.ObjError(GMFormat(srNoSortClauseForField, [PIName.Name]), Self, {$I %CurrentRoutine%});
   end;
end;

function TGMOdbcRecordsetBase.LocateResult(const Values: IGMGetIntfByPosition; const FindPos: LongInt): Boolean;
var i: Integer; PICount: IGMGetCount; PIValue: IGMGetUnionValue; PIName: IGMGetName;
begin
  GMCheckQueryInterface(Values, IGMGetCount, PICount, {$I %CurrentRoutine%});
  Position := FindPos;
  Result := True;
  for i:=0 to PICount.Count-1 do
   begin
    GMCheckGetIntfByPosition(Values, i, IGMGetUnionValue, PIValue, RStrValue, {$I %CurrentRoutine%});
    GMCheckGetIntfByPosition(Values, i, IGMGetName, PIName, RStrValue, {$I %CurrentRoutine%});
    if (GMCheckAskInteger(PIValue, Ord(ivMatchKind), {$I %CurrentRoutine%}) = Ord(GMIntf.mkExactMatch)) and
       (GMCompareUnionValues(PIValue.Value, FieldValue[PIName.Name], GMCheckAskBoolean(PIValue, Ord(bvMatchCase), {$I %CurrentRoutine%})) <> crAEqualToB) then
     begin Result := False; Break; end;
   end;
end;

function TGMOdbcRecordsetBase.PositionOfValues(const Values: IUnknown; var FindPos: LongInt): Boolean; stdcall;
var SaveState: IUnknown; PIIntfByPos: IGMGetIntfByPosition;
begin
  GMCheckQueryInterface(Values, IGMGetIntfByPosition, PIIntfByPos, {$I %CurrentRoutine%});
  CheckLocateValues(PIIntfByPos);
  DisableNotifications;
  try
   SaveState := CaptureState;
   try
    LeaveModifyingState;
    FindPos := PositionOfNearestValues(PIIntfByPos);
    Result := LocateResult(PIIntfByPos, FindPos);
    if not Result then FindPos := CGMUnknownPosition;
   finally
    RestoreState(SaveState);
   end;
  finally
   EnableNotifications;
  end;
end;

function TGMOdbcRecordsetBase.LocateValues(const Values: IUnknown): Boolean; stdcall;
//const CRefreshKind: array [Boolean] of LongInt = (Ord(rgNone), Ord(rgRefreshPosition));
var SaveState: IUnknown; FindPos: LongInt; PIIntfByPos: IGMGetIntfByPosition;
begin
  Result := False;
  GMCheckQueryInterface(Values, IGMGetIntfByPosition, PIIntfByPos, {$I %CurrentRoutine%});
  CheckLocateValues(PIIntfByPos);
  DisableNotifications(Ord(rgRefreshPosition));
  try
   SaveState := CaptureState;
   try
    LeaveModifyingState;
    FindPos := PositionOfNearestValues(PIIntfByPos);
    Result := LocateResult(PIIntfByPos, FindPos);
    if Result then Position := FindPos;
   finally
    if not Result then RestoreState(SaveState);
   end;
  finally
   EnableNotifications(Ord(rgRefreshPosition){CRefreshKind[Result]});
  end;
end;


{ ---- IGMEnumerateItems ---- }

procedure TGMOdbcRecordsetBase.EnumerateItems(const ItemKind: LongInt; const TellEnumSink: IUnknown; const Parameter: Pointer);
const CSlTables = slTables;
var i: Integer; PIEnumSink: IGMTellEnumString; PIActivator, PIQuiet: IUnknown;
    schemaCursor: IGMUnidirectionalCursor;
    PIAssignFromObj: IGMAssignFromObj;
    schemaProps: IGMSchemaProperties;
begin
  inherited EnumerateItems(ItemKind, TellEnumSink, Parameter);
  if not GMQueryInterface(TellEnumSink, IGMTellEnumString, PIEnumSink) then Exit;

  case ItemKind of
   Ord(eidFieldNames):
    begin
     PIQuiet := TGMNotificationDisabler.Create(Self);
     PIActivator := TGMActiveKeeper.Create(Self, True);
     for i:=0 to FieldPosList.Count-1 do
      if (FieldPosList[i] is TGMDBField) and
         ((TGMDBField(FieldPosList[i]).Position <> cOdbcBookmarkColPos) or (raExposeBookmarkColumn in Attributes)) then
       try PIEnumSink.TellEnumString(ItemKind, TGMDBField(FieldPosList[i]).Name, Pointer(Parameter)); except end;
    end;

   Ord(eidTableNames):
    begin
     schemaCursor := TGMOdbcSchemaRecordset.Create(True);
     GMCheckQueryInterface(schemaCursor, IGMAssignFromObj, PIAssignFromObj);
     PIAssignFromObj.AssignFromObj(Self);
     GMCheckExecRSOperation(schemaCursor, roSetSimplestConfiguration);
     GMCheckQueryInterface(schemaCursor, IGMSchemaProperties, schemaProps);
     //GMHrCheckObj(GMGetPropIntfFromIntf(schemaCursor, cStrSchemaData, IGMSchemaProperties, schemaProps), Self);
     schemaProps.SchemaList := cSlTables;
     schemaProps.TableName := cSqlWildcardChar;
     GMSetIntfActive(schemaCursor, True);
     while not schemaCursor.EOF do
      begin
       PIEnumSink.TellEnumString(ItemKind, GMGetItemValue(schemaCursor, COdbcSchemaNameColPos[CSlTables]), Pointer(Parameter));
       schemaCursor.MoveToNext;
      end;
    end;
  end;
end;


{ ---- IGMFreeCursor ---- }

function TGMOdbcRecordsetBase.ODBCFetchSuccess: Boolean;
begin
  Result := OdbcSucceeded(FetchResult.ResultCode);
end;

function TGMOdbcRecordsetBase.IsEmpty: Boolean;
begin
  Result := Count <= 0;
end;

function TGMOdbcRecordsetBase.GetBOF: Boolean; stdcall;
begin
  Result := Position <= 1;
end;

function TGMOdbcRecordsetBase.GetEOF: Boolean; stdcall;
begin
  if CursorType = ctUnidirectional then
   Result := not OdbcSucceeded(FetchResult.ResultCode)
  else
   Result := Position >= Count;
end;

procedure TGMOdbcRecordsetBase.DoCursorMove(const ADirection: SQLSMALLINT; AOffset: SQLLEN; const AMethodName: TGMString);
  function IsValidMove(const ADirection: SQLSMALLINT; var AOffset: SQLLEN): Boolean;
  begin
    case ADirection of
     SQL_FETCH_NEXT, SQL_FETCH_LAST:   Result := not EOF;
     SQL_FETCH_PRIOR, SQL_FETCH_FIRST: Result := not BOF;
     SQL_FETCH_ABSOLUTE: begin Result := not IsEmpty; AOffset := GMBoundedInt(AOffset, 1, Count); end;
     SQL_FETCH_RELATIVE: begin Result := not IsEmpty; AOffset := GMBoundedInt(AOffset, 1 - Position, Count - Position); end;
     else Result := False;
    end;
  end;
begin
  if IsValidMove(ADirection, AOffset) then DoCursorFetch(ADirection, AOffset, AMethodName);
end;

procedure TGMOdbcRecordsetBase.DoCursorFetch(const ADirection: SQLSMALLINT; AOffset: SQLLEN; const AMethodName: TGMString; const ANotifyPositionChange: Boolean);
begin
  if (ADirection = SQL_FETCH_NEXT) or (CursorType <> ctUnidirectional) then
   begin
    if ANotifyPositionChange then NotifyBeforePositionChange;

//  if ADirection in [SQL_FETCH_ABSOLUTE, SQL_FETCH_RELATIVE] then
     case ADirection of
      SQL_FETCH_ABSOLUTE: AOffset := GMBoundedInt(AOffset, 1, Count);
      SQL_FETCH_RELATIVE: AOffset := GMBoundedInt(Position + AOffset, 1, Count);
     end;

    if (CursorType = ctUnidirectional) then
     FFetchResult.ResultCode := SQLFetch(Handle)
    else
     FFetchResult.ResultCode := SQLFetchScroll(Handle, ADirection, AOffset);

    if not ODBCSucceeded(FetchResult.ResultCode) then
     FFetchResult.ErrorText := OdbcErrorDescAsString(OdbcErrorDesc(HandleType, Handle, 1, FetchResult.ResultCode))
    else
     begin
      FFetchResult.ErrorText := '';

      case ADirection of
       SQL_FETCH_NEXT:     Inc(FPosition);
       SQL_FETCH_LAST:     FPosition := Count;
       SQL_FETCH_PRIOR:    Dec(FPosition);
       SQL_FETCH_FIRST:    FPosition := 1;
       SQL_FETCH_ABSOLUTE: FPosition := AOffset;
       SQL_FETCH_RELATIVE: FPosition := FPosition + AOffset;
      end;

      if ANotifyPositionChange then NotifyAfterPositionChange;
     end;
   end;
end;

procedure TGMOdbcRecordsetBase.MoveToNext; stdcall;
begin
  DoCursorMove(SQL_FETCH_NEXT, 0, 'MoveToNext');
end;

procedure TGMOdbcRecordsetBase.MoveToLast; stdcall;
begin
  DoCursorMove(SQL_FETCH_LAST, 0, 'MoveToLast');
end;

procedure TGMOdbcRecordsetBase.MoveToPrevious; stdcall;
begin
  DoCursorMove(SQL_FETCH_PRIOR, 0, 'MoveToPrevious');
end;

procedure TGMOdbcRecordsetBase.MoveToFirst; stdcall;
begin
  DoCursorMove(SQL_FETCH_FIRST, 0, 'MoveToFirst');
end;

{ ---- IGMGetSetPosition ---- }

function TGMOdbcRecordsetBase.GetPosition: PtrInt; stdcall;
begin
  Result := FPosition;
end;

procedure TGMOdbcRecordsetBase.SetPosition(const AValue: PtrInt); stdcall;
begin
  if AValue <> Position then DoCursorMove(SQL_FETCH_ABSOLUTE, AValue, {$I %CurrentRoutine%});
end;

{ ---- IGMAskXxxxx ---- }

function TGMOdbcRecordsetBase.AskBoolean(const AValueId: LongInt): LongInt; stdcall;
begin
  case AValueId of
   //Ord(bvCursorValid):      Result := GMBooleanAskResult(CursorValid);
   Ord(bvConfirmDeletions): Result := GMBooleanAskResult(raConfrimDeletions in Attributes);
   //Ord(bvIsUnidirectional): Result := GMBooleanAskResult(CursorType = ctUnidirectional);
   //Ord(bvIsEmpty):          Result := GMBooleanAskResult(IsEmpty);
   Ord(bvCanModify):        Result := GMBooleanAskResult(CanModify);
   Ord(bvPositionalInsert): Result := GMBooleanAskResult(PositionalInsert);
   Ord(bvCanSetPosition):   Result := GMBooleanAskResult(Active and (CursorType <> ctUnidirectional));
   else Result := Ord(barUnknown);
  end;
end;

function TGMOdbcRecordsetBase.AskInteger(const AValueId: LongInt): LongInt; stdcall;
begin
  case AValueId of
   //Ord(ivRowStatus): Result := RowStatusArray[Low(RowStatusArray)];
   Ord(ivCursorType): Result := Ord(CursorType);
   Ord(ivFieldCount): Result := FieldNameList.Count;
   else Result := CInvalidIntValue;
  end;
end;

procedure TGMOdbcRecordsetBase.LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); stdcall;
var assignToObj: IGMAssignToObj;
begin
  assignToObj := TGMOdbcRecordsetProperties.Create(Source, ACryptCtrlData, True, True);
  assignToObj.AssignToObj(Self);
end;

procedure TGMOdbcRecordsetBase.StoreData(const Dest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); stdcall;
var loadStore: IGMLoadStoreData;
begin
  loadStore := TGMOdbcRecordsetProperties.Create(Self, True, True);
  loadStore.StoreData(Dest, ACryptCtrlData);
end;


{ ---- IGMGetCount ---- }

function TGMOdbcRecordsetBase.ComputeRecordCount: SQLLEN;
const StartCount = 256;
var cnt: SQLLEN; SaveState: IUnknown;
  function CountBySelectStatement: SQLLEN;
  var count: RGMUnionValue; // sss: TGMString;
  begin
    try
     //sss := GMModifyToSelectCountSQL(SQL.SQLText, Self);
     count := GMOdbcSelectSingleValue(ConnectionIntf, GMModifyToSelectCountSQL(SQL.SQLText, Self), 1);
    except count := uvtUnassigned end;
    if Count.IsNullOrUnassigned then Result := cGMUnknownCount else Result := Count;
  end;

  function LogarithmicCount: SQLLEN;
    function FindLastPosition(const al, ar: SQLLEN): SQLLEN;
    var m: LongInt; //SQLCode: SQLRETURN;
    begin
      if al >= ar then Result := al else
       begin
        m := (al + ar) shr 1;
        //SQLCode := SQLFetchScroll(Handle, SQL_FETCH_ABSOLUTE, m);
        //if not ODBCIsValidFetch(SQLFetchScroll(Handle, SQL_FETCH_ABSOLUTE, m)) then
        if not ODBCSucceeded(SQLFetchScroll(Handle, SQL_FETCH_ABSOLUTE, m)) then
         Result := FindLastPosition(al, m)
        else
         if al = m then Result := ar else Result := FindLastPosition(m, ar);
       end;
    end;
  begin
    FFetchResult.ErrorText := '';
    DisableNotifications;
    try
     SaveState := CaptureState;
     try
      LeaveModifyingState;
      cnt := StartCount;
      GMNotifyFieldsBeforePositionChange(FieldPosList);
      try
       //while ODBCIsValidFetch(SQLFetchScroll(Handle, SQL_FETCH_ABSOLUTE, cnt)) do cnt := cnt shl 1;
       while ODBCSucceeded(SQLFetchScroll(Handle, SQL_FETCH_ABSOLUTE, cnt)) do cnt := cnt shl 1;
       Result := FindLastPosition(1, cnt) - 1;
       FFetchResult.ResultCode := SQLFetchScroll(Handle, SQL_FETCH_ABSOLUTE, FPosition);
      finally
       GMNotifyFieldsAfterPositionChange(FieldPosList);
      end;
     finally
      RestoreState(SaveState);
     end;
    finally
     EnableNotifications;
    end;
  end;
begin
  Result := cGMUnknownCount;
  if Active then
   begin
    if csUseCountFromODBCDriver in RecordCountStrategies then Result := ODBCRecordCount;
    if (Result = cGMUnknownCount) and (csUseSelectCountStatement in RecordCountStrategies) then Result := CountBySelectStatement;
    if (Result = cGMUnknownCount) and (csCountByLogarithmicPositioning in RecordCountStrategies) and (CursorType <> ctUnidirectional) then Result := LogarithmicCount;
   end;
end;

function TGMOdbcRecordsetBase.GetCount: PtrInt; stdcall;
//const cUniDirRecCounts: array [Boolean] of LongInt = (0, 1);
begin
  // dont't Assign FRecordCount when inactive
  if not Active then Result := cGMUnknownCount else
   begin
    if FRecordCount = cGMUnknownCount then FRecordCount := ComputeRecordCount;
//   case CursorType of
//    ctUnidirectional: FRecordCount := cUniDirRecCounts[OdbcSucceeded(FetchResult.ResultCode)]
//    else FRecordCount := ComputeRecordCount;
//   end;
    Result := FRecordCount;
   end;
end;


{ ---- IGMSaveRestoreState ---- }

function TGMOdbcRecordsetBase.CaptureState: IUnknown;
begin
  Result := TGMOdbcRecordsetState.Create(Self);
end;

procedure TGMOdbcRecordsetBase.RestoreState(const State: IUnknown);
var PIInftAssign: IGMAssignToIntf;
begin
  if GMQueryInterface(State, IGMAssignToIntf, PIInftAssign) then PIInftAssign.AssignToIntf(Self);
end;


{ ---- Operations ---- }

procedure TGMOdbcRecordsetBase.LeaveModifyingState;
begin
  if IsUpdatableState(Ord(State)) then
   if not (raAutoSaveChanges in Attributes) then CancelChanges else
    try ApplyChanges; except CancelChanges; raise; end;
end;

function TGMOdbcRecordsetBase.CanModify: Boolean;
begin                                                                                                                    
  Result := inherited CanModify and (UpdateStrategy <> usReadOnly);
end;

function TGMOdbcRecordsetBase.PositionalInsert: Boolean;
var posIns: SQLINTEGER;
begin
  Result := GetHandleAllocated and ODBCSucceeded(SQLGetStmtAttr(Handle, SQL_ATTR_GM_POSITIONAL_INSERT, @posIns, SQL_IS_INTEGER, nil)) and (posIns = SQL_TRUE);
end;

procedure TGMOdbcRecordsetBase.Edit;
begin
  DoStateChange(roEdit);
end;

procedure TGMOdbcRecordsetBase.Insert(const Parameter: IUnknown = nil);
begin
  DoStateChange(roInsert, nil, Parameter);
end;

procedure TGMOdbcRecordsetBase.CancelChanges;
begin
  DoStateChange(roCancelChanges);
end;

procedure TGMOdbcRecordsetBase.Applychanges;
var mousePtrWait: IUnknown; oldState: TGMRecordsetState;
begin
  mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);
  oldState := State;
  NotifyValidateFieldValues;
  DoStateChange(roApplyChanges, InternalApplyChanges);
  if (oldState = rsInserting) {and not PositionalInsert} then
   begin
    // Fields have been notified inside InternalApplyChanges
    NotifyConnectedObjectsBeforePositionChange;
    NotifyConnectedObjectsAfterPositionChange;
   end;
end;

procedure TGMOdbcRecordsetBase.InternalApplyChanges;
var i: Integer; nothingChanged: Boolean;
  procedure UpdateData(ARowNumber: SQLSETPOSIROW; AOperation: SQLUSMALLINT);
  var sqlCode: SQLRETURN; searchPos: IGMGetPosition; i: Integer; PParamData: Pointer; field: TGMOdbcField;
  begin
    for i:=0 to FieldPosList.Count-1 do try (FieldPosList[i] as TGMOdbcField).SetupDataLengthForUpdate; except end;
    try
     sqlCode := SQLSetPos(Handle, ARowNumber, AOperation, SQL_LOCK_NO_CHANGE);
     try
      if sqlCode <> SQL_NEED_DATA then OdbcCheck(sqlCode, Self, {$I %CurrentRoutine%}) else
       begin
        repeat
         sqlCode := SQLParamData(Handle, PParamData);
         if sqlCode = SQL_NEED_DATA then
          begin
           searchPos := TGMPositionObj.Create(PLongInt(PParamData)^, True);
           if FieldPosList.Find(searchPos, field) then field.StoreBlobData
           else
            raise EGMException.ObjError(GMFormat(srUnknownBlobFieldPos, [searchPos.Position]), Self, {$I %CurrentRoutine%});
          end;
        until sqlCode <> SQL_NEED_DATA;
        OdbcCheck(sqlCode, Self, {$I %CurrentRoutine%});
       end;
     except
      if sqlCode = SQL_NEED_DATA then SQLCancel(Handle); raise;
     end;
    finally
     for i:=0 to FieldPosList.Count-1 do try (FieldPosList[i] as TGMOdbcField).RestoreDataLength; except end;
    end;
  end;
begin
  nothingChanged := True;
  for i:=0 to FieldPosList.Count-1 do if (FieldPosList[i] as TGMDBField).Modified then begin nothingChanged := False; Break; end;
  case State of
   rsEditing: if not nothingChanged then UpdateData(1, SQL_UPDATE);
   rsInserting:
    begin
     if nothingChanged then raise EGMException.ObjError(srNoFieldChangedBeforeInsert, Self, {$I %CurrentRoutine%});
     UpdateData(1, SQL_ADD);
     if FRecordCount <> cGMUnknownCount then Inc(FRecordCount); // (RecordCountStrategies <> []) and
     if CursorType <> ctUnidirectional then
      begin
       if PositionalInsert then DoCursorFetch(SQL_FETCH_ABSOLUTE, Position, {$I %CurrentRoutine%}, False) else DoCursorFetch(SQL_FETCH_LAST, 0, {$I %CurrentRoutine%}, False);
       GMNotifyFieldsBeforePositionChange(FieldPosList);
       GMNotifyFieldsAfterPositionChange(FieldPosList);
      end;
    end;
  end;
end;

procedure TGMOdbcRecordsetBase.RefreshRecord;
var mousePtrWait: IUnknown;
begin
  mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);
  NotifyBeforeOperation(Ord(roRefreshCurrent));
  ODBCCheck(SQLSetPos(Handle, 1, SQL_REFRESH, SQL_LOCK_NO_CHANGE), Self, {$I %CurrentRoutine%});
  NotifyAfterOperation(Ord(roRefreshCurrent));
end;

procedure TGMOdbcRecordsetBase.Delete;
var mousePtrWait: IUnknown;
begin
  mousePtrWait := TGMTempCursor.Create(vDBWaitCursor);
  NotifyBeforeOperation(Ord(roDelete));
  ODBCCheck(SQLSetPos(Handle, 1, SQL_DELETE, SQL_LOCK_NO_CHANGE), Self, {$I %CurrentRoutine%});
  if (FRecordCount > 0) and (FRecordCount <> cGMUnknownCount) then Dec(FRecordCount); // and (CursorType <> ctUnidirectional) and (RecordCountStrategies <> []) then FRecordCount := Max(0, FRecordCount-1);
  NotifyAfterOperation(Ord(roDelete)); // <- reflect new count here!
  DoCursorFetch(SQL_FETCH_NEXT, Position, {$I %CurrentRoutine%}); // <- SQL_FETCH_ABSOLUTE did�NOT work with ORACLE Odbc driver!
//if CursorType = ctUnidirectional then DoCursorFetch(SQL_FETCH_NEXT, Position, {$I %CurrentRoutine%}) else DoCursorFetch(SQL_FETCH_ABSOLUTE, Position, {$I %CurrentRoutine%});
end;

function TGMOdbcRecordsetBase.CanExecuteOperation(const AOperation: Integer; const AParameter: IUnknown = nil): Boolean;
begin
  case AOperation of         
   Ord(roReExecuteStatement), Ord(roScheduleReExecution): Result := inherited CanExecuteOperation(AOperation, AParameter);
   Ord(roDelete), Ord(roEdit):                  Result := {Active and} CanModify and (State = rsBrowsing) and ODBCFetchSuccess; // not IsEmpty;
   Ord(roRefreshCurrent):                       Result := {Active and} (State = rsBrowsing) and ODBCFetchSuccess; // not IsEmpty;
   Ord(roInsert):                               Result := {Active and} CanModify and (State = rsBrowsing);
   Ord(roSetSimplestConfiguration):             Result := not Active;
   Ord(roCancelChanges), Ord(roApplyChanges), Ord(roLeaveModifyingState): Result := Active and IsUpdatableState(Ord(State));
   else Result := False;
  end;
end;

function TGMOdbcRecordsetBase.ExecuteOperation(const AOperation: Integer; const AParameter: IUnknown = nil): Boolean;
begin
  Result := True;
  case AOperation of
   Ord(roReExecuteStatement), Ord(roScheduleReExecution): Result := inherited ExecuteOperation(AOperation, AParameter);
   Ord(roEdit):                     Edit;
   Ord(roInsert):                   Insert(AParameter);
   Ord(roCancelChanges):            CancelChanges;
   Ord(roApplyChanges):             ApplyChanges;
   Ord(roRefreshCurrent):           RefreshRecord;
   Ord(roDelete):                   Delete;
   Ord(roLeaveModifyingState):      LeaveModifyingState;
   Ord(roSetSimplestConfiguration): SetSimplestConfiguration;
   else Result := False
  end;
end;


{ ---- Set on ODBC API Level ---- }

procedure TGMOdbcRecordsetBase.SetODBCRowArraySize(const AValue: PtrUInt);
begin
  if Active then ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_ROW_ARRAY_SIZE, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcRecordsetBase.SetODBCRowStatusPtr(const AValue: SQLPOINTER);
begin
  if Active then ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_ROW_STATUS_PTR, AValue, 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcRecordsetBase.SetODBCUseSQLEscapeSequences(const AValue: Boolean);
var sqlEscSeq: PtrUInt;
begin
  if Active then
   begin
    if AValue then sqlEscSeq := SQL_NOSCAN_OFF else sqlEscSeq := SQL_NOSCAN_ON;
    ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_NOSCAN, SQLPOINTER(sqlEscSeq), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcRecordsetBase.SetODBCCursorType(const AValue: TGMCursorType);
var sqlCurType: PtrUInt;
begin
  if Active then
   begin
    case AValue of
     ctUnidirectional: sqlCurType := SQL_CURSOR_FORWARD_ONLY;
     ctStatic:         sqlCurType := SQL_CURSOR_STATIC;
     ctKeyset:         sqlCurType := SQL_CURSOR_KEYSET_DRIVEN;
     ctDynamic:        sqlCurType := SQL_CURSOR_DYNAMIC;
     else raise EGMException.ObjError(MsgUnknownValue('TGMCursorType', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_CURSOR_TYPE, SQLPOINTER(sqlCurType), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcRecordsetBase.SetODBCUpdateStrategy(const AValue: TUpdateStrategy);
var sqlUpdtVal: PtrUInt;
begin
  if Active then
   begin
    case AValue of
     usReadOnly:                        sqlUpdtVal := SQL_CONCUR_READ_ONLY;
     usMinimalLock:                     sqlUpdtVal := SQL_CONCUR_LOCK;
     usCompareRecordVersionBeforeWrite: sqlUpdtVal := SQL_CONCUR_ROWVER;
     usCompareValuesBeforeWrite:        sqlUpdtVal := SQL_CONCUR_VALUES;
     usUseDriverDefault:                sqlUpdtVal := 0; // <- Not used
     else raise EGMException.ObjError(MsgUnknownValue('TUpdateStrategy', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    if AValue <> usUseDriverDefault then
       ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_CONCURRENCY, SQLPOINTER(sqlUpdtVal), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcRecordsetBase.SetODBCCursorScrollable(const AValue: Boolean);
var sqlScrollVal: PtrUInt;
begin
  if Active then
   begin
    if AValue then sqlScrollVal := SQL_SCROLLABLE else sqlScrollVal := SQL_NONSCROLLABLE;
    ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_CURSOR_SCROLLABLE, SQLPOINTER(sqlScrollVal), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcRecordsetBase.SetODBCCursorSensitivity(const AValue: TCursorSensitivity);
var Sensitivity: PtrUInt;
begin
  if Active then
   begin
    case AValue of
     csUnspecified:      Sensitivity := SQL_UNSPECIFIED;
     csInsensitive:      Sensitivity := SQL_INSENSITIVE;
     csReflectChanges:   Sensitivity := SQL_SENSITIVE;
     csUseDriverDefault: Sensitivity := 0; // <- Not used
     else raise EGMException.ObjError(MsgUnknownValue('TCursorSensitivity', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    if AValue <> csUseDriverDefault then
       ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_CURSOR_SENSITIVITY, SQLPOINTER(Sensitivity), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcRecordsetBase.SetODBCPositionedUpdateSimulation(const AValue: TPositionedUpdateSimulation);
var updateSimulation: PtrUInt;
begin
  if Active then
   begin
    case AValue of
     pusAllowNonUniqueUpdate:  updateSimulation := SQL_SC_NON_UNIQUE;
     pusTryUniqueUpdate:       updateSimulation := SQL_SC_TRY_UNIQUE;
     pusGuaranteeUniqueUpdate: updateSimulation := SQL_SC_UNIQUE;
     pusUseDriverDefault:      updateSimulation := 0; // <- Not used
     else raise EGMException.ObjError(MsgUnknownValue('TPositionedUpdateSimulation', Ord(AValue)), Self, {$I %CurrentRoutine%});
    end;
    if AValue <> pusUseDriverDefault then
       ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_SIMULATE_CURSOR, SQLPOINTER(updateSimulation), 0), Self, {$I %CurrentRoutine%});
   end;
end;

procedure TGMOdbcRecordsetBase.SetODBCMaxRecordsReturned(const AValue: SQLULEN);
begin
  if Active then ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_MAX_ROWS, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcRecordsetBase.SetODBCKeysetSize(const AValue: SQLULEN);
begin
  if Active then ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_KEYSET_SIZE, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcRecordsetBase.SetODBCMaxFieldDataSize(const AValue: SQLULEN);
begin
  if Active then ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_MAX_LENGTH, SQLPOINTER(AValue), 0), Self, {$I %CurrentRoutine%});
end;

procedure TGMOdbcRecordsetBase.SetODBCBookmarksEnabled(const AValue: Boolean);
var useBookmarks: SQLULEN;
begin
  if Active then
   begin
    if AValue then useBookmarks := SQL_UB_VARIABLE else useBookmarks := SQL_UB_OFF;
    ODBCCheck(SQLSetStmtAttr(Handle, SQL_ATTR_USE_BOOKMARKS, SQLPOINTER(useBookmarks), 0), Self, {$I %CurrentRoutine%});
   end;
end;

function TGMOdbcRecordsetBase.GetODBCRowNumber: SQLLEN;
begin
  if not Active then Result := CGMUnknownPosition else
   //if not ODBCSucceeded(SQLGetStmtAttr(Handle, SQL_ATTR_ROW_NUMBER, @Result, SQL_IS_INTEGER, nil)) then Result := CGMUnknownPosition;
   if not ODBCSucceeded(SQLGetStmtAttr(Handle, SQL_ATTR_ROW_NUMBER, @Result, SizeOf(Result), nil)) then Result := CGMUnknownPosition;
end;

function TGMOdbcRecordsetBase.ODBCRecordCount: SQLLEN;
// var len:
begin
  if not Active then Result := cGMUnknownCount else
   //if not ODBCSucceeded(SQLGetStmtAttr(Handle, SQL_DESC_COUNT, @Result, SizeOf(Result), nil)) then Result := cGMUnknownCount;
   //if not ODBCSucceeded(SQLGetDiagField(HandleType, Handle, 0, SQL_DIAG_CURSOR_ROW_COUNT, @Result, SQL_IS_INTEGER, nil)) then
   if not ODBCSucceeded(SQLGetDiagField(HandleType, Handle, 0, SQL_DIAG_CURSOR_ROW_COUNT, @Result, SizeOf(Result), nil)) then
    Result := cGMUnknownCount
   else
    if Result = 0 then Result := cGMUnknownCount;
end;


{ ---- IGMGetSetAttributes ---- }

function TGMOdbcRecordsetBase.GetAttributes: Longword; stdcall;
begin
  Result := RSAttributesToLongword(Attributes);
end;

procedure TGMOdbcRecordsetBase.SetAttributes(const AValue: Longword); stdcall;
begin
  Attributes := RSAttributesFromLongword(AValue);
end;


{ ---- Property Get/Set ---- }

procedure TGMOdbcRecordsetBase.IDESetAttributes(const AValue: TGMRecordsetAttributes);
begin
  if (raBookmarksEnabled in Attributes) <> (raBookmarksEnabled in AValue) then CheckIsInactive('raBookmarksEnabled Attribute');
  //if (raFetchDataOnDemand in Attributes) <> (raFetchDataOnDemand in AValue) then CheckIsInactive('raFetchDataOnDemand Attribute');
  if (raExposeBookmarkColumn in Attributes) <> (raExposeBookmarkColumn in AValue) then CheckIsInactive('raExposeBookmarkColumn Attribute');
  //if (raUseScrollableCursor in Attributes) <> (raUseScrollableCursor in AValue) then SetODBCCursorScrollable(raUseScrollableCursor in AValue);
  FAttributes := AValue;
end;

procedure TGMOdbcRecordsetBase.SetCascadedContentsProperties(const AValue: TGMCascadedContentsProperties);
begin
  CascadedContentsProperties.AssignFromObj(AValue);
end;

procedure TGMOdbcRecordsetBase.SetUpdateStrategy(const AValue: TUpdateStrategy);
begin
  if AValue <> UpdateStrategy then
   begin
    CheckIsInactive('UpdateStrategy ' + RStrProperty);
    FUpdateStrategy := AValue;
   end;
end;

procedure TGMOdbcRecordsetBase.SetPositionedUpdateSimulation(const AValue: TPositionedUpdateSimulation);
begin
  if AValue <> PositionedUpdateSimulation then
   begin
    SetODBCPositionedUpdateSimulation(AValue);
    FPositionedUpdateSimulation := AValue;
   end;
end;

procedure TGMOdbcRecordsetBase.SetCursorSensitivity(const AValue: TCursorSensitivity);
begin
  if AValue <> CursorSensitivity then
   begin
    SetODBCCursorSensitivity(AValue);
    FCursorSensitivity := AValue;
   end;
end;

procedure TGMOdbcRecordsetBase.SetCursorType(const AValue: TGMCursorType);
begin
  if AValue <> CursorType then
   begin
    CheckIsInactive('CursorType ' + RStrProperty);
    FCursorType := AValue;
   end;
end;

procedure TGMOdbcRecordsetBase.SetKeysetSize(const AValue: SQLUINTEGER);
begin
  if AValue <> KeysetSize then
   begin
    SetODBCKeysetSize(AValue);
    FKeysetSize := AValue;
   end;
end;

procedure TGMOdbcRecordsetBase.SetMaxRecordsReturned(const AValue: SQLUINTEGER);
begin
  if AValue <> MaxRecordsReturned then
   begin
    SetODBCMaxRecordsReturned(AValue);
    FMaxRecordsReturned := AValue;
   end;
end;

procedure TGMOdbcRecordsetBase.SetMaxFieldDataSize(const AValue: SQLUINTEGER);
begin
  if AValue <> MaxFieldDataSize then
   begin
    SetODBCMaxFieldDataSize(AValue);
    FMaxFieldDataSize := AValue;
   end;
end;

//procedure TGMOdbcRecordsetBase.SetBlobCompressionType(const Value: Integer); // TGMCompressionType
//begin
//if Value <> BlobCompressionType then
// begin
//  CheckIsInactive('BlobCompressionType ' + RStrProperty);
//  FBlobCompressionType := Value;
// end;
//end;


//
// Notify fields separate from any other Objects that are connected
// via the connection Points. If something changes controls will contact
// the fields. So the Fields should know before the controls about the Change.
//

procedure TGMOdbcRecordsetBase.AfterValueChange(const FieldName: TGMString);
begin
  GMCpcCallNotifySinks(Self, IGMNamedValueChange, GMCallSinkAfterFieldValueChange, NotifyDisableCount = 0, [FieldName]);
  if Assigned(OnAfterFieldValueChange) and DoCallEvents then try OnAfterFieldValueChange(Self, FieldName); except on E: EGMOdbcError do raise; end;
end;

procedure TGMOdbcRecordsetBase.NotifyConnectedObjectsBeforePositionChange;
begin
  inherited NotifyConnectedObjectsBeforePositionChange;

  if Assigned(OnBeforePositionChange) and DoCallEvents then OnBeforePositionChange(Self);
  GMCpcCallNotifySinks(Self, IGMPositionChangeNotifications, GMCallSinkBeforePositionChange, NotifyDisableCount = 0, []);
end;

procedure TGMOdbcRecordsetBase.NotifyBeforePositionChange;
begin
  NotifyConnectedObjectsBeforePositionChange;
  GMNotifyFieldsBeforePositionChange(FieldPosList);
  LeaveModifyingState;
end;

procedure TGMOdbcRecordsetBase.NotifyConnectedObjectsAfterPositionChange;
begin
  inherited NotifyConnectedObjectsAfterPositionChange;
  GMCpcCallNotifySinks(Self, IGMPositionChangeNotifications, GMCallSinkAfterPositionChange, NotifyDisableCount = 0, []);
  if Assigned(OnAfterPositionChange) and DoCallEvents then try OnAfterPositionChange(Self); except on E: EGMOdbcError do raise; end;
end;

procedure TGMOdbcRecordsetBase.NotifyAfterPositionChange;
begin
  GMNotifyFieldsAfterPositionChange(FieldPosList);
  NotifyConnectedObjectsAfterPositionChange;
end;

procedure TGMOdbcRecordsetBase.NotifyValidateFieldValues;
begin
  GMCpcCallNotifySinks(Self, IGMValidateValues, GMCallSinkValidateValue, NotifyDisableCount = 0, []);
  if Assigned(OnValidateFieldValues) and DoCallEvents then OnValidateFieldValues(Self);
end;

procedure TGMOdbcRecordsetBase.NotifyConnectedObjectsBeforeOperation(const Operation: Integer; const Parameter: IUnknown = nil);
begin
  if Assigned(OnBeforeOperation) and DoCallEvents then OnBeforeOperation(Self, TGMRecordsetOperation(Operation));
  GMCpcCallNotifySinks(Self, IGMOperationNotifications, GMCallSinkBeforeOperation, NotifyDisableCount = 0, [Operation, Parameter]);
end;

procedure TGMOdbcRecordsetBase.NotifyBeforeOperation(const Operation: Integer; const Parameter: IUnknown = nil);
begin
  inherited NotifyBeforeOperation(Operation, Parameter);
  NotifyConnectedObjectsBeforeOperation(Operation, Parameter);
  GMNotifyFieldsBeforeOperation(FieldPosList, Operation, Parameter);
end;

procedure TGMOdbcRecordsetBase.NotifyConnectedObjectsAfterOperation(const Operation: Integer; const Parameter: IUnknown = nil);
begin
  GMCpcCallNotifySinks(Self, IGMOperationNotifications, GMCallSinkAfterOperation, NotifyDisableCount = 0, [Operation, Parameter]);
  if Assigned(OnAfterOperation) and DoCallEvents then try OnAfterOperation(Self, TGMRecordsetOperation(Operation)); except on E: EGMOdbcError do raise; end;
end;

procedure TGMOdbcRecordsetBase.NotifyAfterOperation(const Operation: Integer; const Parameter: IUnknown = nil);
begin
  inherited NotifyAfterOperation(Operation, Parameter);
  GMNotifyFieldsAfterOperation(FieldPosList, Operation, Parameter);
  NotifyConnectedObjectsAfterOperation(Operation, Parameter);
end;

procedure TGMOdbcRecordsetBase.NotifyBeforeActiveChange(const NewActive: Boolean);
begin
  //try
   if not NewActive then LeaveModifyingState;
  //except
   //if not (csDestroying in ComponentState) then raise;
  //end;
  inherited NotifyBeforeActiveChange(NewActive);
  GMNotifyFieldsBeforeActiveChange(FieldPosList, NewActive);
end;

procedure TGMOdbcRecordsetBase.NotifyAfterActiveChange(const ANewActive: Boolean);
var i: Integer;
begin
  if ANewActive then
   begin
    for i:=0 to FieldPosList.Count-1 do (FieldPosList[i] as TGMOdbcField).BindBuffer(True); // <- Some drivers need a buffer before fetching
    DoCursorFetch(SQL_FETCH_NEXT, 0, {$I %CurrentRoutine%}, False);
    FPosition := 1; // <- Have Position 1 even when empty
   end;

  GMNotifyFieldsAfterActiveChange(FieldPosList, ANewActive); // <- must be done after cursor fetch, will ask the cursor state
  inherited NotifyAfterActiveChange(ANewActive);
end;


{ -------------------------- }
{ ---- TGMOdbcRecordset ---- }
{ -------------------------- }

constructor TGMOdbcRecordset.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);

  FMasterSource := TGMRecordsetMasterSource.Create(Self, []);
  MasterSource.OnAfterActiveChange := AfterMasterActiveChange;
  MasterSource.OnAfterPositionChange := AfterMasterPositionChange;
  MasterSource.OnAfterOperation := AfterMasterOperation;
  MasterSource.OnAfterFieldValueChange := AfterMasterFieldValueChange;
end;

destructor TGMOdbcRecordset.Destroy;
begin
  inherited Destroy;
  GMFreeAndNil(FMasterSource);
end;

//procedure TGMOdbcRecordset.CheckSQLStatementText(const SQL: TGMString);
//begin
//GMCheckSQLNotEmpty(SQL, Self, {$I %CurrentRoutine%});
////if not IsSelectSQL(SQL) then raise EGMException.ObjError(RStrNoModfifiyngSQL, Self, {$I %CurrentRoutine%});
//end;

procedure TGMOdbcRecordset.Insert(const Parameter: IUnknown = nil);
begin
  inherited Insert(Parameter);
  AssignFieldValuesFromMasterSource;
end;

procedure TGMOdbcRecordset.OpenMasterSource(const MethodName: TGMString);
begin
  GMSetIntfActive(MasterSource.InterfaceSource, True, MethodName);
end;

procedure TGMOdbcRecordset.EnumerateItems(const ItemKind: LongInt; const TellEnumSink: IUnknown; const Parameter: Pointer);
var PIMasterActive: IUnknown; PIEnumSink: IGMTellEnumString;
begin
  if GMQueryInterface(TellEnumSink, IGMTellEnumString, PIEnumSink) then
   case ItemKind of
    Ord(eidFieldNames): PIMasterActive := TGMActiveKeeper.Create(MasterSource.InterfaceSource, True);
   end;

  inherited EnumerateItems(ItemKind, TellEnumSink, Parameter);
end;

procedure TGMOdbcRecordset.AssignMasterParamValues;
var i: Integer; PIFieldByName: IGMGetIntfByName; PIGetValue: IGMGetUnionValue;
begin
  if MasterSource.InterfaceSource <> nil then
   begin
    OpenMasterSource({$I %CurrentRoutine%});
    if GMQueryInterface(MasterSource.InterfaceSource, IGMGetIntfByName, PIFieldByName) then
     for i:=0 to SQL.SQLParameter.Count-1 do
      if PIFieldByName.GetIntfByName(SQL.SQLParameter[i].Name, IGMGetUnionValue, PIGetValue) = S_OK then
       SQL.SQLParameter[i].AssignValue(PIGetValue.Value, False); // <- AssignFromObj without triggering ScheduleReExecution (again)
   end;
end;

procedure TGMOdbcRecordset.AssignFieldValuesFromMasterSource;
var ChPos, StartPos: PtrInt; Token, SQLText: TGMString; PIFieldValue: IGMGetSetUnionValue;
begin
  if MasterSource.InterfaceSource <> nil then
   begin
    OpenMasterSource({$I %CurrentRoutine%});

    SQLText := SQL.SQLText;
    ChPos := 1;
    while ChPos <= Length(SQLText) do
     begin
      StartPos := ChPos-1;
      Token := GMNextSQLToken(ChPos, SQLText, cSqlSeparators + cSqlOperators);
      if (Length(Token) > 1) and (Token[1] = cSqlParamPrefixChar) and
         (GetIntfByName(GMPreviousWord(StartPos, SQLText, cSqlSeparators + cSqlOperators), IGMGetSetUnionValue, PIFieldValue) = S_OK) then
       PIFieldValue.Value := GMCheckGetItemValue(MasterSource.InterfaceSource, GMStrip(Token, cSqlParamPrefixChar + cSqlSeparators + cSqlOperators), {$I %CurrentRoutine%});
     end;
   end;
end;

procedure TGMOdbcRecordset.IDESetMasterSource(const AValue: TGMRecordsetMasterSource);
begin
  MasterSource.AssignFromObj(AValue);
end;

function TGMOdbcRecordset.GetMasterSource: IUnknown;
begin
  Result := MasterSource.InterfaceSource;
end;

procedure TGMOdbcRecordset.SetMasterSource(const AValue: IUnknown);
begin
  MasterSource.InterfaceSource := AValue;
end;

function TGMOdbcRecordset.GetResolvedSQLStatement: TGMString;
begin
  AssignMasterParamValues;
  Result := inherited GetResolvedSQLStatement;
end;

procedure TGMOdbcRecordset.AfterMasterActiveChange(const ANewActive: Boolean);
begin
  if not MasterSource.AutoActivate then Exit;
  if ANewActive then ScheduleReexecution else Close;
end;

procedure TGMOdbcRecordset.AfterMasterPositionChange;
begin
  ScheduleReexecution;
end;

procedure TGMOdbcRecordset.AfterMasterOperation(const AOperation: LongInt; const AParameter: IUnknown = nil);
begin
  case AOperation of
   Ord(roInsert), Ord(roCancelChanges), {Ord(roApplychanges),} Ord(roRefreshCurrent), Ord(roReExecuteStatement): ScheduleReExecution;
  end;
end;

procedure TGMOdbcRecordset.AfterMasterFieldValueChange(ASender: IUnknown; const AFieldName: TGMString);
var i: Integer;
begin
  for i:=0 to SQL.SQLParameter.Count-1 do
   if GMSameText(SQL.SQLParameter[i].Name, AFieldName) then
    begin ScheduleReExecution; Break; end;
end;


{ ----------------------------- }
{ ---- TGMOdbcGroupSlaveRS ---- }
{ ----------------------------- }

constructor TGMOdbcGroupSlaveRS.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  MasterSource.AlwaysNotify := True;
end;

procedure TGMOdbcGroupSlaveRS.ScheduleReExecution(const ColumnsStayValid: Boolean = True);
begin
  ReExecuteStatement(ColumnsStayValid);
end;


{ -------------------------------- }
{ ---- TGMOdbcSchemaRecordset ---- }
{ -------------------------------- }

constructor TGMOdbcSchemaRecordset.Create(const ARefLifeTime: Boolean);
begin
  inherited Create(ARefLifeTime);
  FSchemaData := TGMSchemaProperties.Create(Self);
  SchemaData.OnAfterSchemaDataChange := AfterSchemaDataChange;
end;

destructor TGMOdbcSchemaRecordset.Destroy;
begin
  inherited Destroy;
  GMFreeAndNil(FSchemaData);
end;

//function TGMOdbcSchemaRecordset.Obj: TGMOdbcSchemaRecordset;
//begin
//  Result := Self;
//end;

//function TGMOdbcSchemaRecordset.GetPropertyIntf(const PropertyName: TGMString; const IID: TGUID; out Intf): HResult;
//begin
//  if GMSameText(PropertyName, cStrSchemaData) then
//   Result := SchemaData.QueryInterface(IID, Intf)
//  else
//   Result := inherited GetPropertyIntf(PropertyName, IID, Intf);
//end;

procedure TGMOdbcSchemaRecordset.AssignFromObj(const ASource: TObject);
begin
  inherited AssignFromObj(ASource);
  SchemaData.AssignFromObj(ASource);
  //if ASource is TGMOdbcSchemaRecordset then SchemaData := TGMOdbcSchemaRecordset(ASource).SchemaData;
end;

//procedure TGMOdbcSchemaRecordset.SetSchemaData(const Value: TGMSchemaProperties);
//begin
//  SchemaData.AssignFromObj(Value);
//end;

//function TGMOdbcSchemaRecordset.GetText: TGMString;
//begin
//  Result := SchemaData.GetText;
//end;

procedure TGMOdbcSchemaRecordset.AfterSchemaDataChange(const Sender: TObject);
begin
  NotifyAfterSQLChange;
end;

//function TGMOdbcSchemaRecordset.GetODBCFieldCount: SQLSMALLINT;
//begin
//case SchemaData.SchemaList of
// slSystemTables, slTables, slViews: Result := 5; 
// else Result := inherited GetODBCFieldCount;
//end;
//end;

procedure TGMOdbcSchemaRecordset.InternalExecute;
var pszCatalogName, pszSchemaName, pszTableName, pszTableKind, pszColumnName, pszForeignCatalogName, pszForeignSchemaName, pszForeignTableName: SQLPCHAR;
    ccDatabaseNameLen, ccSchemaNameLen, ccTableNameLen, ccTableKindLen, ccColumnNameLen, ccForeignCatalogNameLen, ccForeignSchemaNameLen, ccForeignTableNameLen: SQLSMALLINT;
  procedure AssignString(const AValue: TGMString; var APszFilterStr: SQLPCHAR; var AFilterStrLen: SQLSMALLINT);
  begin
    if GMSameText(AValue, cStrNil) then
     begin APszFilterStr := nil; AFilterStrLen := 0; end
    else
     begin APszFilterStr := PGMChar(AValue); AFilterStrLen := Length(AValue); end;
  end;
  procedure OpenCheck(const SQLCode: SQLRETURN; const RoutineName: TGMString);
  begin
    if SQLCode <> SQL_NO_DATA then ODBCCheck(SQLCode, Self, RoutineName);
  end;
begin
  AssignString(SchemaData.CatalogName, pszCatalogName, ccDatabaseNameLen);
  AssignString(SchemaData.SchemaName, pszSchemaName, ccSchemaNameLen);
  AssignString(SchemaData.TableName, pszTableName, ccTableNameLen);
  AssignString(SchemaData.ColumnName, pszColumnName, ccColumnNameLen);

  AssignString(SchemaData.ForeignCatalogName, pszForeignCatalogName, ccForeignCatalogNameLen);
  AssignString(SchemaData.ForeignSchemaName, pszForeignSchemaName, ccForeignSchemaNameLen);
  AssignString(SchemaData.ForeignTableName, pszForeignTableName, ccForeignTableNameLen);

  case SchemaData.SchemaList of
   slTables:
    begin
//   case SchemaData.SchemaList of
//    slSystemTables: AssignString(cStrSysTable, pszTableKind, ccTableKindLen);
//    slTables:       AssignString(cStrTable, pszTableKind, ccTableKindLen);
//    slViews:        AssignString(cStrView, pszTableKind, ccTableKindLen);
//    else            AssignString(cStrNil, pszTableKind, ccTableKindLen);
//   end;
     AssignString(SchemaData.TableKind, pszTableKind, ccTableKindLen);

     OpenCheck(SQLTables(Handle, pszCatalogName, ccDatabaseNameLen,
                                 pszSchemaName, ccSchemaNameLen,
                                 pszTableName, ccTableNameLen,
                                 pszTableKind, ccTableKindLen), cStrSQLTables);
    end;

   slPrimaryKeys:
    OpenCheck(SQLPrimaryKeys(Handle, pszCatalogName, ccDatabaseNameLen,
                                     pszSchemaName, ccSchemaNameLen,
                                     pszTableName, ccTableNameLen), cStrSQLPrimaryKeys);
   slColumns:
    OpenCheck(SQLColumns(Handle, pszCatalogName, ccDatabaseNameLen,
                                 pszSchemaName, ccSchemaNameLen,
                                 pszTableName, ccTableNameLen,
                                 pszColumnName, ccColumnNameLen), cStrSQLColumns);
   slStatistics:
    OpenCheck(SQLStatistics(Handle, pszCatalogName, ccDatabaseNameLen,
                                    pszSchemaName, ccSchemaNameLen,
                                    pszTableName, ccTableNameLen,
                                    SQL_INDEX_ALL, SQL_QUICK), cStrSQLStatistics);
   slProcedures:
    OpenCheck(SQLProcedures(Handle, pszCatalogName, ccDatabaseNameLen,
                                    pszSchemaName, ccSchemaNameLen,
                                    pszTableName, ccTableNameLen), cStrSQLProcedures);
   slProcedureColumns:
    OpenCheck(SQLProcedureColumns(Handle, pszCatalogName, ccDatabaseNameLen,
                                          pszSchemaName, ccSchemaNameLen,
                                          pszTableName, ccTableNameLen,
                                          pszColumnName, ccColumnNameLen), cStrSQLProcedureColumns);
   slForeignKeys:
    OpenCheck(SQLForeignKeys(Handle, pszCatalogName, ccDatabaseNameLen,
                                     pszSchemaName, ccSchemaNameLen,
                                     pszTableName, ccTableNameLen,
                                     pszForeignCatalogName, ccForeignCatalogNameLen,
                                     pszForeignSchemaName, ccForeignSchemaNameLen,
                                     pszForeignTableName, ccForeignTableNameLen), cStrSQLForeignKeys);
   slTablePrivileges:
    OpenCheck(SQLTablePrivileges(Handle, pszCatalogName, ccDatabaseNameLen,
                                         pszSchemaName, ccSchemaNameLen,
                                         pszTableName, ccTableNameLen), cStrSQLTablePrivileges);
   slColumnPrivileges:
    OpenCheck(SQLColumnPrivileges(Handle, pszCatalogName, ccDatabaseNameLen,
                                          pszSchemaName, ccSchemaNameLen,
                                          pszTableName, ccTableNameLen,
                                          pszColumnName, ccColumnNameLen), cStrSQLColumnPrivileges);

   slTypeInfo:
    OpenCheck(SQLGetTypeInfo(Handle, GMStrToInt(GMMakeDezInt(SchemaData.ColumnName, SQL_UNKNOWN_TYPE))), cStrSQLGetTypeInfo);

   else raise EGMException.ObjError(MsgUnsupportedValue(cStrSchemaList, Ord(SchemaData.SchemaList)), Self, {$I %CurrentRoutine%});
  end;
end;


initialization

  vSQLDatTimeFmtStr := cODBCDateTimeFormatStrMS;

end.