////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// DataBase API constants. Derived from ADO etc.                              //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20221031 - 1st release                                                     //
// 20221119 - AdoFieldTypeToString                                            //
// 20230826 - Modified for FPC (A.K.)                                         //
// 20250625 - DbEventTypeToString, DbFieldTypeCodeToString, etc               //
// 20250718 - TDbEngineAssistant                                              //
////////////////////////////////////////////////////////////////////////////////

unit _crw_dbcon; // DataBase Constants

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$WARN 5023 off : Unit "$1" not used in $2}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, math, strutils, db, sqltypes, sqldb, bufdataset,
 _crw_alloc, _crw_ef, _crw_rtc, _crw_str, _crw_base64, _crw_environ,
 _crw_fio, _crw_crypt, _crw_uri, _crw_dbglog;

const //////////////////// ADO DB constants; ADO = ActiveX Data Objects
 db_engine_code_ado      = 1;       // ADO engine code
 db_engine_name_ado      = 'ADO';   // ADO engine name

const //////////////////// SQLDB constants; SQLDB = default Lazarus DB engine
 db_engine_code_sqldb    = 2;       // SQLDB engine code
 db_engine_name_sqldb    = 'SQLDB'; // SQLDB engine name

const //////////////////// ZEOS constants; ZEOS = ZEOSDBO engine (need install)
 db_engine_code_zeos     = 3;       // ZEOS engine code
 db_engine_name_zeos     = 'ZEOS';  // ZEOS engine name

////////////////////////////////////////////////////////////////////////////////
// ADO constants derived from AdoInt.pas with next command:
// type "c:\Program Files\Borland\Delphi5\Source\Vcl\adoint.pas" ^
//   | unix grep -i -e '^\s*ad[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*[\$0-9\-]'  ^
//   | unix gawk '{printf " %%-30s %%s %%-10s\n", $1, $2, $3 }' ^
//   | unix u2d
////////////////////////////////////////////////////////////////////////////////

///////////////////////
// AdoInt.pas constants
///////////////////////
const
 ///////////////////////////////// CursorTypeEnum constants
 adOpenUnspecified              = -1;
 adOpenForwardOnly              = $00000000;
 adOpenKeyset                   = $00000001;
 adOpenDynamic                  = $00000002;
 adOpenStatic                   = $00000003;
 //////////////////////////////// CursorOptionEnum constants
 adHoldRecords                  = $00000100;
 adMovePrevious                 = $00000200;
 adAddNew                       = $01000400;
 adDelete                       = $01000800;
 adUpdate                       = $01008000;
 adBookmark                     = $00002000;
 adApproxPosition               = $00004000;
 adUpdateBatch                  = $00010000;
 adResync                       = $00020000;
 adNotify                       = $00040000;
 adFind                         = $00080000;
 adSeek                         = $00400000;
 adIndex                        = $00800000;
 //////////////////////////////// LockTypeEnum constants
 adLockUnspecified              = -1;
 adLockReadOnly                 = $00000001;
 adLockPessimistic              = $00000002;
 adLockOptimistic               = $00000003;
 adLockBatchOptimistic          = $00000004;
 //////////////////// ExecuteOptionEnum constants
 adOptionUnspecified            = -1;
 adAsyncExecute                 = $00000010;
 adAsyncFetch                   = $00000020;
 adAsyncFetchNonBlocking        = $00000040;
 adExecuteNoRecords             = $00000080;
 //////////////////////////////// ConnectOptionEnum constants
 adConnectUnspecified           = -1;
 adAsyncConnect                 = $00000010;
 //////////////////////////////// ObjectStateEnum constants
 adStateClosed                  = $00000000;
 adStateOpen                    = $00000001;
 adStateConnecting              = $00000002;
 adStateExecuting               = $00000004;
 adStateFetching                = $00000008;
 /////////////////////////////// CursorLocationEnum constants
 adUseNone                      = $00000001;
 adUseServer                    = $00000002;
 adUseClient                    = $00000003;
 adUseClientBatch               = $00000003;
 //////////////////////////////// DataTypeEnum constants
 adEmpty                        = $00000000; // 0   No value
 adTinyInt                      = $00000010; // 16  A 1-byte signed integer.
 adSmallInt                     = $00000002; // 2   A 2-byte signed integer.
 adInteger                      = $00000003; // 3   A 4-byte signed integer.
 adBigInt                       = $00000014; // 20  An 8-byte signed integer.
 adUnsignedTinyInt              = $00000011; // 17  A 1-byte unsigned integer.
 adUnsignedSmallInt             = $00000012; // 18  A 2-byte unsigned integer.
 adUnsignedInt                  = $00000013; // 19  A 4-byte unsigned integer.
 adUnsignedBigInt               = $00000015; // 21  An 8-byte unsigned integer.
 adSingle                       = $00000004; // 4   A single-precision floating-point value.
 adDouble                       = $00000005; // 5   A double-precision floating-point value.
 adCurrency                     = $00000006; // 6   A currency value
 adDecimal                      = $0000000E; // 14  An exact numeric value with a fixed precision and scale.
 adNumeric                      = $00000083; // 131 An exact numeric value with a fixed precision and scale.
 adBoolean                      = $0000000B; // 11  A boolean value.
 adError                        = $0000000A; // 10  A 32-bit error code
 adUserDefined                  = $00000084; // 132 A user-defined variable.
 adVariant                      = $0000000C; // 12  An Automation Variant. Note: Currently not supported by ADO.
 adIDispatch                    = $00000009; // 9   A pointer to an IDispatch interface on a COM object. Note: Currently not supported by ADO.
 adIUnknown                     = $0000000D; // 13  A pointer to an IUnknown interface on a COM object. Note: Currently not supported by ADO.
 adGUID                         = $00000048; // 72  A globally unique identifier (GUID)
 adDate                         = $00000007; // 7   The number of days since December 30, 1899 + the fraction of a day.
 adDBDate                       = $00000085; // 133 A date value (yyyymmdd).
 adDBTime                       = $00000086; // 134 A time value (hhmmss).
 adDBTimeStamp                  = $00000087; // 135 A date/time stamp (yyyymmddhhmmss plus a fraction in billionths).
 adBSTR                         = $00000008; // 8   A null-terminated character string.
 adChar                         = $00000081; // 129 A string value.
 adVarChar                      = $000000C8; // 200 A string value (Parameter object only).
 adLongVarChar                  = $000000C9; // 201 A long string value.
 adWChar                        = $00000082; // 130 A null-terminated Unicode character string.
 adVarWChar                     = $000000CA; // 202 A null-terminated Unicode character string.
 adLongVarWChar                 = $000000CB; // 203 A long null-terminated Unicode string value.
 adBinary                       = $00000080; // 128 A binary value.
 adVarBinary                    = $000000CC; // 204 A binary value (Parameter object only).
 adLongVarBinary                = $000000CD; // 205 A long binary value.
 adChapter                      = $00000088; // 136 A 4-byte chapter value that identifies rows in a child rowset
 adFileTime                     = $00000040; // 64  The number of 100-nanosecond intervals since January 1,1601
 adDBFileTime                   = $00000089; // 137 Database file time
 adPropVariant                  = $0000008A; // 138 An Automation PROPVARIANT.
 adVarNumeric                   = $0000008B; // 139 A numeric value (Parameter object only).
 adArray                        = $00002000; // 0x2000 A flag value combined with another data type constant. Indicates an array of that other data type.
 //////////////////////////////// FieldAttributeEnum constants
 adFldUnspecified               = -1;
 adFldMayDefer                  = $00000002;
 adFldUpdatable                 = $00000004;
 adFldUnknownUpdatable          = $00000008;
 adFldFixed                     = $00000010;
 adFldIsNullable                = $00000020;
 adFldMayBeNull                 = $00000040;
 adFldLong                      = $00000080;
 adFldRowID                     = $00000100;
 adFldRowVersion                = $00000200;
 adFldCacheDeferred             = $00001000;
 adFldNegativeScale             = $00004000;
 adFldKeyColumn                 = $00008000;
 //////////////////////////////// EditModeEnum constants
 adEditNone                     = $00000000;
 adEditInProgress               = $00000001;
 adEditAdd                      = $00000002;
 adEditDelete                   = $00000004;
 //////////////////////////////// RecordStatusEnum constants
 adRecOK                        = $00000000;
 adRecNew                       = $00000001;
 adRecModified                  = $00000002;
 adRecDeleted                   = $00000004;
 adRecUnmodified                = $00000008;
 adRecInvalid                   = $00000010;
 adRecMultipleChanges           = $00000040;
 adRecPendingChanges            = $00000080;
 adRecCanceled                  = $00000100;
 adRecCantRelease               = $00000400;
 adRecConcurrencyViolation      = $00000800;
 adRecIntegrityViolation        = $00001000;
 adRecMaxChangesExceeded        = $00002000;
 adRecObjectOpen                = $00004000;
 adRecOutOfMemory               = $00008000;
 adRecPermissionDenied          = $00010000;
 adRecSchemaViolation           = $00020000;
 adRecDBDeleted                 = $00040000;
 //////////////////////////////// GetRowsOptionEnum constants
 adGetRowsRest                  = -1;
 //////////////////////////////// PositionEnum constants
 adPosUnknown                   = -1;       
 adPosBOF                       = $FFFFFFFE;
 adPosEOF                       = $FFFFFFFD;
 //////////////////////////////// BookmarkEnum constants
 adBookmarkCurrent              = $00000000;
 adBookmarkFirst                = $00000001;
 adBookmarkLast                 = $00000002;
 //////////////////////////////// MarshalOptionsEnum constants
 adMarshalAll                   = $00000000;
 adMarshalModifiedOnly          = $00000001;
 //////////////////////////////// AffectEnum constants
 adAffectCurrent                = $00000001;
 adAffectGroup                  = $00000002;
 adAffectAll                    = $00000003;
 adAffectAllChapters            = $00000004;
 //////////////////////////////// ResyncEnum constants
 adResyncUnderlyingValues       = $00000001;
 adResyncAllValues              = $00000002;
 //////////////////////////////// CompareEnum constants
 adCompareLessThan              = $00000000;
 adCompareEqual                 = $00000001;
 adCompareGreaterThan           = $00000002;
 adCompareNotEqual              = $00000003;
 adCompareNotComparable         = $00000004;
 //////////////////////////////// FilterGroupEnum constants
 adFilterNone                   = $00000000;
 adFilterPendingRecords         = $00000001;
 adFilterAffectedRecords        = $00000002;
 adFilterFetchedRecords         = $00000003;
 adFilterPredicate              = $00000004;
 adFilterConflictingRecords     = $00000005;
 //////////////////////////////// SearchDirectionEnum constants
 adSearchForward                = $00000001;
 adSearchBackward               = -1;
 //////////////////////////////// PersistFormatEnum constants
 adPersistADTG                  = $00000000;
 adPersistXML                   = $00000001;
 //////////////////////////////// StringFormatEnum constants
 adClipString                   = $00000002;
 //////////////////////////////// ConnectPromptEnum constants
 adPromptAlways                 = $00000001;
 adPromptComplete               = $00000002;
 adPromptCompleteRequired       = $00000003;
 adPromptNever                  = $00000004;
 //////////////////////////////// ConnectModeEnum constants
 adModeUnknown                  = $00000000;
 adModeRead                     = $00000001;
 adModeWrite                    = $00000002;
 adModeReadWrite                = $00000003;
 adModeShareDenyRead            = $00000004;
 adModeShareDenyWrite           = $00000008;
 adModeShareExclusive           = $0000000C;
 adModeShareDenyNone            = $00000010;
 //////////////////////////////// IsolationLevelEnum constants
 adXactUnspecified              = -1;
 adXactChaos                    = $00000010;
 adXactReadUncommitted          = $00000100;
 adXactBrowse                   = $00000100;
 adXactCursorStability          = $00001000;
 adXactReadCommitted            = $00001000;
 adXactRepeatableRead           = $00010000;
 adXactSerializable             = $00100000;
 adXactIsolated                 = $00100000;
 //////////////////////////////// XactAttributeEnum constants
 adXactCommitRetaining          = $00020000;
 adXactAbortRetaining           = $00040000;
 adXactAsyncPhaseOne            = $00080000;
 adXactSyncPhaseOne             = $00100000;
 //////////////////////////////// PropertyAttributesEnum constants
 adPropNotSupported             = $00000000;
 adPropRequired                 = $00000001;
 adPropOptional                 = $00000002;
 adPropRead                     = $00000200;
 adPropWrite                    = $00000400;
 //////////////////////////////// ErrorValueEnum constants
 adErrInvalidArgument           = $00000BB9;
 adErrNoCurrentRecord           = $00000BCD;
 adErrIllegalOperation          = $00000C93;
 adErrInTransaction             = $00000CAE;
 adErrFeatureNotAvailable       = $00000CB3;
 adErrItemNotFound              = $00000CC1;
 adErrObjectInCollection        = $00000D27;
 adErrObjectNotSet              = $00000D5C;
 adErrDataConversion            = $00000D5D;
 adErrObjectClosed              = $00000E78;
 adErrObjectOpen                = $00000E79;
 adErrProviderNotFound          = $00000E7A;
 adErrBoundToCommand            = $00000E7B;
 adErrInvalidParamInfo          = $00000E7C;
 adErrInvalidConnection         = $00000E7D;
 adErrNotReentrant              = $00000E7E;
 adErrStillExecuting            = $00000E7F;
 adErrOperationCancelled        = $00000E80;
 adErrStillConnecting           = $00000E81;
 adErrNotExecuting              = $00000E83;
 adErrUnsafeOperation           = $00000E84;
 //////////////////////////////// ParameterAttributesEnum constants
 adParamSigned                  = $00000010;
 adParamNullable                = $00000040;
 adParamLong                    = $00000080;
 //////////////////////////////// ParameterDirectionEnum constants
 adParamUnknown                 = $00000000;
 adParamInput                   = $00000001;
 adParamOutput                  = $00000002;
 adParamInputOutput             = $00000003;
 adParamReturnValue             = $00000004;
 //////////////////////////////// CommandTypeEnum constants
 adCmdUnspecified               = -1;       
 adCmdUnknown                   = $00000008;
 adCmdText                      = $00000001;
 adCmdTable                     = $00000002;
 adCmdStoredProc                = $00000004;
 adCmdFile                      = $00000100;
 adCmdTableDirect               = $00000200;
 //////////////////////////////// EventStatusEnum constants
 adStatusOK                     = $00000001;
 adStatusErrorsOccurred         = $00000002;
 adStatusCantDeny               = $00000003;
 adStatusCancel                 = $00000004;
 adStatusUnwantedEvent          = $00000005;
 //////////////////////////////// EventReasonEnum constants
 adRsnAddNew                    = $00000001;
 adRsnDelete                    = $00000002;
 adRsnUpdate                    = $00000003;
 adRsnUndoUpdate                = $00000004;
 adRsnUndoAddNew                = $00000005;
 adRsnUndoDelete                = $00000006;
 adRsnRequery                   = $00000007;
 adRsnResynch                   = $00000008;
 adRsnClose                     = $00000009;
 adRsnMove                      = $0000000A;
 adRsnFirstChange               = $0000000B;
 adRsnMoveFirst                 = $0000000C;
 adRsnMoveNext                  = $0000000D;
 adRsnMovePrevious              = $0000000E;
 adRsnMoveLast                  = $0000000F;
 //////////////////////////////// SchemaEnum constants
 adSchemaProviderSpecific       = -1;       
 adSchemaAsserts                = $00000000;
 adSchemaCatalogs               = $00000001;
 adSchemaCharacterSets          = $00000002;
 adSchemaCollations             = $00000003;
 adSchemaColumns                = $00000004;
 adSchemaCheckConstraints       = $00000005;
 adSchemaConstraintColumnUsage  = $00000006;
 adSchemaConstraintTableUsage   = $00000007;
 adSchemaKeyColumnUsage         = $00000008;
 adSchemaReferentialConstraints = $00000009;
 adSchemaTableConstraints       = $0000000A;
 adSchemaColumnsDomainUsage     = $0000000B;
 adSchemaIndexes                = $0000000C;
 adSchemaColumnPrivileges       = $0000000D;
 adSchemaTablePrivileges        = $0000000E;
 adSchemaUsagePrivileges        = $0000000F;
 adSchemaProcedures             = $00000010;
 adSchemaSchemata               = $00000011;
 adSchemaSQLLanguages           = $00000012;
 adSchemaStatistics             = $00000013;
 adSchemaTables                 = $00000014;
 adSchemaTranslations           = $00000015;
 adSchemaProviderTypes          = $00000016;
 adSchemaViews                  = $00000017;
 adSchemaViewColumnUsage        = $00000018;
 adSchemaViewTableUsage         = $00000019;
 adSchemaProcedureParameters    = $0000001A;
 adSchemaForeignKeys            = $0000001B;
 adSchemaPrimaryKeys            = $0000001C;
 adSchemaProcedureColumns       = $0000001D;
 adSchemaDBInfoKeywords         = $0000001E;
 adSchemaDBInfoLiterals         = $0000001F;
 adSchemaCubes                  = $00000020;
 adSchemaDimensions             = $00000021;
 adSchemaHierarchies            = $00000022;
 adSchemaLevels                 = $00000023;
 adSchemaMeasures               = $00000024;
 adSchemaProperties             = $00000025;
 adSchemaMembers                = $00000026;
 adSchemaTrustees               = $00000027;
 //////////////////////////////// SeekEnum constants
 adSeekFirstEQ                  = $00000001;
 adSeekLastEQ                   = $00000002;
 adSeekAfterEQ                  = $00000004;
 adSeekAfter                    = $00000008;
 adSeekBeforeEQ                 = $00000010;
 adSeekBefore                   = $00000020;
 //////////////////////////////// ADCPROP_UPDATECRITERIA_ENUM constants
 adCriteriaKey                  = $00000000;
 adCriteriaAllCols              = $00000001;
 adCriteriaUpdCols              = $00000002;
 adCriteriaTimeStamp            = $00000003;
 //////////////////////////////// ADCPROP_ASYNCTHREADPRIORITY_ENUM constants
 adPriorityLowest               = $00000001;
 adPriorityBelowNormal          = $00000002;
 adPriorityNormal               = $00000003;
 adPriorityAboveNormal          = $00000004;
 adPriorityHighest              = $00000005;
 //////////////////////////////// CEResyncEnum constants
 adResyncNone                   = $00000000;
 adResyncAutoIncrement          = $00000001;
 adResyncConflicts              = $00000002;
 adResyncUpdates                = $00000004;
 adResyncInserts                = $00000008;
 adResyncAll                    = $0000000F;
 //////////////////////////////// ADCPROP_AUTORECALC_ENUM constants
 adRecalcUpFront                = $00000000;
 adRecalcAlways                 = $00000001;

const // ADO data types
 adScalarFieldTypes = [adEmpty,adTinyInt,adSmallInt,adInteger,adBigInt,
 adUnsignedTinyInt,adUnsignedSmallInt,adUnsignedInt, adUnsignedBigInt,
 adSingle,adDouble,adCurrency,adDecimal,adNumeric,adBoolean,adError,
 adUserDefined,adVariant,adIDispatch,adIUnknown,adGUID,adDate,
 adDBDate,adDBTime,adDBTimeStamp,adBSTR,adChar,adVarChar,adLongVarChar,
 adWChar,adVarWChar,adLongVarWChar,adBinary,adVarBinary,adLongVarBinary,
 adChapter,adFileTime,adDBFileTime,adPropVariant,adVarNumeric];

const // Data Format for Save
 dfBinary  = BufDataset.dfBinary;
 dfXML     = BufDataset.dfXML;
 dfXMLUTF8 = BufDataset.dfXMLUTF8;
 dfAny     = BufDataset.dfAny;
 dfDefault = BufDataset.dfDefault;

const                                     // PasswordEncriptMode:
 pem_NONE     = 0;                        // None     encription uses
 pem_Blowfish = 1;                        // Blowfish encription uses
 pem_GOST     = 2;                        // GOST     encription uses
 pem_RC2      = 3;                        // RC2      encription uses
 pem_RC4      = 4;                        // RC4      encription uses
 pem_RC5      = 5;                        // RC5      encription uses
 pem_RC6      = 6;                        // RC6      encription uses
 pem_B64      = 7;                        // Base64   encoding   uses
 pem_HEX      = 8;                        // HEX      encoding   uses
 pem_OFF      = 9;                        // Encription is OFF = NONE
 pem_MIN      = pem_NONE;                 // Minimal encription mode
 pem_MAX      = pem_OFF;                  // Maximal encription mode
 pem_Secure   = [pem_Blowfish..pem_HEX];  // Who uses any encription
type
 TPwdEncryptMode = pem_MIN..pem_MAX;

const // Default ConnOptions for GetSQLStatementInfo
 DefGsiConnOptions=[sqEscapeRepeat,sqEscapeSlash];

 //////////////////////////////////////////////////////////////////////
 // TDbConverter encapsulate all DataBase related conversion functions.
 // The DbCon is the only one instance of TDbConverter.
 //////////////////////////////////////////////////////////////////////
type
 TDbConverter = class(TMasterObject)
 private
  myDbFieldTypeMap:TStringList;
 protected
  function DbFieldTypeMap:TStringList;
 public
  constructor Create;
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  class var UsesSystemDbFieldTypeNames:Boolean;
 public // Convert DB event type to string like detExecute.
  function EventTypeToString(EventType:TDbEventType):LongString;
 public // Convert dataset state to string like dsInactive.
  function DataSetStateToString(const State:TDataSetState):LongString;
 public // Convert DB schema type to string like stTables.
  function SchemaTypeToString(SchemaType:TSchemaType):LongString;
 public // Convert SQL statment type to string like stSelect.
  function StatementTypeToString(st:TStatementType):LongString;
 public // Convert connection options to string.
  function ConnOptionToString(Opt:TConnOption):LongString;
  function ConnOptionsToString(const Opts:TConnOptions; Sep:Char=','):LongString;
 public // Convert SQL connection options to string.
  function SQLConnectionOptionToString(opt:TSQLConnectionOption):LongString;
  function SQLConnectionOptionsToString(opts:TSQLConnectionOptions; Sep:Char=','):LongString;
  function StringToSQLConnectionOptions(arg:LongString):TSQLConnectionOptions;
 public // Convert SQL query options to string.
  function SQLQueryOptionToString(opt:TSQLQueryOption):LongString;
  function SQLQueryOptionsToString(opts:TSQLQueryOptions; Sep:Char=','):LongString;
  function StringToSQLQueryOptions(arg:LongString):TSQLQueryOptions;
 public // Convert SQL query update mode to string.
  function UpdateModeToString(opt:TUpdateMode):LongString;
  function StringToUpdateMode(arg:LongString; def:TUpdateMode=upWhereKeyOnly):TUpdateMode;
 public // Convert ADO field type to string like adInteger.
  function AdoFieldTypeToString(FieldType:Integer):LongString;
 public // Convert SQLDB field type to string like ftInteger.
  function SqlDbFieldTypeToString(FieldType:TFieldType):LongString;
  function XMLFieldTypeToString(FieldType:TFieldType):LongString;
 public // Convert string to DB field type integer code or return default.
        // String must to have prefix like adInteger (ADO), ftInteger (SQDB).
  function StringToFieldTypeCode(const S:LongString; Def:Integer=-1):Integer;
 public // Convert DB field type integer code to string.
        // Return string like adInteger or ftInteger depending on Engine (ADO,SQLDB).
  function FieldTypeCodeToString(Code:Integer; Engine:Integer):LongString;
 public // Convert integer code to TFieldType (with range checking).
  function CodeToFieldType(Code:Integer; Def:TFieldType=ftUnknown):TFieldType;
 public // DateTime converters
  function IsDateTimeFieldTypeCode(typ,Engine:Integer):Boolean;
  function AdoDateTimeToMs(tm:Double; typ:Integer):Double;
  function SqlDbDateTimeToMs(tm:Double; typ:Integer):Double;
  function DbDateTimeToMs(tm:Double; typ,Engine:Integer):Double;
 public // BLOB functions
  function IsBlobFieldTypeCode(typ,Engine:Integer):Boolean;
  function DetectBlobImageType(const Blob:LongString):LongString;
 end;

function DbCon:TDbConverter; // The only one DB converter.

 /////////////////////////////////////////////////////////////////
 // TDbEngineAssistant - assistant class for database connections.
 // Purposes: to check, parse, validate parameters of connections.
 /////////////////////////////////////////////////////////////////
type
 TDbEngineAssistant = class(TMasterObject)
 public // Parse connection string parameters
  class function FetchParam(const Buff,Items:LongString):LongString;
  class function SubstParam(Buff,Items,Value:LongString; NMax:Integer=1; NoEmpty:Boolean=False):LongString;
  class function DropParam(Buff,Items:LongString):LongString;
  class function ValidateParams(const aBuff,aIncl,aExcl:LongString;
                                const aDelim:LongString=EOL):LongString;
 public // Parse SQL expressions
  class function StrToStatementType(const S:LongString):TStatementType;
  class function GetStatementType(const ASQL:LongString; ConnOptions:TConnOptions=DefGsiConnOptions):TStatementType;
  class function GetStatementInfo(const ASQL:LongString; ConnOptions:TConnOptions=DefGsiConnOptions):TSQLStatementInfo;
 public // Get Provider's default Key=(UserName|Password) from environment
  class function GetProviderEnv(const Provider,Key:LongString):LongString;
 public // Connection String Separator - ";"
  class function csSep:Char;
 public // String identifier alternatives
  class function sia_Driver:LongString;
  class function sia_Provider:LongString;
  class function sia_DataBase:LongString;
  class function sia_HostName:LongString;
  class function sia_UserName:LongString;
  class function sia_Password:LongString;
  class function sia_Charset:LongString;
  class function sia_FileDSN:LongString;
  class function sia_Catalog:LongString;
  class function sia_Port:LongString;
  class function sia_Role:LongString;
  class function sia_Verbose:LongString;
  class function sia_PwdCryptMode:LongString;
  class function sia_PwdCryptKey:LongString;
  class function sia_PwdCryptIv:LongString;
 public // Password encryption service
  class function DbApiMasterKey(key:LongString=''):LongString;
  class function EncryptPassword(pwd:LongString;
                                 mode:Integer;
                                 key:LongString='';
                                 iv:LongString=''
                                ):LongString;
  class function DecryptPassword(pwd:LongString;
                                 mode:Integer;
                                 key:LongString='';
                                 iv:LongString=''
                                ):LongString;
  class function EncryptConnectionString(cs:LongString;
                                         mode:Integer;
                                         key:LongString='';
                                         iv:LongString=''
                                        ):LongString;
  class function DecryptConnectionString(cs:LongString;
                                         out mode:Integer;
                                         out key:LongString;
                                         out iv:LongString
                                        ):LongString; overload;
  class function DecryptConnectionString(cs:LongString):LongString; overload;
 end;

const
 UseDbApiEnvirons : Boolean = True;

 ////////////////////
 // DebugLog channels
 ////////////////////
function dlc_DbApiBug:Integer;
function dlc_DbApiLog:Integer;

implementation

//////////////////
// Common routines
//////////////////

function dlc_DbApiBug:Integer;
const dlc:Integer=0;
begin
 if (dlc=0) then dlc:=RegisterDebugLogChannel('_DbApiBug');
 Result:=dlc;
end;

function dlc_DbApiLog:Integer;
const dlc:Integer=0;
begin
 if (dlc=0) then dlc:=RegisterDebugLogChannel('_DbApiLog');
 Result:=dlc;
end;

var
 TheDbCon:TDbConverter=nil;

function DbCon:TDbConverter;
begin
 if not Assigned(TheDbCon) then begin
  TheDbCon:=TDbConverter.Create;
  TheDbCon.Master:=@TheDbCon;
 end;
 Result:=TheDbCon;
end;

procedure Kill(var TheObject:TDbConverter); overload;
begin
 try
  FreeAndNil(TheObject);
 except
  on E:Exception do BugReport(E,nil,'Kill');
 end;
end;

//////////////////////////////
// TDbConverter implementation
//////////////////////////////

constructor TDbConverter.Create;
begin
 inherited Create;
end;

destructor TDbConverter.Destroy;
begin
 inherited Destroy;
end;

procedure TDbConverter.AfterConstruction;
begin
 inherited AfterConstruction;
 DbFieldTypeMap;
end;

procedure TDbConverter.BeforeDestruction;
begin
 Kill(myDbFieldTypeMap);
 inherited BeforeDestruction;
end;

function TDbConverter.EventTypeToString(EventType:TDbEventType):LongString;
begin
 Result:='';
 case EventType of
  detCustom     : Result:='detCustom';       // Custom event message
  detPrepare    : Result:='detPrepare';      // SQL prepare message
  detExecute    : Result:='detExecute';      // SQLExecute message
  detFetch      : Result:='detFetch';        // Fetch data message
  detCommit     : Result:='detCommit';       // Transaction Commit message
  detRollBack   : Result:='detRollBack';     // Transaction rollback message
  detParamValue : Result:='detParamValue';   // Parameter name and value message
  detActualSQL  : Result:='detActualSQL';    // Actual SQL as sent to engine message
 end;
end;

function TDbConverter.DataSetStateToString(const State:TDataSetState):LongString;
begin
 Result:='';
 case State of
  dsInactive      : Result:='dsInactive';     // The dataset is not active. No data is available.
  dsBrowse        : Result:='dsBrowse';       // The dataset is active, and the cursor can be used to navigate the data.
  dsEdit          : Result:='dsEdit';         // The dataset is in editing mode: the current record can be modified.
  dsInsert        : Result:='dsInsert';       // The dataset is in insert mode: the current record is a new record which can be edited.
  dsSetKey        : Result:='dsSetKey';       // The dataset is calculating the primary key.
  dsCalcFields    : Result:='dsCalcFields';   // The dataset is calculating it's calculated fields.
  dsFilter        : Result:='dsFilter';       // The dataset is filtering records.
  dsNewValue      : Result:='dsNewValue';     // The dataset is showing the new values of a record.
  dsOldValue      : Result:='dsOldValue';     // The dataset is showing the old values of a record.
  dsCurValue      : Result:='dsCurValue';     // The dataset is showing the current values of a record.
  dsBlockRead     : Result:='dsBlockRead';    // The dataset is open, but no events are transferred to datasources.
  dsInternalCalc  : Result:='dsInternalCalc'; // The dataset is calculating it's internally calculated fields.
  dsOpening       : Result:='dsOpening';      // The dataset is currently opening, but is not yet completely open.
  dsRefreshFields : Result:='dsOpening';      // The dataset is refreshing field values from server after update.
 end;
end;

function TDbConverter.SchemaTypeToString(SchemaType:TSchemaType):LongString;
begin
 Result:='';
 case SchemaType of
  stNoSchema        : Result:='stNoSchema';        // No schema
  stTables          : Result:='stTables';          // User Tables in database
  stSysTables       : Result:='stSysTables';       // System tables in database
  stProcedures      : Result:='stProcedures';      // Stored procedures in database
  stColumns         : Result:='stColumns';         // Columns in a table
  stProcedureParams : Result:='stProcedureParams'; // Parameters for a stored procedure
  stIndexes         : Result:='stIndexes';         // Indexes for a table
  stPackages        : Result:='stPackages';        // Packages (for databases that support them)
  stSchemata        : Result:='stSchemata';        // List of schemas in database(s) (for databases that support them)
  stSequences       : Result:='stSequences';       // Sequences (for databases that support them)
 end;
end;

function TDbConverter.StatementTypeToString(st:TStatementType):LongString;
begin
 Result:='';
 case st of
  stUnknown       : Result:='stUnknown';       // The statement type could not be detected.
  stSelect        : Result:='stSelect';        // The statement is a SQL SELECT statement
  stInsert        : Result:='stInsert';        // The statement is a SQL INSERT statement
  stUpdate        : Result:='stUpdate';        // The statement is a SQL UPDATE statement
  stDelete        : Result:='stDelete';        // The statement is a SQL DELETE statement
  stDDL           : Result:='stDDL';           // The statement is a SQL DDL (Data Definition Language) statement
  stGetSegment    : Result:='stGetSegment';    // The statement is a SQL get segment statement
  stPutSegment    : Result:='stPutSegment';    // The statement is a SQL put segment statement
  stExecProcedure : Result:='stExecProcedure'; // The statement executes a stored procedure
  stStartTrans    : Result:='stStartTrans';    // The statement starts a transaction
  stCommit        : Result:='stCommit';        // The statement commits a transaction
  stRollback      : Result:='stRollback';      // The statement rolls back a transaction
  stSelectForUpd  : Result:='stSelectForUpd';  // The statement selects data for update
 end;
end;

function TDbConverter.ConnOptionToString(Opt:TConnOption):LongString;
begin
 Result:='';
 case Opt of
  sqSupportParams            : Result:='sqSupportParams';            // The connection type has native support for parameters.
  sqSupportEmptyDatabaseName : Result:='sqSupportEmptyDatabaseName'; // Does the connection allow empty database names ?
  sqEscapeSlash              : Result:='sqEscapeSlash';              // Escapes in string literals are done with backslash characters.
  sqEscapeRepeat             : Result:='sqEscapeRepeat';             // Escapes in string literals are done by repeating the character.
  sqImplicitTransaction      : Result:='sqImplicitTransaction';      // Does the connection support implicit transaction management
  sqLastInsertID             : Result:='sqLastInsertID';             // Does the connection support getting the ID for the last insert operation.
  sqSupportReturning         : Result:='sqSupportReturning';         // The connection type supports INSERT/UPDATE with RETURNING clause
  sqSequences                : Result:='sqSequences';                // Are sequences supported.
 end;
end;

function TDbConverter.ConnOptionsToString(const Opts:TConnOptions; Sep:Char=','):LongString;
var opt:TConnOption;
begin
 Result:='';
 for opt:=Low(TConnOption) to High(TConnOption) do
 if (opt in Opts) then Result:=Result+ConnOptionToString(opt)+Sep;
 if (Result<>'') then Result:=TrimRightChars(Result,[Sep]);
end;

function TDbConverter.SQLConnectionOptionToString(opt:TSQLConnectionOption):LongString;
begin
 case opt of
  scoExplicitConnect                : Result:='scoExplicitConnect';                // Require explicit connection to the database (default is implicit)
  scoApplyUpdatesChecksRowsAffected : Result:='scoApplyUpdatesChecksRowsAffected'; // ApplyUpdates will check that the RowsAffected is 1 after an update.
 end;
end;

function TDbConverter.SQLConnectionOptionsToString(opts:TSQLConnectionOptions; Sep:Char=','):LongString;
var opt:TSQLConnectionOption;
begin
 Result:='';
 for opt:=Low(TSQLConnectionOption) to High(TSQLConnectionOption) do
 if (opt in Opts) then Result:=Result+SQLConnectionOptionToString(opt)+Sep;
 if (Result<>'') then Result:=TrimRightChars(Result,[Sep]);
end;

function TDbConverter.StringToSQLConnectionOptions(arg:LongString):TSQLConnectionOptions;
var opt:TSQLConnectionOption; Delims:TCharSet;
begin
 Result:=[];
 Delims:=ScanSpaces+['[',']']; arg:=TrimChars(arg,Delims,Delims);
 for opt:=Low(TSQLConnectionOption) to High(TSQLConnectionOption) do
 if (WordIndex(SQLConnectionOptionToString(opt),arg,ScanSpaces)>0)
 then Include(Result,opt);
end;

function TDbConverter.SQLQueryOptionToString(opt:TSQLQueryOption):LongString;
begin
 Result:='';
 case opt of
  sqoKeepOpenOnCommit:       Result:='sqoKeepOpenOnCommit';
  sqoAutoApplyUpdates:       Result:='sqoAutoApplyUpdates';
  sqoAutoCommit:             Result:='sqoAutoCommit';
  sqoCancelUpdatesOnRefresh: Result:='sqoCancelUpdatesOnRefresh';
  sqoRefreshUsingSelect:     Result:='sqoRefreshUsingSelect';
 end;
end;

function TDbConverter.SQLQueryOptionsToString(opts:TSQLQueryOptions; Sep:Char=','):LongString;
var opt:TSQLQueryOption;
begin
 Result:='';
 for opt:=Low(TSQLQueryOption) to High(TSQLQueryOption) do
 if (opt in Opts) then Result:=Result+SQLQueryOptionToString(opt)+Sep;
 if (Result<>'') then Result:=TrimRightChars(Result,[Sep]);
end;

function TDbConverter.StringToSQLQueryOptions(arg:LongString):TSQLQueryOptions;
var opt:TSQLQueryOption; Delims:TCharSet;
begin
 Result:=[];
 Delims:=ScanSpaces+['[',']']; arg:=TrimChars(arg,Delims,Delims);
 for opt:=Low(TSQLQueryOption) to High(TSQLQueryOption) do
 if (WordIndex(SQLQueryOptionToString(opt),arg,ScanSpaces)>0)
 then Include(Result,opt);
end;

function TDbConverter.UpdateModeToString(opt:TUpdateMode):LongString;
begin
 Result:='';
 case opt of
  upWhereAll:     Result:='upWhereAll';
  upWhereChanged: Result:='upWhereChanged';
  upWhereKeyOnly: Result:='upWhereKeyOnly';
 end;
end;

function TDbConverter.StringToUpdateMode(arg:LongString; def:TUpdateMode=upWhereKeyOnly):TUpdateMode;
var opt:TUpdateMode;
begin
 Result:=def;
 for opt:=Low(TUpdateMode) to High(TUpdateMode) do
 if SameText(UpdateModeToString(opt),arg) then Exit(opt);
end;

 // Converts an ADO native types into string related.
function TDbConverter.AdoFieldTypeToString(FieldType:Integer):LongString;
begin
 Result:='';
 case (FieldType and not adArray) of
  adChar             : Result := 'adChar';
  adVarChar          : Result := 'adVarChar';
  adBSTR             : Result := 'adBSTR';
  adWChar            : Result := 'adWChar';
  adVarWChar         : Result := 'adVarWChar';
  adBoolean          : Result := 'adBoolean';
  adTinyInt          : Result := 'adTinyInt';
  adUnsignedTinyInt  : Result := 'adUnsignedTinyInt';
  adSmallInt         : Result := 'adSmallInt';
  adUnsignedSmallInt : Result := 'adUnsignedSmallInt';
  adInteger          : Result := 'adInteger';
  adUnsignedInt      : Result := 'adUnsignedInt';
  adBigInt           : Result := 'adBigInt';
  adUnsignedBigInt   : Result := 'adUnsignedBigInt';
  adSingle           : Result := 'adSingle';
  adDouble           : Result := 'adDouble';
  adDecimal          : Result := 'adDecimal';
  adNumeric          : Result := 'adNumeric';
  adVarNumeric       : Result := 'adVarNumeric';
  adCurrency         : Result := 'adCurrency';
  adDBDate           : Result := 'adDBDate';
  adDBTime           : Result := 'adDBTime';
  adDate             : Result := 'adDate';
  adDBTimeStamp      : Result := 'adDBTimeStamp';
  adFileTime         : Result := 'adFileTime';
  adDBFileTime       : Result := 'adDBFileTime';
  adLongVarChar      : Result := 'adLongVarChar';
  adLongVarWChar     : Result := 'adLongVarWChar';
  adBinary           : Result := 'adBinary';
  adVarBinary        : Result := 'adVarBinary';
  adLongVarBinary    : Result := 'adLongVarBinary';
  adGUID             : Result := 'adGUID';
  adEmpty            : Result := 'adEmpty';
  adError            : Result := 'adError';
  adArray            : Result := 'adArray';
  adChapter          : Result := 'adChapter';
  adIDispatch        : Result := 'adIDispatch';
  adIUnknown         : Result := 'adIUnknown';
  adPropVariant      : Result := 'adPropVariant';
  adUserDefined      : Result := 'adUserDefined';
  adVariant          : Result := 'adVariant';
 end;
 if HasFlags(FieldType,adArray) then begin
  if (Result='')
  then Result:='adArray'
  else Result:=Result+'Array';
 end;
end;

function TDbConverter.XMLFieldTypeToString(FieldType:TFieldType):LongString;
begin
 Result:='';
 case FieldType of
  ftUnknown       : Result:='Unknown';
  ftString        : Result:='string';
  ftSmallint      : Result:='i2';
  ftInteger       : Result:='i4';
  ftWord          : Result:='i4';
  ftBoolean       : Result:='boolean';
  ftFloat         : Result:='r8';
  ftCurrency      : Result:='r8:Money';
  ftBCD           : Result:='fixed';
  ftDate          : Result:='date';
  ftTime          : Result:='time';
  ftDateTime      : Result:='datetime';
  ftBytes         : Result:='bin.hex';
  ftVarBytes      : Result:='bin.hex';
  ftAutoInc       : Result:='i4:Autoinc';
  ftBlob          : Result:='bin.hex:Binary';
  ftMemo          : Result:='bin.hex:Text';
  ftGraphic       : Result:='bin.hex:Graphics';
  ftFmtMemo       : Result:='bin.hex:Formatted';
  ftParadoxOle    : Result:='bin.hex:Ole';
  ftDBaseOle      : Result:='bin.hex:Ole';
  ftTypedBinary   : Result:='bin.hex:Graphics';
  ftCursor        : Result:='';
  ftFixedChar     : Result:='string';
  ftWideString    : Result:='string.uni';
  ftLargeint      : Result:='i8';
  ftADT           : Result:='';
  ftArray         : Result:='';
  ftReference     : Result:='';
  ftDataSet       : Result:='';
  ftOraBlob       : Result:='';
  ftOraClob       : Result:='';
  ftVariant       : Result:='';
  ftInterface     : Result:='';
  ftIDispatch     : Result:='';
  ftGuid          : Result:='string:Guid';
  ftTimeStamp     : Result:='';
  ftFMTBcd        : Result:='fixedFMT';
  ftFixedWideChar : Result:='string.uni';
  ftWideMemo      : Result:='bin.hex:WideText';
 end;
end;

 // Converts an SQLDB native types into string related.
function TDbConverter.SqlDbFieldTypeToString(FieldType:TFieldType):LongString;
begin
 if UsesSystemDbFieldtypenames then
 if InRange(Ord(FieldType),Ord(Low(TFieldType)),Ord(High(TFieldType))) then begin
  Result:=Format('ft%s',[db.Fieldtypenames[FieldType]]);
  Exit;
 end;
 Result:='';
 case FieldType of
  ftUnknown       : Result:='ftUnknown';       // 0  Unknown data type
  ftString        : Result:='ftString';        // 1  String data value (ansistring)
  ftSmallint      : Result:='ftSmallint';      // 2  Small integer value(1 byte, signed)
  ftInteger       : Result:='ftInteger';       // 3  Regular integer value (4 bytes, signed)
  ftWord          : Result:='ftWord';          // 4  Word-sized value(2 bytes, unsigned)
  ftBoolean       : Result:='ftBoolean';       // 5  Boolean value
  ftFloat         : Result:='ftFloat';         // 6  Floating point value (double)
  ftCurrency      : Result:='ftCurrency';      // 7  Currency value (4 decimal points)
  ftBCD           : Result:='ftBCD';           // 8  Binary Coded Decimal value (DECIMAL and NUMERIC SQL types)
  ftDate          : Result:='ftDate';          // 9  Date value
  ftTime          : Result:='ftTime';          // 10 Time value
  ftDateTime      : Result:='ftDateTime';      // 11 Date/Time (timestamp) value
  ftBytes         : Result:='ftBytes';         // 12 Array of bytes value, fixed size (unytped)
  ftVarBytes      : Result:='ftVarBytes';      // 13 Array of bytes value, variable size (untyped)
  ftAutoInc       : Result:='ftAutoInc';       // 14 Auto-increment integer value (4 bytes)
  ftBlob          : Result:='ftBlob';          // 15 Binary data value (no type, no size)
  ftMemo          : Result:='ftMemo';          // 16 Binary text data (no size)
  ftGraphic       : Result:='ftGraphic';       // 17 Graphical data value (no size)
  ftFmtMemo       : Result:='ftFmtMemo';       // 18 Formatted memo data value (no size)
  ftParadoxOle    : Result:='ftParadoxOle';    // 19 Paradox OLE field data (no size)
  ftDBaseOle      : Result:='ftDBaseOle';      // 20 Paradox OLE field data
  ftTypedBinary   : Result:='ftTypedBinary';   // 21 Binary typed data (no size)
  ftCursor        : Result:='ftCursor';        // 22 Cursor data value (no size)
  ftFixedChar     : Result:='ftFixedChar';     // 23 Fixed character array (string)
  ftWideString    : Result:='ftWideString';    // 24 Widestring (2 bytes per character)
  ftLargeint      : Result:='ftLargeint';      // 25 Large integer value (8-byte)
  ftADT           : Result:='ftADT';           // 26 ADT value
  ftArray         : Result:='ftArray';         // 27 Array data
  ftReference     : Result:='ftReference';     // 28 Reference data
  ftDataSet       : Result:='ftDataSet';       // 29 Dataset data (blob)
  ftOraBlob       : Result:='ftOraBlob';       // 30 Oracle BLOB data
  ftOraClob       : Result:='ftOraClob';       // 31 Oracle CLOB data
  ftVariant       : Result:='ftVariant';       // 32 Variant data value
  ftInterface     : Result:='ftInterface';     // 33 interface data value
  ftIDispatch     : Result:='ftIDispatch';     // 34 Dispatch data value
  ftGuid          : Result:='ftGuid';          // 35 GUID data value
  ftTimeStamp     : Result:='ftTimeStamp';     // 36 Timestamp data value
  ftFMTBcd        : Result:='ftFMTBcd';        // 37 Formatted BCD (Binary Coded Decimal) value.
  ftFixedWideChar : Result:='ftFixedWideChar'; // 38 Fixed wide character date (2 bytes per character)
  ftWideMemo      : Result:='ftWideMemo';      // 39 Widestring memo data
 end;
end;

function TDbConverter.DbFieldTypeMap:TStringList;
var ad:Integer; ft:TFieldType; Ptr:Pointer; S:LongString;
begin
 if not Assigned(myDbFieldTypeMap) then begin
  myDbFieldTypeMap:=TStringList.Create;
  myDbFieldTypeMap.Duplicates:=dupIgnore;
  myDbFieldTypeMap.Sorted:=True;
  for ad:=Low(Byte) to High(Byte) do begin
   if ad in adScalarFieldTypes then begin
    Ptr:=PtrIntToPointer(ad);
    S:=AdoFieldTypeToString(ad);
    if (S<>'') then myDbFieldTypeMap.AddObject(S,Ptr);
    Ptr:=PtrIntToPointer(ad or adArray);
    S:=AdoFieldTypeToString(ad or adArray);
    if (S<>'') then myDbFieldTypeMap.AddObject(S,Ptr);
   end;
  end;
  myDbFieldTypeMap.AddObject('adArray',PtrIntToPointer(adArray));
  for ft:=Low(TFieldType) to High(TFieldType) do begin
   Ptr:=PtrIntToPointer(Ord(ft));
   S:=SqlDbFieldTypeToString(ft);
   if (S<>'') then myDbFieldTypeMap.AddObject(S,Ptr);
  end;
 end;
 Result:=myDbFieldTypeMap;
end;

function TDbConverter.StringToFieldTypeCode(const S:LongString; Def:Integer=-1):Integer;
var i:Integer;
begin
 Result:=Def;
 if (S<>'') then
 try
  i:=DbFieldTypeMap.IndexOf(S);
  if (i>=0) then Result:=PointerToPtrInt(DbFieldTypeMap.Objects[i]);
 except
  on E:Exception do BugReport(E,nil,'StringToDbFieldTypeCode');
 end;
end;

function TDbConverter.CodeToFieldType(Code:Integer; Def:TFieldType=ftUnknown):TFieldType;
begin
 if InRange(Code,Ord(Low(TFieldType)),Ord(High(TFieldType)))
 then Result:=TFieldType(Code)
 else Result:=Def;
end;

function TDbConverter.FieldTypeCodeToString(Code:Integer; Engine:Integer):LongString;
begin
 Result:='';
 case Engine of
  db_engine_code_ado:   Result:=AdoFieldTypeToString(Code);
  db_engine_code_sqldb: Result:=SqlDbFieldTypeToString(CodeToFieldType(Code));
  db_engine_code_zeos:  Result:=SqlDbFieldTypeToString(CodeToFieldType(Code));
 end;
end;

function TDbConverter.IsDateTimeFieldTypeCode(typ,Engine:Integer):Boolean;
const AdoCodes=[adDate,adDBDate,adDBTime,adDBTimeStamp,adFileTime,adDBFileTime];
const SqlDbCodes=[ftDate,ftTime,ftDateTime,ftTimeStamp];
begin
 case Engine of
  db_engine_code_ado:   Result:=(typ in AdoCodes);
  db_engine_code_sqldb: Result:=(CodeToFieldType(typ) in SqlDbCodes);
  db_engine_code_zeos:  Result:=(CodeToFieldType(typ) in SqlDbCodes);
  else                  Result:=False;
 end;
end;

function TDbConverter.AdoDateTimeToMs(tm:Double; typ:Integer):Double;
begin
 case typ of
  adDate:        Result:=OleTimeToMs(tm);
  adDBDate:      Result:=OleTimeToMs(tm);
  adDBTime:      Result:=OleTimeToMs(tm);
  adDBTimeStamp: Result:=OleTimeToMs(tm);
  adFileTime:    Result:=FileTimeToMs(tm);
  adDBFileTime:  Result:=FileTimeToMs(tm);
  else           Result:=tm;
 end;
end;

function TDbConverter.SqlDbDateTimeToMs(tm:Double; typ:Integer):Double;
var ft:TFieldType;
begin
 ft:=CodeToFieldType(typ);
 case ft of
  ftDate:      Result:=OleTimeToMs(tm);
  ftTime:      Result:=OleTimeToMs(tm);
  ftDateTime:  Result:=OleTimeToMs(tm);
  ftTimeStamp: Result:=OleTimeToMs(tm);
  else         Result:=tm;
 end;
end;

function TDbConverter.DbDateTimeToMs(tm:Double; typ,Engine:Integer):Double;
begin
 case Engine of
  db_engine_code_ado:   Result:=AdoDateTimeToMs(tm,typ);
  db_engine_code_sqldb: Result:=SqlDbDateTimeToMs(tm,typ);
  db_engine_code_zeos:  Result:=SqlDbDateTimeToMs(tm,typ);
  else                  Result:=tm;
 end;
end;

function TDbConverter.IsBlobFieldTypeCode(typ,Engine:Integer):Boolean;
const AdoCodes=[adBinary,adVarBinary,adLongVarBinary];
const SqlDbCodes=[ftBlob,ftGraphic,ftFmtMemo,ftParadoxOle, ftDBaseOle,
                  ftTypedBinary,ftOraBlob,ftOraClob,ftBytes,ftVarBytes];
begin
 case Engine of
  db_engine_code_ado:   Result:=(typ in AdoCodes);
  db_engine_code_sqldb: Result:=(CodeToFieldType(typ) in SqlDbCodes);
  db_engine_code_zeos:  Result:=(CodeToFieldType(typ) in SqlDbCodes);
  else                  Result:=False;
 end;
end;

 ///////////////////////////////////////////////////////////////////////////////
 // Detect Blob image type, return (semicolon separated list of) expected file
 // extensions like: bmp,png,gif,jpg,pbm,ppm,pgm
 ///////////////////////////////////////////////////////////////////////////////
function TDbConverter.DetectBlobImageType(const Blob:LongString):LongString;
const SignJPG_SOI=#$FF#$D8; SignJPG_EOI=#$FF#$D9;
const SignPNG=#137#80#78#71#13#10#26#10;
var Leng:Integer;
 function ReadInt(Offs,Leng:Integer):Integer;
 begin
  Result:=Dump2i(Copy(Blob,1+Offs,Leng));
 end;
 function HasSub(Sub:LongString):Boolean;
 begin
  Result:=(Pos(Sub,Blob)>0);
 end;
 function DetectPcx:Boolean;
 begin
  Result:=False;
  if (Length(Blob)<=128) then Exit;
  if not (Ord(StrFetch(Blob,1+0))  in [10]        ) then Exit; // Identifier
  if not (Ord(StrFetch(Blob,1+1))  in [0,2,3,4,5] ) then Exit; // Version
  if not (Ord(StrFetch(Blob,1+2))  in [1]         ) then Exit; // Encoding
  if not (Ord(StrFetch(Blob,1+3))  in [1,4,8]     ) then Exit; // BitsPerPixel
  if not (Ord(StrFetch(Blob,1+64)) in [0]         ) then Exit; // Reserved, 0
  if not (Ord(StrFetch(Blob,1+65)) in [1,3,4]     ) then Exit; // NumBitPlanes
  if not (Ord(StrFetch(Blob,1+68)) in [1,2]       ) then Exit; // PaletteType
  if not (Ord(StrFetch(Blob,1+69)) in [0]         ) then Exit; // Always be 0
  if not (Ord(StrFetch(Blob,1+74)) in [0]         ) then Exit; // Reserved, 0
  Result:=True;
 end;
begin
 Result:='';
 if (Blob<>'') then
 try
  Leng:=Length(Blob);
  {
  BMP: https://docs.fileformat.com/image/bmp/
  }
  if StartsStr('BM',Blob) and (ReadInt(2,4)=Leng)
  then Exit('bmp');
  {
  GIF: https://docs.fileformat.com/image/gif/
  }
  if StartsStr('GIF87a',Blob)
  or StartsStr('GIF89a',Blob)
  then Exit('gif');
  {
  PNG:  https://libpng.org/pub/png/spec/1.2/PNG-Structure.html
  }
  if StartsStr(SignPNG,Blob) and HasSub('IHDR') and HasSub('IEND')
  then Exit('png');
  {
  PBM: https://netpbm.sourceforge.net/doc/pbm.html
  PGM: https://netpbm.sourceforge.net/doc/pgm.html
  PPM: https://netpbm.sourceforge.net/doc/ppm.html
  PBM Header MagicValue:	Literally P1 for ASCII version, P4 for binary
  PGM Header MagicValue:	Literally P2 for ASCII version, P5 for binary
  PPM Header MagicValue:	Literally P3 for ASCII version, P6 for binary
  }
  if StartsStr('P1'#10,Blob) or StartsStr('P1'#13#10,Blob)
  or StartsStr('P4'#10,Blob) or StartsStr('P4'#13#10,Blob)
  then Exit('pbm');
  if StartsStr('P2'#10,Blob) or StartsStr('P2'#13#10,Blob)
  or StartsStr('P5'#10,Blob) or StartsStr('P5'#13#10,Blob)
  then Exit('pgm');
  if StartsStr('P3'#10,Blob) or StartsStr('P3'#13#10,Blob)
  or StartsStr('P6'#10,Blob) or StartsStr('P6'#13#10,Blob)
  then Exit('ppm');
  {
  JPEG: https://docs.fileformat.com/image/jpeg/
  }
  if StartsStr(SignJPG_SOI,Blob) and EndsStr(SignJPG_EOI,Blob)
  then Exit('jpg');
  {
  XPM: http://www.xfree86.org/current/xpm.pdf
  }
  if StartsStr('/* XPM */'#10,Blob)
  or StartsStr('/* XPM */'#13#10,Blob)
  then Exit('xpm');
  {
  TIFF: https://docs.fileformat.com/image/tiff/
  MM means MSB byte order, II means LSB byte order
  }
  if StartsStr('MM'#0#42,Blob)
  or StartsStr('II'#42#0,Blob)
  then Exit('tif');
  {
  PCX: https://en.wikipedia.org/wiki/PCX
  }
  if DetectPcx
  then Exit('pcx');
 except
  on E:Exception do BugReport(E,nil,'DetectBlobImageType');
 end;
end;

////////////////////////////////////
// TDbEngineAssistant implementation
////////////////////////////////////

class function TDbEngineAssistant.FetchParam(const Buff,Items:LongString):LongString;
begin
 Result:=Trim(CookieScanAlter(Buff,Items,Ord(csSep)));
end;

class function TDbEngineAssistant.SubstParam(Buff,Items,Value:LongString; NMax:Integer=1; NoEmpty:Boolean=False):LongString;
var Lines:TStringList; i,nc:Integer; sn,sv:LongString;
begin
 Result:=Buff; nc:=0; sn:=''; sv:='';
 Buff:=Trim(Buff); if (Buff='') then Exit;
 Items:=Trim(Items); if (Items='') then Exit;
 Value:=Trim(Value); if (Value='') and NoEmpty then Exit;
 try
  Lines:=TStringList.Create;
  try
   Lines.Text:=StringReplace(Buff,csSep,EOL,[rfReplaceAll]);
   for i:=0 to Lines.Count-1 do begin
    if (ExtractNameValuePair(Lines[i],sn,sv)>0) then
    if (WordIndex(sn,Items,ScanSpaces)>0) then begin
     Lines[i]:=sn+'='+Value; inc(nc);
     if (NMax>0) and (nc>NMax)
     then Break;
    end;
   end;
   if (nc>0) then Result:=StringReplace(Lines.Text,EOL,csSep,[rfReplaceAll]);
  finally
   Lines.Free;
  end;
 except
  on E:Exception do BugReport(E,nil,'TDbEngineAssistant.SubstParam');
 end;
end;

class function TDbEngineAssistant.DropParam(Buff,Items:LongString):LongString;
begin
 Result:=TDbEngineAssistant.ValidateParams(Buff,'',Items,csSep);
end;

class function TDbEngineAssistant.ValidateParams(const aBuff,aIncl,aExcl:LongString;
                                                 const aDelim:LongString=EOL):LongString;
var Lines:TStringList; sn,sv:LongString; i:Integer;
const Delims=EolnDelims+[';',','];
begin
 Result:='';
 try
  sn:='';sv:='';
  Lines:=TStringList.Create;
  try
   Lines.Text:=StringReplace(aBuff,csSep,EOL,[rfReplaceAll]);
   for i:=Lines.Count-1 downto 0 do begin
    if (ExtractNameValuePair(Lines[i],sn,sv)>0) then begin
     if (aIncl<>'') then begin
      if (WordIndex(sn,aIncl,Delims)=0) then begin sn:=''; sv:=''; end;
     end;
     if (aExcl<>'') then begin
      if (WordIndex(sn,aExcl,Delims)>0) then begin sn:=''; sv:=''; end;
     end;
    end else begin
     sn:=''; sv:='';
    end;
    if (sn<>'') and (sv<>'')
    then Lines[i]:=Format('%s=%s',[sn,sv])
    else Lines.Delete(i);
   end;
   Result:=Lines.Text;
   if (aDelim<>EOL) then Result:=StringReplace(Result,EOL,aDelim,[rfReplaceAll]);
  finally
   Kill(Lines);
  end;
 except
  on E:Exception do BugReport(E,nil,'ValidateParams');
 end;
end;

 // Derived from TSQLConnector
class function TDbEngineAssistant.StrToStatementType(const S:LongString):TStatementType;
var T:TStatementType;
begin
 Result:=stUnknown;
 for T:=stSelect to stRollback do
 if SameText(S,StatementTokens[T]) then Exit(T);
end;

 // Derived from TSQLConnector
class function TDbEngineAssistant.GetStatementInfo(const ASQL:LongString; ConnOptions:TConnOptions):TSQLStatementInfo;
type
 TParsePart=(ppStart,ppWith,ppSelect,ppTableName,ppFrom,ppWhere,ppGroup,ppOrder,ppBogus);
 TPhraseSeparator=(sepNone,sepWhiteSpace,sepComma,sepComment,sepParentheses,sepDoubleQuote,sepEnd);
 TKeyword=(kwWITH,kwSELECT,kwINSERT,kwUPDATE,kwDELETE,kwFROM,kwJOIN,kwWHERE,kwGROUP,kwORDER,kwUNION,kwROWS,kwLIMIT,kwUnknown);
const
 KeywordNames:array[TKeyword] of LongString=('WITH','SELECT','INSERT','UPDATE',
 'DELETE','FROM','JOIN','WHERE','GROUP','ORDER','UNION','ROWS','LIMIT','');
var
 PSQL,CurrentP,SavedP,PhraseP,PStatementPart,PEnd:PChar; S:LongString;
 ParsePart:TParsePart; BracketCount:Integer; Keyword,K:TKeyword;
 Separator:TPhraseSeparator;
begin
 Result.StatementType:=stUnknown;
 Result.Updateable:=False;
 Result.WhereStartPos:=0;
 Result.WhereStopPos:=0;
 Result.TableName:='';
 ParsePart:=ppStart;
 PSQL:=PChar(ASQL);
 CurrentP:=PSQL-1; PhraseP:=PSQL;
 PEnd:=PSQL+StrLLen(PSQL,Length(ASQL));
 repeat
  inc(CurrentP);
  SavedP:=CurrentP;
  case CurrentP[0] of
   ' ',#9..#13: Separator:=sepWhiteSpace;
   ',':         Separator:=sepComma;
   #0,';':      Separator:=sepEnd;
  '(': begin
   Separator:=sepParentheses;
   // skip everything between brackets, since it could be a sub-select, and
   // further nothing between brackets could be interesting for the parser.
   BracketCount:=1;
   repeat
    inc(CurrentP);
    if (CurrentP[0]='(') then inc(BracketCount) else
    if (CurrentP[0]=')') then dec(BracketCount);
   until (CurrentP>=PEnd) or (CurrentP[0]=#0) or (BracketCount=0);
   if (CurrentP[0]<>#0) and (CurrentP<PEnd) then inc(CurrentP);
  end;
  '"','`':
   if SkipComments(CurrentP,sqEscapeSlash in ConnOptions,sqEscapeRepeat in ConnOptions)
   then Separator:=sepDoubleQuote;
  else
   if SkipComments(CurrentP,sqEscapeSlash in ConnOptions,sqEscapeRepeat in ConnOptions)
   then Separator:=sepComment
   else Separator:=sepNone;
  end;
  if (Separator<>sepNone) then begin
   // there is something before comment or left parenthesis or double quote
   if (CurrentP>SavedP) and (SavedP>PhraseP)
   then CurrentP:=SavedP;
   // skip comments (but not parentheses) and white spaces
   if (Separator in [sepWhitespace,sepComment]) and (SavedP=PhraseP)
   then PhraseP:=CurrentP;
   if (CurrentP-PhraseP>0) or (Separator=sepEnd) then begin
    SetString(s,PhraseP,CurrentP-PhraseP);
    Keyword:=kwUnknown;
    for K in TKeyword do
    if SameText(s,KeywordNames[K]) then begin
     Keyword:=K;
     break;
    end;
    case ParsePart of
     ppStart: begin
      Result.StatementType:=StrToStatementType(s);
      case Keyword of
       kwWITH:   ParsePart:=ppWith;
       kwSELECT: ParsePart:=ppSelect;
       else      break;
      end;
     end;
     ppWith: begin
      // WITH [RECURSIVE] CTE_name [ ( column_names ) ] AS ( CTE_query_definition ) [, ...]
      //  { SELECT | INSERT | UPDATE | DELETE } ...
      case Keyword of
       kwSELECT: Result.StatementType:=stSelect;
       kwINSERT: Result.StatementType:=stInsert;
       kwUPDATE: Result.StatementType:=stUpdate;
       kwDELETE: Result.StatementType:=stDelete;
      end;
      if (Result.StatementType<>stUnknown) then break;
     end;
     ppSelect: begin
      if (Keyword=kwFROM) then ParsePart:=ppTableName;
     end;
     ppTableName: begin
      // Meta-data requests are never updateable
      //  and select statements from more than one table
      //  and/or derived tables are also not updateable
      if (Separator in [sepWhitespace,sepComment,sepDoubleQuote,sepEnd]) then begin
       Result.TableName:=Result.TableName+s;
       Result.Updateable:=True;
      end;
      // compound delimited classifier like: "schema name"."table name"
      if not (CurrentP[0] in ['.','"']) then ParsePart:=ppFrom;
     end;
     ppFrom: begin
      if (Keyword in [kwWHERE,kwGROUP,kwORDER,kwLIMIT,kwROWS]) or (Separator=sepEnd) then begin
       case Keyword of
        kwWHERE: ParsePart:=ppWhere;
        kwGROUP: ParsePart:=ppGroup;
        kwORDER: ParsePart:=ppOrder;
        else     ParsePart:=ppBogus;
       end;
       Result.WhereStartPos:=PhraseP-PSQL+1;
       PStatementPart:=CurrentP;
      end else
      // joined table or user_defined_function (...)
      if (Keyword=kwJOIN) or (Separator in [sepComma,sepParentheses]) then begin
       Result.TableName:='';
       Result.Updateable:=False;
      end;
     end;
     ppWhere: begin
      if (Keyword in [kwGROUP,kwORDER,kwLIMIT,kwROWS]) or (Separator=sepEnd) then begin
       ParsePart:=ppBogus;
       Result.WhereStartPos:=PStatementPart-PSQL;
       if (Separator=sepEnd)
       then Result.WhereStopPos:=CurrentP-PSQL+1
       else Result.WhereStopPos:=PhraseP-PSQL+1;
      end else
      if (Keyword=kwUNION) then begin
       ParsePart:=ppBogus;
       Result.Updateable:=False;
      end;
     end;
    end; {case}
   end;
   if (Separator in [sepComment,sepParentheses,sepDoubleQuote]) then dec(CurrentP);
   PhraseP:=CurrentP+1;
  end;
 until (CurrentP>=PEnd) or (CurrentP[0]=#0);
end;

class function TDbEngineAssistant.GetStatementType(const ASQL:LongString; ConnOptions:TConnOptions):TStatementType;
begin
 Result:=GetStatementInfo(ASQL,ConnOptions).StatementType;
end;

class function TDbEngineAssistant.GetProviderEnv(const Provider,Key:LongString):LongString;
begin
 Result:='';
 if (WordIndex(Provider,'IB,FB,Firebird,Interbase',ScanSpaces)>0) then begin
  if (WordIndex(Key,sia_USERNAME,ScanSpaces)>0) then Result:=GetEnv('ISC_USER');
  if (WordIndex(Key,sia_PASSWORD,ScanSpaces)>0) then Result:=GetEnv('ISC_PASSWORD');
  if (WordIndex(Key,sia_HOSTNAME,ScanSpaces)>0) then Result:=GetEnv('ISC_HOST');
  if (WordIndex(Key,sia_PORT,ScanSpaces)>0) then Result:=GetEnv('ISC_PORT');
  if (Result<>'') then Exit;
 end;
 // https://www.postgresql.org/docs/current/libpq-envars.html
 if (WordIndex(Provider,'PQ,PG,Postgres,PostgreSQL',ScanSpaces)>0) then begin
  if (WordIndex(Key,sia_USERNAME,ScanSpaces)>0) then Result:=GetEnv('PGUSER');
  if (WordIndex(Key,sia_PASSWORD,ScanSpaces)>0) then Result:=GetEnv('PGPASSWORD');
  if (WordIndex(Key,sia_HOSTNAME,ScanSpaces)>0) then Result:=GetEnv('PGHOST');
  if (WordIndex(Key,sia_PORT,ScanSpaces)>0) then Result:=GetEnv('PGPORT');
  if (Result<>'') then Exit;
 end;
 // MYSQL specific, like Tango Controls uses
 if StartsText('MySQL',Provider) or StartsText('MariaDB',Provider) then begin
  if (WordIndex(Key,sia_USERNAME,ScanSpaces)>0) then Result:=GetEnv('MYSQL_USER');
  if (WordIndex(Key,sia_PASSWORD,ScanSpaces)>0) then Result:=GetEnv('MYSQL_PASSWORD');
  if (WordIndex(Key,sia_HOSTNAME,ScanSpaces)>0) then Result:=GetEnv('MYSQL_HOST');
  if (WordIndex(Key,sia_PORT,ScanSpaces)>0) then Result:=GetEnv('MYSQL_PORT');
  if (Result<>'') then Exit;
 end;
 // DBAPI specific
 if UseDbApiEnvirons and (Key<>'') and (Result='') then begin
  if (WordIndex(Key,sia_USERNAME,ScanSpaces)>0) then Result:=GetEnv('DBAPI_USER');
  if (WordIndex(Key,sia_PASSWORD,ScanSpaces)>0) then Result:=GetEnv('DBAPI_PASSWORD');
  if (WordIndex(Key,sia_HOSTNAME,ScanSpaces)>0) then Result:=GetEnv('DBAPI_HOST');
  if (WordIndex(Key,sia_PORT,ScanSpaces)>0) then Result:=GetEnv('DBAPI_PORT');
 end;
end;

class function TDbEngineAssistant.csSep:Char;
begin
 Result:=';';
end;

class function TDbEngineAssistant.sia_Driver:LongString;
begin
 Result:='Driver';
end;
class function TDbEngineAssistant.sia_Provider:LongString;
begin
 Result:='Provider;Protocol';
end;
class function TDbEngineAssistant.sia_DataBase:LongString;
begin
 Result:='DataBase;Location;Data Source;DBNAME;DSN;DataBaseName';
end;
class function TDbEngineAssistant.sia_HostName:LongString;
begin
 Result:='HostName;DataSource;Server;Host';
end;
class function TDbEngineAssistant.sia_UserName:LongString;
begin
 Result:='UserName;User Name;User_Name;UserId;User ID;User_ID;User;UID';
end;
class function TDbEngineAssistant.sia_Password:LongString;
begin
 Result:='Password;PWD';
end;
class function TDbEngineAssistant.sia_Charset:LongString;
begin
 Result:='Charset;Codepage;ClientCodepage';
end;
class function TDbEngineAssistant.sia_FileDSN:LongString;
begin
 Result:='FILEDSN;FileDataSourceName';
end;
class function TDbEngineAssistant.sia_Catalog:LongString;
begin
 Result:='Catalog;Directory';
end;
class function TDbEngineAssistant.sia_Port:LongString;
begin
 Result:='Port';
end;
class function TDbEngineAssistant.sia_Role:LongString;
begin
 Result:='Role';
end;
class function TDbEngineAssistant.sia_Verbose:LongString;
begin
 Result:='Verbose;Verb';
end;
class function TDbEngineAssistant.sia_PwdCryptMode:LongString;
begin
 Result:='PwdCryptMode;PasswordCryptMode';
end;
class function TDbEngineAssistant.sia_PwdCryptKey:LongString;
begin
 Result:='PwdCryptKey;PasswordCryptKey';
end;
class function TDbEngineAssistant.sia_PwdCryptIv:LongString;
begin
 Result:='PwdCryptIv;PasswordCryptIv';
end;

class function TDbEngineAssistant.DbApiMasterKey(key:LongString=''):LongString;
const FallbackKey='DbApiMasterKey';
const MasterKey:LongString='';
begin
 if (key='') then begin
  if (MasterKey='') then begin
   if ReadIniFileAlpha(SysIniFile,'[DbApi.Defaults]','DbApiMasterKey%a',key,efConfigNC)
   then MasterKey:=TrimDef(key,FallbackKey)
   else MasterKey:=FallbackKey;
  end;
  key:=MasterKey;
 end;
 Result:=LeftStr(Pad(key,32),32); // GOST required key of 32 byte
end;

class function TDbEngineAssistant.EncryptPassword(pwd:LongString;
                                                  mode:Integer;
                                                  key:LongString='';
                                                  iv:LongString=''
                                                 ):LongString;
begin
 Result:='';
 if (pwd='') then Exit;
 try
  key:=DbApiMasterKey(key);
  case mode of
   pem_NONE     : Result:=pwd;
   pem_Blowfish : Result:=EncryptText(pwd,key,iv,ek_Blowfish,em_CBC,df_Bin,df_Mime);
   pem_GOST     : Result:=EncryptText(pwd,key,iv,ek_Gost,em_CBC,df_Bin,df_Mime);
   pem_RC2      : Result:=EncryptText(pwd,key,iv,ek_RC2,em_CBC,df_Bin,df_Mime);
   pem_RC4      : Result:=EncryptText(pwd,key,iv,ek_RC4,em_CBC,df_Bin,df_Mime);
   pem_RC5      : Result:=EncryptText(pwd,key,iv,ek_RC5,em_CBC,df_Bin,df_Mime);
   pem_RC6      : Result:=EncryptText(pwd,key,iv,ek_RC6,em_CBC,df_Bin,df_Mime);
   pem_B64      : Result:=mime_encode(pwd);
   pem_HEX      : Result:=hex_encode(pwd);
   pem_OFF      : Result:=pwd;
  end;
 except
  on E:Exception do BugReport(E,nil,'TDbEngineAssistant.EncryptPassword');
 end;
end;

class function TDbEngineAssistant.DecryptPassword(pwd:LongString;
                                                  mode:Integer;
                                                  key:LongString='';
                                                  iv:LongString=''
                                                 ):LongString;
begin
 Result:='';
 if (pwd='') then Exit;
 try
  key:=DbApiMasterKey(key);
  case mode of
   pem_NONE     : Result:=pwd;
   pem_Blowfish : Result:=DecryptText(pwd,key,iv,ek_Blowfish,em_CBC,df_Mime,df_Bin);
   pem_GOST     : Result:=DecryptText(pwd,key,iv,ek_Gost,em_CBC,df_Mime,df_Bin);
   pem_RC2      : Result:=DecryptText(pwd,key,iv,ek_RC2,em_CBC,df_Mime,df_Bin);
   pem_RC4      : Result:=DecryptText(pwd,key,iv,ek_RC4,em_CBC,df_Mime,df_Bin);
   pem_RC5      : Result:=DecryptText(pwd,key,iv,ek_RC5,em_CBC,df_Mime,df_Bin);
   pem_RC6      : Result:=DecryptText(pwd,key,iv,ek_RC6,em_CBC,df_Mime,df_Bin);
   pem_B64      : Result:=mime_decode(pwd);
   pem_HEX      : Result:=hex_decode(pwd);
   pem_OFF      : Result:=pwd;
  end;
 except
  on E:Exception do BugReport(E,nil,'TDbEngineAssistant.DecryptPassword');
 end;
end;

class function TDbEngineAssistant.EncryptConnectionString(cs:LongString;
                                                          mode:Integer;
                                                          key:LongString='';
                                                          iv:LongString=''
                                                         ):LongString;
var pwd:LongString;
begin
 cs:=Trim(cs);
 Result:=cs;
 try
  key:=Trim(Key); iv:=Trim(iv);
  pwd:=FetchParam(cs,sia_Password);
  mode:=EnsureRange(mode,pem_MIN,pem_MAX);
  cs:=DropParam(cs,sia_PwdCryptMode+csSep+sia_PwdCryptKey+csSep+sia_PwdCryptIv);
  if (pwd<>'') and (mode in pem_Secure) then begin
   cs:=cs+ExtractWord(1,sia_PwdCryptMode,ScanSpaces)+'='+IntToStr(mode)+csSep;
   if (key<>'') and (key<>Trim(DbApiMasterKey))
   then cs:=cs+ExtractWord(1,sia_PwdCryptKey,ScanSpaces)+'='+key+csSep;
   if (iv<>'') then cs:=cs+ExtractWord(1,sia_PwdCryptIv,ScanSpaces)+'='+iv+csSep;
   cs:=SubstParam(cs,sia_Password,EncryptPassword(pwd,mode,key,iv));
  end;
  Result:=cs;
 except
  on E:Exception do BugReport(E,nil,'TDbEngineAssistant.EncryptConnectionString');
 end;
end;

class function TDbEngineAssistant.DecryptConnectionString(cs:LongString;
                                                          out mode:Integer;
                                                          out key:LongString;
                                                          out iv:LongString
                                                         ):LongString;
var pwd,pcm,uErrors:LongString;
begin
 cs:=Trim(cs);
 Result:=cs;
 try
  pwd:=''; pcm:=''; uErrors:='';
  // Try to detect DB URI & convert to cookies
  cs:=UriMan.Copy_DB_URI_As_Params(cs,uErrors);
  if IsNonEmptyStr(uErrors) then DebugLog(dlc_DbApiLog,uErrors);
  pwd:=FetchParam(cs,sia_Password);
  iv:=FetchParam(cs,sia_PwdCryptIv);
  key:=FetchParam(cs,sia_PwdCryptKey);
  pcm:=FetchParam(cs,sia_PwdCryptMode);
  mode:=EnsureRange(StrToIntDef(pcm,pem_NONE),pem_MIN,pem_MAX);
  cs:=DropParam(cs,sia_PwdCryptMode+csSep+sia_PwdCryptKey+csSep+sia_PwdCryptIv);
  if (pwd<>'') and (mode in pem_Secure) then begin
   pwd:=DecryptPassword(pwd,mode,key,iv);
   Result:=SubstParam(cs,sia_Password,pwd);
  end else begin
   mode:=pem_NONE; key:=''; iv:='';
   Result:=cs;
  end;
 except
  on E:Exception do BugReport(E,nil,'TDbEngineAssistant.DecryptConnectionString');
 end;
end;

class function TDbEngineAssistant.DecryptConnectionString(cs:LongString):LongString;
var mode:Integer; key,iv:LongString;
begin
 mode:=0; key:=''; iv:='';
 Result:=DecryptConnectionString(cs,mode,key,iv);
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_dbcon;
begin
 DbCon.Ok;
 DbCon.UsesSystemDbFieldtypenames:=True;
end;

procedure Free_crw_dbcon;
begin
 Kill(TheDbCon);
end;

initialization

 Init_crw_dbcon;

finalization

 Free_crw_dbcon;

end.

//////////////
// END OF FILE
//////////////

