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

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

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Engine agent for Lazarus SQLDB.                                            //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20250609 - Created by A.K.                                                 //
// 20250718 - TSqlDbAssistant                                                 //
////////////////////////////////////////////////////////////////////////////////

unit _crw_dblaz; //  DB Lazarus - interface for Lazarus SQLDB.

{$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, db,  sqldb, sqldblib, bufdataset,
 mssqlconn, pqconnection, oracleconnection, odbcconn,
 mysql40conn, mysql41conn, mysql50conn, mysql51conn,
 mysql55conn, mysql56conn, mysql57conn, mysql80conn,
 sqlite3conn, ibconnection, lclversion,
 xmldatapacketreader,
 Forms, Controls, Graphics, Dialogs,
 _crw_alloc, _crw_ef, _crw_str, _crw_fio, _crw_dynar,
 _crw_dbglog, _crw_proc, _crw_rtc, _crw_uri, _crw_dbcon;

const /////////////////// SQLDB engine identifiers
 db_engine_code_sqldb   = _crw_dbcon.db_engine_code_sqldb; // SQLDB engine code
 db_engine_name_sqldb   = _crw_dbcon.db_engine_name_sqldb; // SQLDB engine name

////////////////////////////////////////////////////////////////////////////////
// From SqlDbAssistant:
// KnownProviders=IB,MSSQL,MySQL40,MySQL41,MySQL50,MySQL51,MySQL55,
//                MySQL56,MySQL57,MySQL80,ODBC,Oracle,PQ,SQLite3,Sybase
// KnownConnTypes=Firebird,MSSQLServer,MySQL 4.0,MySQL 4.1,MySQL 5.0,MySQL 5.1,
//                MySQL 5.5,MySQL 5.6,MySQL 5.7,MySQL 8.0,ODBC,Oracle,
//                PostgreSQL,SQLite3,Sybase
////////////////////////////////////////////////////////////////////////////////

type
 TSqlStatementTypes = set of TStatementType;

 ///////////////////////////////////////////////////////////////////////////////
 // TSqlDbConnecter - SQLDB connector, include SQL Connection,Transaction,Query.
 ///////////////////////////////////////////////////////////////////////////////
type
 TSqlDbApi = class;
 TSqlDbRecorder = class;
 TSqlDbCommander = class;
 TSqlDbConnecter = class;
 ESqlDbApi = class(Exception);
 TSqlDbMasterObject = class(TMasterObject)
 private
  myParent:TObject;
  function GetParent:TObject;
  procedure SetParent(aParent:TObject);
 public
  property Parent:TObject read GetParent write SetParent;
 protected
  procedure BugReport(E:Exception; O:TObject; const S:LongString);
 end;
 TSqlDbApi = class(TSqlDbMasterObject)
 private
  myConnection  : TSqlConnection;
  myDataSource  : TDataSource;
  myQuery       : TSQLQuery;
  myTrans       : TSQLTransaction;
  myConStrng    : LongString;
  myProvider    : LongString;
  myErrorsList  : TText;
  myErrorsCount : Integer;
  myIsLinked    : Boolean;
 private
  function  GetVerbose:Boolean;
  procedure SetVerbose(aVerbose:Boolean);
  function  GetErrorsList:TText;
  function  GetErrorsCount:Integer;
  procedure SetErrorsCount(aCount:Integer);
  function  GetProvider:LongString;
  function  GetConnection:TSqlConnection;
  function  GetDataSource:TDataSource;
  function  GetQuery:TSQLQuery;
  function  GetTrans:TSQLTransaction;
  function  GetConStrng:LongString;
  procedure SetConStrng(arg:LongString);
  function  GetIsLinked:Boolean;
  function  LinkSqlObjects:Boolean;
  procedure UnlinkSqlObjects;
  procedure FreeSqlObjects;
 public
  constructor Create;
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  property Verbose:Boolean           read GetVerbose write SetVerbose;
  property ErrorsList:TText          read GetErrorsList;
  property ErrorsCount:Integer       read GetErrorsCount write SetErrorsCount;
  property IsLinked:Boolean          read GetIsLinked;
  property Provider:LongString       read GetProvider;
  property Connection:TSqlConnection read GetConnection;
  property DataSource:TDataSource    read GetDataSource;
  property Query:TSQLQuery           read GetQuery;
  property Trans:TSQLTransaction     read GetTrans;
  property ConStrng:LongString       read GetConStrng write SetConStrng;
 public
  function IncErrorsCount:Integer;
  function MakeConnection(const aConnStr:LongString):Boolean;
 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_Role:LongString;
  class function sia_Verbose:LongString;
  class function sia_Internals:LongString;
 public // Helper functions
  class procedure ErrorNilReference(const Where:LongString);
  class procedure ErrorNotSupported(const Where:LongString);
  class procedure ParameterUnused(x:Boolean); overload;
  class procedure ParameterUnused(x:Integer); overload;
  class procedure ParameterUnused(x:LongString); overload;
 public
  class var DefVerbose:Boolean;
  class var RaiseOnNilReference:Boolean;
  class var RaiseOnResetProvider:Boolean;
  class var RaiseOnResetConStrng:Boolean;
 end;
 //////////////////
 // TSqlDbConnecter
 //////////////////
 TSqlDbConnecter = class(TSqlDbMasterObject)
 private
  myApi       : TSqlDbApi;
  myCommander : TSqlDbCommander;
  myRecorder  : TSqlDbRecorder;
 private
  function  GetApi:TSqlDbApi;
  function  GetProvider:LongString;
  procedure SetProvider(p:LongString);
  function  GetRecorder:TSqlDbRecorder;
  function  GetCommander:TSqlDbCommander;
 public
  constructor Create(aApi:TSqlDbApi);
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  property Api:TSqlDbApi read GetApi;
  property Provider:LongString read GetProvider write SetProvider;
  property Recorder:TSqlDbRecorder read GetRecorder;
  property Commander:TSqlDbCommander read GetCommander;
 public
  function Open(Opt:Integer=0):Boolean;
  function Close:Boolean;
  function Cancel:Boolean;
  function BeginTrans:Integer;
  function CommitTrans(aRetaining:Boolean=False):Boolean;
  function RollbackTrans(aRetaining:Boolean=False):Boolean;
  function Execute(arg:LongString; opt:Integer=0; rof:Boolean=True):TSqlDbRecorder;
  function State:Integer;
  function Active:Boolean;
 private
  function  GetQuerySQLText:LongString;
  procedure SetQuerySQLText(const arg:LongString);
  function  GetQuerySQLType:TStatementType;
 public
  property  QuerySQLText:LongString read GetQuerySQLText write SetQuerySQLText;
  property  QuerySQLType:TStatementType read GetQuerySQLType;
 public
  function  CreateDatabase:Boolean;
 public
  procedure OnDbLog(Sender:TSQLConnection; EventType:TDBEventType; const Msg:String);
 public
  class var DefLogEvents:TDBEventTypes;
 private
  myPropertiesWanted:LongString;
 private // Properties
  function  GetVersion:LongString;
  function  GetDefaultDataBase:LongString;
  procedure SetDefaultDataBase(aDatabase:LongString);
  function  GetProperties:LongString;
  procedure SetProperties(p:LongString);
 private // ADO simulation internals
  function  GetConnectionTimeout:Integer;
  procedure SetConnectionTimeout(t:Integer);
  function  GetCommandTimeout:Integer;
  procedure SetCommandTimeout(t:Integer);
  function  GetIsolationLevel:Integer;
  procedure SetIsolationLevel(t:Integer);
  function  GetMode:Integer;
  procedure SetMode(m:Integer);
  function  GetCursorLocation:Integer;
  procedure SetCursorLocation(p:Integer);
  function  GetAttributes:Integer;
  procedure SetAttributes(a:Integer);
 public  // ADO simulation properties
  property  ConnectionTimeout:Integer read GetConnectionTimeout write SetConnectionTimeout;
  property  CommandTimeout:Integer read GetCommandTimeout write SetCommandTimeout;
  property  IsolationLevel:Integer read GetIsolationLevel write SetIsolationLevel;
  property  DefaultDataBase:LongString read GetDefaultDataBase write SetDefaultDataBase;
  property  Version:LongString read GetVersion;
  property  Properties:LongString read GetProperties write SetProperties;
  property  Mode:Integer read GetMode write SetMode;
  property  CursorLocation:Integer read GetCursorLocation write SetCursorLocation;
  property  Attributes:Integer read GetAttributes write SetAttributes;
 public
  function  GetErrors(aClear:Boolean=False):LongString;
  function  GetErrorsCount(aClear:Boolean=False):Integer;
 public // Helper functions
  class function FieldTypeToCode(T:TFieldType):Integer;
 public
  class var SelectableStatementTypes:TSqlStatementTypes;
 private
  mySaveNullSubs:LongString;
  function  GetSaveNullSubs:LongString;
  procedure SetSaveNullSubs(s:LongString);
 public
  property  SaveNullSubs:LongString read GetSaveNullSubs write SetSaveNullSubs;
  class var DefSaveNullSubs:LongString;
  class var SaveAsModeFlags:Integer;
 end;
 //////////////////
 // TSqlDbCommander
 //////////////////
 TSqlDbCommander = class(TSqlDbMasterObject)
 private
  myConnecter:TSqlDbConnecter;
 private
  function  GetConnecter:TSqlDbConnecter;
 public
  constructor Create(aConnecter:TSqlDbConnecter);
 public
  property Connecter:TSqlDbConnecter read GetConnecter;
 public
  function  State:Integer;
  function  Active:Boolean;
  function  Cancel:Boolean;
 private
  function  GetCommandType:Integer;
  procedure SetCommandType(t:Integer);
  function  GetCommandText:LongString;
  procedure SetCommandText(aCommand:LongString);
 public
  property  CommandType:Integer read GetCommandType write SetCommandType;
  property  CommandText:LongString read GetCommandText write SetCommandText;
 end;
 /////////////////
 // TSqlDbRecorder
 /////////////////
 TSqlDbRecorder = class(TSqlDbMasterObject)
 private
  myConnecter:TSqlDbConnecter;
 private
  function  GetConnecter:TSqlDbConnecter;
  function  GetFieldsCount:Integer;
  function  GetFieldsNames(i:Integer):LongString;
  function  GetFieldsTypes(i:Integer):Integer;
  function  GetFieldsAsString(i:Integer):LongString;
  procedure SetFieldsAsString(i:Integer; const v:LongString);
  function  GetFieldsAsInteger(i:Integer):Integer;
  procedure SetFieldsAsInteger(i:Integer; v:Integer);
  function  GetFieldsAsFloat(i:Integer):Double;
  procedure SetFieldsAsFloat(i:Integer; v:Double);
 public
  constructor Create(aConnecter:TSqlDbConnecter);
 public
  property  Connecter:TSqlDbConnecter read GetConnecter;
 public // DB API functions
  function  BOF:Boolean;
  function  EOF:Boolean;
  function  MoveFirst:Boolean;
  function  MoveLast:Boolean;
  function  MoveNext:Boolean;
  function  MovePrevious:Boolean;
  function  Close:Boolean;
  function  Open(opt:Integer):Boolean;
  function  Update:Boolean;
  function  Cancel:Boolean;
  function  CancelUpdate:Boolean;
  function  State:Integer;
  function  Active:Boolean;
  function  Requery(opt:Integer=0):Boolean;
  function  Resync(aff,res:Integer):Boolean;
  function  Delete(aff:Integer):Boolean;
  function  AddNew(arg:LongString):Boolean;
  function  IndexOfField(const id:LongString):Integer;
  function  GetString(n:Integer; coldel,rowdel,NullSubs:LongString):LongString;
  function  Supports(CursorOptions:Integer):Boolean;
 public // Data access properties
  property  FieldsCount:Integer read GetFieldsCount;
  property  FieldsNames[i:Integer]:LongString read GetFieldsNames;
  property  FieldsTypes[i:Integer]:Integer read GetFieldsTypes;
  property  FieldsAsString[i:Integer]:LongString read GetFieldsAsString write SetFieldsAsString;
  property  FieldsAsInteger[i:Integer]:Integer read GetFieldsAsInteger write SetFieldsAsInteger;
  property  FieldsAsFloat[i:Integer]:Double read GetFieldsAsFloat write SetFieldsAsFloat;
 private
  function  GetSource:LongString;
  procedure SetSource(aSource:LongString);
 public
  property  Source:LongString read GetSource write SetSource;
 private
  function  GetAbsolutePage:Integer;
  procedure SetAbsolutePage(p:Integer);
  function  GetAbsolutePosition:Integer;
  procedure SetAbsolutePosition(p:Integer);
  function  GetBookmark:Integer;
  procedure SetBookmark(p:Integer);
  function  GetCacheSize:Integer;
  procedure SetCacheSize(p:Integer);
  function  GetCursorType:Integer;
  procedure SetCursorType(p:Integer);
  function  GetEditMode:Integer;
  procedure SetEditMode(p:Integer);
  function  GetFilter:LongString;
  procedure SetFilter(v:LongString);
  function  GetIndex:LongString;
  procedure SetIndex(v:LongString);
  function  GetLockType:Integer;
  procedure SetLockType(p:Integer);
  function  GetMarshalOptions:Integer;
  procedure SetMarshalOptions(p:Integer);
  function  GetMaxRecords:Integer;
  procedure SetMaxRecords(p:Integer);
  function  GetPageCount:Integer;
  function  GetPageSize:Integer;
  procedure SetPageSize(p:Integer);
  function  GetRecordCount:Integer;
  function  GetSort:LongString;
  procedure SetSort(v:LongString);
  function  GetStatus:Integer;
  function  GetStayInSync:Boolean;
  procedure SetStayInSync(v:Boolean);
  function  GetCursorLocation:Integer;
  procedure SetCursorLocation(p:Integer);
 public
  property  AbsolutePage:Integer read GetAbsolutePage write SetAbsolutePage;
  property  AbsolutePosition:Integer read GetAbsolutePosition write SetAbsolutePosition;
  property  Bookmark:Integer read GetBookmark write SetBookmark;
  property  CacheSize:Integer read GetCacheSize write SetCacheSize;
  property  CursorType:Integer read GetCursorType write SetCursorType;
  property  EditMode:Integer read GetEditMode write SetEditMode;
  property  Filter:LongString read GetFilter write SetFilter;
  property  Index:LongString read GetIndex write SetIndex;
  property  LockType:Integer read GetLockType write SetLockType;
  property  MarshalOptions:Integer read GetMarshalOptions write SetMarshalOptions;
  property  MaxRecords:Integer read GetMaxRecords write SetMaxRecords;
  property  PageCount:Integer read GetPageCount;
  property  PageSize:Integer read GetPageSize write SetPageSize;
  property  RecordCount:Integer read GetRecordCount;
  property  Sort:LongString read GetSort write SetSort;
  property  Status:Integer read GetStatus;
  property  StayInSync:Boolean read GetStayInSync write SetStayInSync;
  property  CursorLocation:Integer read GetCursorLocation write SetCursorLocation;
 public
  function  Save(Destination:LongString; PersistFormat:Integer):Boolean;
  function  SaveAsText(Dest,coldel,rowdel,NullSubs:LongString;Quote:Char):Boolean;
 end;
 //////////////////
 // TSqlDbAssistant
 //////////////////
 TSqlDbAssistant = class(TDbEngineAssistant)
 private
  myLoaders:TStringList;
  myProviders:TStringList;
  myConnTypes:TStringList;
  myNickNames:TStringList;
  myKnownProviders:LongString;
  myKnownConnTypes:LongString;
 private
  procedure InitLoaders;
  procedure FreeLoaders;
 public
  constructor Create;
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  function  KnownProviders:LongString;
  function  KnownConnTypes:LongString;
  function  FindCodePage(aCharSet:LongString):Word;
  function  FindCharSet(aCharSet:LongString):LongString;
  function  FindProvider(arg:LongString):LongString;
  function  FindConnType(arg:LongString):LongString;
  function  FindConnectionDef(arg:LongString):TConnectionDef;
  function  FindLoadedLibraryName(arg:LongString):LongString;
  function  FindConnectionClass(arg:LongString):TSQLConnectionClass;
  function  ConnTypeToProvider(ct:LongString):LongString;
  function  ProviderToConnType(pv:LongString):LongString;
  function  FindLoader(const arg:LongString):TSQLDBLibraryLoader;
  function  IsPreloadedLibrary(const aProvider:LongString):Boolean;
  function  PreloadedLibrary(const aProvider:LongString):LongString;
  function  PreloadLibrary(const aProvider:LongString):Boolean;
  function  GetConnectionListAsText:LongString;
 public
  class var UsesPreloadLibraries:Boolean;
 end;

function  NewSqlDbConnecter(const aConnStr:LongString):TSqlDbConnecter;
procedure Kill(var TheObject:TSqlDbConnecter); overload;
procedure Kill(var TheObject:TSqlDbCommander); overload;
procedure Kill(var TheObject:TSqlDbRecorder); overload;
procedure Kill(var TheObject:TSqlDbApi); overload;

function AllSqlDbConnecters:TObjectStorage;

function SqlDbAssistant:TSqlDbAssistant;

 ////////////////////////////////////////////////////////
 // Get list of ODBC drivers by command 'odbcinst -q -d'.
 // This feature depends on deb package 'odbcinst'.
 ////////////////////////////////////////////////////////
function ReadUnixOdbcDriverList(Update:Boolean=False):LongString;

 /////////////////////////////////////////////////////
 // Get list of ODBC drivers by command 'odbcinst -j'.
 // This feature depends on deb package 'odbcinst'.
 // Return Name=Value list with names from list of
 // UnixOdbcConfigParamsNames.
 /////////////////////////////////////////////////////
function ReadUnixOdbcConfigParams(Update:Boolean=False):LongString;

 /////////////////////////////////////////////////////
 // List of Names of UnixODBC configuration parameters
 /////////////////////////////////////////////////////
const
 UnixOdbcConfigParamsNames='DRIVERS'+EOL
                          +'SYSTEM DATA SOURCES'+EOL
                          +'FILE DATA SOURCES'+EOL
                          +'USER DATA SOURCES'+EOL
                          +'SQLULEN Size'+EOL
                          +'SQLLEN Size'+EOL
                          +'SQLSETPOSIROW Size'+EOL;

 ////////////////////
 // DebugLog channels
 ////////////////////
function dlc_SqlDbBug:Integer;
function dlc_SqlDbLog:Integer;

implementation

uses
 _crw_dbapi;

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

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

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

function FormatExceptionMsg(E:Exception; O:TObject; const S:LongString):LongString;
var exc,who,msg:LongString;
begin
 if Assigned(E) then exc:=E.ClassName else exc:='ENil';
 if Assigned(O) then who:=O.ClassName else who:='Unknown';
 Result:='Exception '+DoubleAngleQuotedStr(exc)+' from '+DoubleAngleQuotedStr(who);
 if (S<>'') then begin
  msg:=S;
  if StartsText('@!',S) then msg:=SkipWords(1,S,ScanSpaces);
  if (msg<>'') then Result:=Result+' note '+DoubleAngleQuotedStr(msg);
 end;
 if Assigned(E) and (E.Message<>'')
 then Result:=Result+' hint '+DoubleAngleQuotedStr(E.Message);
 if PosEol(Result)>0 then Result:=StringReplace(Result,EOL,' ',[rfReplaceAll]);
end;

procedure BugReport(E:Exception; O:TObject; const S:LongString);
var Api:TSqlDbApi; verb:Integer; msg:LongString;
begin
 TDbConnection.IncTotalBugsCount;
 if (StrFetch(S,1)='@')
 then _crw_alloc.BugReport(E,O,S)
 else _crw_alloc.BugReport(E,O,db_bugreport_prefix+S);
 if Assigned(O) then begin
  if (O is TSqlDbApi) then Api:=TSqlDbApi(O) else
  if (O is TSqlDbConnecter) then Api:=TSqlDbConnecter(O).Api else
  if (O is TSqlDbCommander) then Api:=TSqlDbCommander(O).Connecter.Api else
  if (O is TSqlDbRecorder) then Api:=TSqlDbRecorder(O).Connecter.Api else Api:=nil;
  Api.IncErrorsCount;
  verb:=BoolToInt(Api.Verbose)+BoolToInt(DebugLogEnabled(dlc_SqlDbBug))*2;
  if (verb<>0) then begin
   msg:=FormatExceptionMsg(E,O,S);
   if HasFlags(verb,1) then Api.ErrorsList.AddLn(msg);
   if HasFlags(verb,2) then DebugLog(dlc_SqlDbBug,msg);
  end;
 end;
end;

function  NewSqlDbConnecter(const aConnStr:LongString):TSqlDbConnecter;
var Api:TSqlDbApi;
begin
 Result:=nil;
 try
  Api:=TSqlDbApi.Create;
  if not Api.MakeConnection(aConnStr) then Kill(Api);
  if Assigned(Api) then Result:=TSqlDbConnecter.Create(Api);
 except
  on E:Exception do BugReport(E,nil,'NewSqlDbConnecter');
 end;
end;

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

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

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

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

function ReadUnixOdbcDriverList(Update:Boolean=False):LongString;
var ans,line:LongString; i:Integer;
const cmd='odbcinst -q -d';
const drv:LongString='';
begin
 Result:='';
 if IsUnix then
 try
  ans:='';
  if Update then drv:='';
  if (drv<>'') then Exit(drv);
  if RunCommand(cmd,ans) then
  for i:=1 to WordCount(ans,EolnDelims) do begin
   line:=Trim(ExtractWord(i,ans,EolnDelims));
   if StartsText('[',line) and EndsText(']',line)
   then Result:=Result+Copy(line,2,length(line)-2)+EOL;
  end;
  drv:=Result;
 except
  on E:Exception do BugReport(E,nil,'ReadUnixOdbcDriverList');
 end;
end;

function ReadUnixOdbcConfigParams(Update:Boolean=False):LongString;
var ans,line,sn,sv:LongString; i:Integer;
const cmd='odbcinst -j';
const par:LongString='';
begin
 Result:='';
 if IsUnix then
 try
  ans:=''; sn:=''; sv:='';
  if Update then par:='';
  if (par<>'') then Exit(par);
  if RunCommand(cmd,ans) then
  for i:=1 to WordCount(ans,EolnDelims) do begin
   line:=Trim(ExtractWord(i,ans,EolnDelims));
   if ExtractNameValuePair(line,sn,sv,':')>0 then
   if IsNonEmptyStr(sn) and IsNonEmptyStr(sv) then
   Result:=Result+TrimRightChars(sn,ScanSpaces+['.'])+'='+sv+EOL;
  end;
  par:=Result;
 except
  on E:Exception do BugReport(E,nil,'ReadUnixOdbcConfigParameters');
 end;
end;

/////////////////////////////////
// TSqlDbAssistant implementation
/////////////////////////////////

constructor TSqlDbAssistant.Create;
begin
 inherited Create;
 myLoaders:=TStringList.Create;
 myProviders:=TStringList.Create;
 myConnTypes:=TStringList.Create;
 myNickNames:=TStringList.Create;
 myKnownProviders:='';
 myKnownConnTypes:='';
end;

destructor TSqlDbAssistant.Destroy;
begin
 Kill(myLoaders);
 Kill(myProviders);
 Kill(myConnTypes);
 Kill(myNickNames);
 myKnownProviders:='';
 myKnownConnTypes:='';
 inherited Destroy;
end;

procedure TSqlDbAssistant.AfterConstruction;
begin
 inherited AfterConstruction;
 InitLoaders;
end;

procedure TSqlDbAssistant.BeforeDestruction;
begin
 FreeLoaders;
 inherited BeforeDestruction;
end;

procedure TSqlDbAssistant.InitLoaders;
var i:Integer; Items:TStringList; ct,pv:LongString;
var Loader:TSQLDBLibraryLoader; cd:TConnectionDef;
 procedure AddNickNames(arg:LongString);
 var i:Integer; Target,Nicks,Nick:LongString;
 var Delims:TCharSet;
 begin
  Delims:=ScanSpaces-JustBlanks;
  Target:=''; Nicks:=''; Nick:='';
  if (ExtractNameValuePair(arg,Target,Nicks)>0) then
  for i:=1 to WordCount(Nicks,Delims) do begin
   Nick:=Trim(ExtractWord(i,Nicks,Delims));
   if IsEmptyStr(Nick) then continue;
   myNickNames.Values[Nick]:=Target;
  end;
 end;
begin
 if Assigned(Self) then
 try
  Items:=TStringList.Create;
  try
   myLoaders.Clear;
   myProviders.Clear;
   myConnTypes.Clear;
   myNickNames.Clear;
   myKnownProviders:='';
   myKnownConnTypes:='';
   sqldb.GetConnectionList(Items);
   for i:=0 to Items.Count-1 do begin
    ct:=Items[i]; if (ct='') then continue;
    Loader:=TSQLDBLibraryLoader.Create(nil);
    Loader.ConnectionType:=ct;
    myLoaders.AddObject(ct,Loader);
    cd:=sqldb.GetConnectionDef(ct);
    if Assigned(cd) and Assigned(cd.ConnectionClass)
    then pv:=cd.ConnectionClass.ClassName else pv:='';
    // Assume ClassName string is T<Provider>Connection
    if StartsText('T',pv) and EndsText('Connection',pv)
    then pv:=Copy(pv,2,Length(pv)-Length('TConnection'));
    if (pv<>'') then myProviders.Values[ct]:=pv;
    if (pv<>'') then myConnTypes.Values[pv]:=ct;
    if (pv<>'') then myKnownProviders:=myKnownProviders+pv+EOL;
    if (pv<>'') then myKnownConnTypes:=myKnownConnTypes+ct+EOL;
   end;
   AddNickNames('MySQL80=MySQL;');
   AddNickNames('SQLite3=SQLite;SQLite3;');
   AddNickNames('IB=IB;FB;Interbase;Firebird;');
   AddNickNames('PQ=PQ;PostgreSQL;Postgres;PostgresSQL;');
   AddNickNames('IB=.fdb;');
   AddNickNames('SQLite3=.db;.sdb;.sqlite;.db3;.s3db;');
   AddNickNames('SQLite3=.sqlite3;.sl3;.db2;.s2db;.sqlite2;.sl2;');
  finally
   Kill(Items);
  end;
 except
  on E:Exception do BugReport(E,Self,'InitLoaders');
 end;
end;

procedure TSqlDbAssistant.FreeLoaders;
var i:Integer; Obj:TObject;
begin
 if Assigned(Self) then
 try
  for i:=myLoaders.Count-1 downto 0 do begin
   Obj:=myLoaders.Objects[i];
   if (Obj is TSQLDBLibraryLoader)
   then FreeAndNil(Obj);
   myLoaders.Delete(i);
  end;
 except
  on E:Exception do BugReport(E,Self,'FreeLoaders');
 end;
end;

function TSqlDbAssistant.KnownProviders:LongString;
begin
 if Assigned(Self)
 then Result:=myKnownProviders
 else Result:='';
end;

function TSqlDbAssistant.KnownConnTypes:LongString;
begin
 if Assigned(Self)
 then Result:=myKnownConnTypes
 else Result:='';
end;

 // map connection CharSet to corresponding local CodePage
 // do not set FCodePage to CP_ACP if FCodePage = DefaultSystemCodePage
 // aliases listed here are commonly used, but not recognized by CodePageNameToCodePage()
function TSqlDbAssistant.FindCodePage(aCharSet:LongString):Word;
begin
 Result:=CP_NONE;
 if (WordIndex(aCharSet,'win1250,cp1250',ScanSpaces)>0) then Result:=1250 else
 if (WordIndex(aCharSet,'win1251,cp1251',ScanSpaces)>0) then Result:=1251 else
 if (WordIndex(aCharSet,'utf8,utf-8,utf8mb4',ScanSpaces)>0) then Result:=CP_UTF8 else
 if (WordIndex(aCharSet,'win1252,cp1252,latin1,iso8859_1',ScanSpaces)>0) then Result:=1252 else begin
  Result:=CodePageNameToCodePage(aCharSet);
  if (Result=CP_NONE) then Result:=CP_ACP;
 end;
end;

function TSqlDbAssistant.FindCharSet(aCharSet:LongString):LongString;
begin
 Result:=CodePageToCodePageName(FindCodePage(aCharSet));
end;

function TSqlDbAssistant.FindProvider(arg:LongString):LongString;
var i,Iter:Integer;
begin
 Result:='';
 if Assigned(Self) then
 try
  arg:=Trim(arg);
  if (arg<>'') then
  for Iter:=1 to 2 do begin
   i:=myConnTypes.IndexOfName(arg);
   if (i>=0) then Exit(myConnTypes.Names[i]);
   i:=myProviders.IndexOfName(arg);
   if (i>=0) then Exit(myProviders.ValueFromIndex[i]);
   if (myNickNames.IndexOfName(arg)>=0)
   then arg:=myNickNames.Values[arg]
   else break;
  end;
 except
  on E:Exception do BugReport(E,Self,'FindProvider');
 end;
end;

function TSqlDbAssistant.FindConnType(arg:LongString):LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  Result:=ProviderToConnType(FindProvider(arg));
 except
  on E:Exception do BugReport(E,Self,'FindConnType');
 end;
end;

function TSqlDbAssistant.FindConnectionDef(arg:LongString):TConnectionDef;
var ct:LongString;
begin
 Result:=nil;
 if Assigned(Self) then
 try
  ct:=FindConnType(arg);
  if (ct<>'') then Result:=GetConnectionDef(ct);
 except
  on E:Exception do BugReport(E,Self,'FindConnectionDef');
 end;
end;

function TSqlDbAssistant.FindLoadedLibraryName(arg:LongString):LongString;
var ct:LongString; cd:TConnectionDef;
begin
 Result:='';
 if Assigned(Self) then
 try
  ct:=FindConnType(arg);
  cd:=GetConnectionDef(ct);
  if Assigned(cd) then Result:=cd.LoadedLibraryName;
 except
  on E:Exception do BugReport(E,Self,'FindLoadedLibraryName');
 end;
end;

function TSqlDbAssistant.FindConnectionClass(arg:LongString):TSQLConnectionClass;
var ct:LongString; cd:TConnectionDef;
begin
 Result:=nil;
 if Assigned(Self) then
 try
  ct:=FindConnType(arg);
  cd:=GetConnectionDef(ct);
  if Assigned(cd) then Result:=cd.ConnectionClass;
 except
  on E:Exception do BugReport(E,Self,'FindConnectionClass');
 end;
end;

function TSqlDbAssistant.ConnTypeToProvider(ct:LongString):LongString;
begin
 if Assigned(Self)
 then Result:=myProviders.Values[ct]
 else Result:='';
end;

function TSqlDbAssistant.ProviderToConnType(pv:LongString):LongString;
begin
 if Assigned(Self)
 then Result:=myConnTypes.Values[pv]
 else Result:='';
end;

function TSqlDbAssistant.FindLoader(const arg:LongString):TSQLDBLibraryLoader;
var i:Integer; ct:LongString; Obj:TObject;
begin
 Result:=nil;
 if Assigned(Self) then
 try
  ct:=FindConnType(arg);
  i:=myLoaders.IndexOf(ct);
  if (i>=0) then Obj:=myLoaders.Objects[i] else Obj:=nil;
  if (Obj is TSQLDBLibraryLoader) then Result:=TSQLDBLibraryLoader(Obj);
 except
  on E:Exception do BugReport(E,Self,'FindLoader');
 end;
end;

procedure ValidateLibraryName(Loader:TSQLDBLibraryLoader);
begin
 if Assigned(Loader) then begin
  if IsUnix then begin
   if SameText(Loader.ConnectionType,'Firebird') then begin
    if StartsText('libfbclient.so',Loader.LibraryName)
    then Loader.LibraryName:='libfbclient.so';
   end;
  end;
 end;
end;

function TSqlDbAssistant.IsPreloadedLibrary(const aProvider:LongString):Boolean;
var Loader:TSQLDBLibraryLoader; pv:LongString;
begin
 Result:=False;
 if Assigned(Self) then
 try
  pv:=Trim(aProvider);
  Loader:=FindLoader(pv);
  if not Assigned(Loader) then Exit;
  if Loader.Enabled then Exit(True);
 except
  on E:Exception do BugReport(E,Self,'IsPreloadedLibrary');
 end;
end;

function TSqlDbAssistant.PreloadedLibrary(const aProvider:LongString):LongString;
var Loader:TSQLDBLibraryLoader; pv:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  pv:=Trim(aProvider);
  Loader:=FindLoader(pv);
  if not Assigned(Loader) then Exit;
  Result:=Loader.LibraryName;
 except
  on E:Exception do BugReport(E,Self,'PreloadedLibrary');
 end;
end;

function TSqlDbAssistant.PreloadLibrary(const aProvider:LongString):Boolean;
var Loader:TSQLDBLibraryLoader; pv,ct:LongString; cd:TConnectionDef;
begin
 Result:=False;
 if Assigned(Self) then
 try
  pv:=Trim(aProvider);
  Loader:=FindLoader(pv);
  if not Assigned(Loader) then Exit;
  if Loader.Enabled then Exit(True); // Already preloaded
  ct:=ProviderToConnType(pv); if IsEmptyStr(ct) then Exit;
  cd:=sqldb.GetConnectionDef(ct); if not Assigned(cd) then Exit;
  if (cd.LoadedLibraryName<>'')
  then Loader.LibraryName:=cd.LoadedLibraryName
  else ValidateLibraryName(Loader);
  Loader.Enabled:=True;
  Result:=Loader.Enabled;
  if DebugLogEnabled(dlc_SqlDbLog)
  then DebugLog(dlc_SqlDbLog,'PreloadLibrary '+pv+'='+Loader.LibraryName);
 except
  on E:Exception do BugReport(E,Self,'PreloadLibrary');
 end;
end;

function TSqlDbAssistant.GetConnectionListAsText:LongString;
var Lines,Items:TStringList; i,pf:Integer; Item:TConnectionDef;
var line,ct,pv,cc,dl,ll,dn,pl:LongString; Loader:TSQLDBLibraryLoader;
 function Qs(const s:LongString):LongString;
 begin
  Result:=s;
  if (s='') or HasChars(s,ScanSpaces)
  then Result:=AnsiQuotedStr(s,QuoteMark);
 end;
begin
 Result:='';
 try
  Items:=TStringList.Create;
  Lines:=TStringList.Create;
  try
   sqldb.GetConnectionList(Items);
   for i:=0 to Items.Count-1 do begin
    ct:=Items[i];
    Item:=sqldb.GetConnectionDef(ct);
    if not Assigned(Item) then Continue;
    Loader:=FindLoader(ct);
    if not Assigned(Loader) then Continue;
    if not Assigned(Item.ConnectionClass) then Continue;
    cc:=Item.ConnectionClass.ClassName;
    dl:=Item.DefaultLibraryName;
    ll:=Item.LoadedLibraryName;
    dn:=Item.Description;
    pv:=ConnTypeToProvider(ct);
    if IsEmptyStr(pv) then Continue;
    pf:=BoolToInt(IsPreloadedLibrary(ct));
    pl:=Loader.LibraryName;
    line:=Format('%s=%s,%d,%s,%s,%s,%s,%s',
                [ct,Qs(pv),pf,Qs(pl),Qs(cc),Qs(dl),Qs(ll),Qs(dn)]);
    Lines.Add(line);
   end;
   Result:=Lines.Text;
  finally
   Kill(Lines);
   Kill(Items);
  end;
 except
  on E:Exception do _crw_dblaz.BugReport(E,nil,'GetConnectionListAsText');
 end;
end;

function SqlDbAssistant:TSqlDbAssistant;
const pm:TSqlDbAssistant=nil;
begin
 if not Assigned(pm) then begin
  pm:=TSqlDbAssistant.Create;
  pm.Master:=@pm;
 end;
 Result:=pm;
end;

////////////////////////////////////
// TSqlDbMasterObject implementation
////////////////////////////////////

function TSqlDbMasterObject.GetParent:TObject;
begin
 if Assigned(Self)
 then Result:=myParent
 else Result:=nil;
end;

procedure TSqlDbMasterObject.SetParent(aParent:TObject);
begin
 if Assigned(Self) and (aParent is TDbConnection)
 then myParent:=aParent;
end;

procedure TSqlDbMasterObject.BugReport(E:Exception; O:TObject; const S:LongString);
var Root:TDbConnection; BgPref:LongString;
begin
 Root:=nil; BgPref:='';
 if Assigned(Self) then begin
  if (myParent is TDbConnection)
  then Root:=TDbConnection(myParent);
  if Assigned(Root) then begin
   BgPref:=Root.Control('BugReportPrefix');
   Root.IncBugsCount;
  end;
 end;
 if (Self=nil)
 then _crw_dblaz.BugReport(E,O,S)
 else _crw_dblaz.BugReport(E,O,BgPref+S);
end;

///////////////////////////
// TSqlDbApi implementation
///////////////////////////

constructor TSqlDbApi.Create;
begin
 inherited Create;
 Verbose:=DefVerbose;
end;

destructor TSqlDbApi.Destroy;
begin
 myConStrng:='';
 myProvider:='';
 FreeSqlObjects;
 Kill(myErrorsList);
 inherited Destroy;
end;

procedure TSqlDbApi.AfterConstruction;
begin
 inherited AfterConstruction;
 LinkSqlObjects;
end;

procedure TSqlDbApi.BeforeDestruction;
begin
 Verbose:=False;
 UnlinkSqlObjects;
 inherited BeforeDestruction;
end;

function TSqlDbApi.GetVerbose:Boolean;
begin
 if Assigned(Self)
 then Result:=Assigned(myErrorsList)
 else Result:=False;
end;

procedure TSqlDbApi.SetVerbose(aVerbose:Boolean);
begin
 if Assigned(Self) then begin
  if not aVerbose then Kill(myErrorsList) else
  if not Assigned(myErrorsList) then begin
   myErrorsList:=NewText;
   myErrorsList.Master:=@myErrorsList;
  end;
 end;
end;

function TSqlDbApi.GetErrorsList:TText;
begin
 if Assigned(Self)
 then Result:=myErrorsList
 else Result:=nil;
end;

function TSqlDbApi.IncErrorsCount:Integer;
begin
 if Assigned(Self)
 then Result:=LockedInc(myErrorsCount)
 else Result:=0;
end;

function TSqlDbApi.GetErrorsCount:Integer;
begin
 if Assigned(Self)
 then Result:=LockedGet(myErrorsCount)
 else Result:=0;
end;

procedure TSqlDbApi.SetErrorsCount(aCount:Integer);
begin
 if Assigned(Self)
 then LockedSet(myErrorsCount,aCount)
end;

function TSqlDbApi.GetProvider:LongString;
begin
 if Assigned(Self)
 then Result:=myProvider
 else Result:='';
end;

function TSqlDbApi.GetConnection:TSqlConnection;
begin
 Result:=nil;
 if Assigned(Self)
 then Result:=myConnection
 else ErrorNilReference('GetConnection');
end;

function TSqlDbApi.GetDataSource:TDataSource;
begin
 Result:=nil;
 if Assigned(Self)
 then Result:=myDataSource
 else ErrorNilReference('GetDataSource');
end;

function TSqlDbApi.GetQuery:TSQLQuery;
begin
 Result:=nil;
 if Assigned(Self)
 then Result:=myQuery
 else ErrorNilReference('GetQuery');
end;

function TSqlDbApi.GetTrans:TSQLTransaction;
begin
 Result:=nil;
 if Assigned(Self)
 then Result:=myTrans
 else ErrorNilReference('GetTrans');
end;

function TSqlDbApi.GetConStrng:LongString;
begin
 if Assigned(Self)
 then Result:=myConStrng
 else Result:='';
end;

procedure TSqlDbApi.SetConStrng(arg:LongString);
begin
 if Assigned(Self) then begin
  if RaiseOnResetConStrng
  then ErrorNotSupported('SetConStrng');
  myConStrng:=arg;
 end;
end;

function TSqlDbApi.GetIsLinked:Boolean;
begin
 if Assigned(Self)
 then Result:=myIsLinked
 else Result:=False;
end;

function TSqlDbApi.LinkSqlObjects:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Assigned(myConnection) then myConnection.Transaction:=myTrans;
  if Assigned(myQuery) then myQuery.Transaction:=myTrans;
  if Assigned(myQuery) then myQuery.SQLConnection:=myConnection;
  if Assigned(myTrans) then myTrans.SQLConnection:=myConnection;
  if Assigned(myDataSource) then myDataSource.DataSet:=myQuery;
  Result:=Assigned(myConnection) and Assigned(myQuery)
      and Assigned(myDataSource) and Assigned(myTrans)
      and (myQuery.SQLConnection=myConnection)
      and (myTrans.SQLConnection=myConnection)
      and (myConnection.Transaction=myTrans)
      and (myDataSource.DataSet=myQuery)
      and (myQuery.Transaction=myTrans);
  myIsLinked:=Result;
 except
  on E:Exception do BugReport(E,Self,'LinkSqlObjects');
 end;
end;

procedure TSqlDbApi.UnlinkSqlObjects;
begin
 if Assigned(Self) then
 try
  myIsLinked:=False;
  if Assigned(myTrans) then myTrans.Active:=False;
  if Assigned(myQuery) then myQuery.Active:=False;
  if Assigned(myConnection) then myConnection.Connected:=False;
  if Assigned(myConnection) then myConnection.Transaction:=nil;
  if Assigned(myQuery) then myQuery.Transaction:=nil;
  if Assigned(myQuery) then myQuery.SQLConnection:=nil;
  if Assigned(myTrans) then myTrans.SQLConnection:=nil;
  if Assigned(myDataSource) then myDataSource.DataSet:=nil;
 except
  on E:Exception do BugReport(E,Self,'UnlinkSqlObjects');
 end;
end;

procedure TSqlDbApi.FreeSqlObjects;
begin
 if Assigned(Self) then
 try
  UnlinkSqlObjects;
  Kill(TObject(myDataSource));
  Kill(TObject(myConnection));
  Kill(TObject(myTrans));
  Kill(TObject(myQuery));
 except
  on E:Exception do BugReport(E,Self,'FreeSqlObjects');
 end;
end;

function TSqlDbApi.MakeConnection(const aConnStr:LongString):Boolean;
var sProvider,sDriver,sDataBase,sHostName,sUserName,sPassword:LongString;
var sCharset,sRole,sVerbose,sConStr,sParams,sFileDSN,sCatalog:LongString;
var ClassFactory:TSQLConnectionClass; sn,sv,uConnStr,uErrors:LongString;
begin
 Result:=false;
 if Assigned(Self) then
 try
  FreeSqlObjects;
  sn:=''; sv:=''; uErrors:='';
  // Try to detect DB URI and convert to cookies
  uConnStr:=UriMan.Copy_DB_URI_As_Params(aConnStr,uErrors);
  if IsNonEmptyStr(uErrors) then DebugLog(dlc_SqlDbLog,uErrors);
  // Parse connection string, extract general parameters
  myConStrng:=SqlDbAssistant.ValidateParams(uConnStr,'','');
  sConStr:=SqlDbAssistant.ValidateParams(uConnStr,sia_Internals,'');
  sParams:=SqlDbAssistant.ValidateParams(uConnStr,'',sia_Internals);
  sProvider:=SqlDbAssistant.FetchParam(sConStr,sia_Provider);
  sDataBase:=SqlDbAssistant.FetchParam(sConStr,sia_DataBase);
  sHostName:=SqlDbAssistant.FetchParam(sConStr,sia_HostName);
  sUserName:=SqlDbAssistant.FetchParam(sConStr,sia_UserName);
  sPassword:=SqlDbAssistant.FetchParam(sConStr,sia_Password);
  sCharset:=SqlDbAssistant.FetchParam(sConStr,sia_Charset);
  sVerbose:=SqlDbAssistant.FetchParam(sConStr,sia_Verbose);
  sFileDSN:=SqlDbAssistant.FetchParam(sConStr,sia_FileDSN);
  sCatalog:=SqlDbAssistant.FetchParam(sConStr,sia_Catalog);
  sDriver:=SqlDbAssistant.FetchParam(sConStr,sia_Driver);
  sRole:=SqlDbAssistant.FetchParam(sConStr,sia_Role);
  // Test HOSTNAME:DATABASE expression
  if (ExtractNameValuePair(sDatabase,sn,sv,':')>2) then begin
   if IsLexeme(sn,lex_DnsHost) then begin
    sHostName:=TrimDef(sHostName,sn);
    sDataBase:=sv;
   end;
  end;
  if IsNonEmptyStr(sProvider) then begin
   ClassFactory:=SqlDbAssistant.FindConnectionClass(sProvider);
   if Assigned(ClassFactory) then begin
    myProvider:=SqlDbAssistant.FindProvider(sProvider);
    if IsNonEmptyStr(myProvider)
    then myConnection:=ClassFactory.Create(nil);
   end else
   ////////////
   // Fallback:
   ////////////
   // Microsoft SQL
   if (WordIndex(sProvider,'MSSQL',ScanSpaces)>0) then begin
    myConnection:=TMSSQLConnection.Create(nil);
    myProvider:='MSSQL';
   end else
   // Sybase
   if (WordIndex(sProvider,'Sybase',ScanSpaces)>0) then begin
    myConnection:=TSybaseConnection.Create(nil);
    myProvider:='Sybase';
   end else
   // PostgreSQL
   if (WordIndex(sProvider,'PQ PostgreSQL',ScanSpaces)>0) then begin
    myConnection:=TPQConnection.Create(nil);
    myProvider:='PQ';
   end else
   // Oracle
   if (WordIndex(sProvider,'Oracle',ScanSpaces)>0) then begin
    myConnection:=TOracleConnection.Create(nil);
    myProvider:='Oracle';
   end else
   // ODBC
   if (WordIndex(sProvider,'ODBC',ScanSpaces)>0) then begin
    myConnection:=TODBCConnection.Create(nil);
    myProvider:='ODBC';
   end else
   // MySQL 4.0
   if (WordIndex(sProvider,'MySQL40',ScanSpaces)>0) then begin
    myConnection:=TMySQL40Connection.Create(nil);
    myProvider:='MySQL40';
   end else
   // MySQL 4.1
   if (WordIndex(sProvider,'MySQL41',ScanSpaces)>0) then begin
    myConnection:=TMySQL41Connection.Create(nil);
    myProvider:='MySQL41';
   end else
   // MySQL 5.0
   if (WordIndex(sProvider,'MySQL50',ScanSpaces)>0) then begin
    myConnection:=TMySQL50Connection.Create(nil);
    myProvider:='MySQL50';
   end else
   // MySQL 5.1
   if (WordIndex(sProvider,'MySQL51',ScanSpaces)>0) then begin
    myConnection:=TMySQL51Connection.Create(nil);
    myProvider:='MySQL51';
   end else
   // MySQL 5.5
   if (WordIndex(sProvider,'MySQL55',ScanSpaces)>0) then begin
    myConnection:=TMySQL55Connection.Create(nil);
    myProvider:='MySQL55';
   end else
   // MySQL 5.6
   if (WordIndex(sProvider,'MySQL56',ScanSpaces)>0) then begin
    myConnection:=TMySQL56Connection.Create(nil);
    myProvider:='MySQL56';
   end else
   // MySQL 5.7
   if (WordIndex(sProvider,'MySQL57',ScanSpaces)>0) then begin
    myConnection:=TMySQL57Connection.Create(nil);
    myProvider:='MySQL57';
   end else
   // MySQL 8.0
   if (WordIndex(sProvider,'MySQL80',ScanSpaces)>0) then begin
    myConnection:=TMySQL80Connection.Create(nil);
    myProvider:='MySQL80';
   end else
   // SQLite3
   if (WordIndex(sProvider,'SQLite3',ScanSpaces)>0) then begin
    myConnection:=TSQLite3Connection.Create(nil);
    myProvider:='SQLite3';
   end else
   // Interbase, Firebird
   if (WordIndex(sProvider,'IB FB Interbase Firebird',ScanSpaces)>0) then begin
    myConnection:=TIBConnection.Create(nil);
    myProvider:='IB';
   end;
   if not Assigned(myConnection)
   then Raise ESqlDbApi.CreateFmt('Bad SQLDB Provider (%s).',[sProvider]);
   if (myProvider='') and Assigned(myConnection) then begin
    myProvider:=myConnection.ClassName; System.Delete(myProvider,1,1);
    myProvider:=StringReplace(myProvider,'Connection','',[rfIgnoreCase]);
   end;
   myConnection.LoginPrompt:=false; // For thread safety
   if (sUserName='') then sUserName:=SqlDbAssistant.GetProviderEnv(myProvider,'UserName');
   if (sPassword='') then sPassword:=SqlDbAssistant.GetProviderEnv(myProvider,'Password');
   if (sHostName='') then sHostName:=SqlDbAssistant.GetProviderEnv(myProvider,'HostName');
   if (sDataBase<>'') then myConnection.DatabaseName:=sDataBase;
   if (sHostName<>'') then myConnection.HostName:=sHostName;
   if (sUserName<>'') then myConnection.UserName:=sUserName;
   if (sPassword<>'') then myConnection.Password:=sPassword;
   if (sCatalog<>'') then myConnection.Directory:=sCatalog;
   if (sCharset<>'') then myConnection.CharSet:=SqlDbAssistant.FindCharSet(sCharset);
   if (sVerbose<>'') then Verbose:=(StrToIntDef(sVerbose,BoolToInt(Verbose))>0);
   if (sRole<>'') then myConnection.Role:=sRole;
   if (sDriver<>'') and (myConnection is TODBCConnection)
   then TODBCConnection(myConnection).Driver:=sDriver;
   if (sFileDSN<>'') and (myConnection is TODBCConnection)
   then TODBCConnection(myConnection).FileDSN:=sFileDSN;
   if (sParams<>'') then myConnection.Params.Text:=sParams;
   if (myDataSource=nil) then myDataSource:=TDataSource.Create(nil);
   if (myTrans=nil) then myTrans:=TSQLTransaction.Create(nil);
   if (myQuery=nil) then myQuery:=TSQLQuery.Create(nil);
   Result:=LinkSqlObjects;
  end;
 except
  on E:Exception do BugReport(E,Self,'MakeConnection');
 end;
end;

class function TSqlDbApi.sia_Driver:LongString;
begin
 Result:=SqlDbAssistant.sia_Driver;
end;
class function TSqlDbApi.sia_Provider:LongString;
begin
 Result:=SqlDbAssistant.sia_Provider;
end;
class function TSqlDbApi.sia_DataBase:LongString;
begin
 Result:=SqlDbAssistant.sia_DataBase;
end;
class function TSqlDbApi.sia_HostName:LongString;
begin
 Result:=SqlDbAssistant.sia_HostName;
end;
class function TSqlDbApi.sia_UserName:LongString;
begin
 Result:=SqlDbAssistant.sia_UserName;
end;
class function TSqlDbApi.sia_Password:LongString;
begin
 Result:=SqlDbAssistant.sia_Password;
end;
class function TSqlDbApi.sia_Charset:LongString;
begin
 Result:=SqlDbAssistant.sia_Charset;
end;
class function TSqlDbApi.sia_FileDSN:LongString;
begin
 Result:=SqlDbAssistant.sia_FileDSN;
end;
class function TSqlDbApi.sia_Catalog:LongString;
begin
 Result:=SqlDbAssistant.sia_Catalog;
end;
class function TSqlDbApi.sia_Role:LongString;
begin
 Result:=SqlDbAssistant.sia_Role;
end;
class function TSqlDbApi.sia_Verbose:LongString;
begin
 Result:=SqlDbAssistant.sia_Verbose;
end;
class function TSqlDbApi.sia_Internals:LongString;
const Sep=';'; // Separator
begin
 Result:=sia_Provider+Sep+sia_Driver+Sep+sia_DataBase+Sep+sia_Catalog+Sep
        +sia_HostName+Sep+sia_UserName+Sep+sia_Password+Sep+sia_Charset+Sep
        +sia_FileDSN+Sep+sia_Role+Sep+sia_Verbose+Sep;
end;

class procedure TSqlDbApi.ErrorNilReference(const Where:LongString);
begin
 if RaiseOnNilReference then
 raise ESqlDbApi.Create(Format('Nil reference at %s.',[Where]));
end;

class procedure TSqlDbApi.ErrorNotSupported(const Where:LongString);
begin
 raise ESqlDbApi.Create(Format('SQLDB not supports %s.',[Where]));
end;

class procedure TSqlDbApi.ParameterUnused(x:Boolean);
begin
 FakeNop(x);
end;

class procedure TSqlDbApi.ParameterUnused(x:Integer);
begin
 FakeNop(x);
end;

class procedure TSqlDbApi.ParameterUnused(x:LongString);
begin
 FakeNop(x);
end;

/////////////////////////////////
// TSqlDbConnecter implementation
/////////////////////////////////

constructor TSqlDbConnecter.Create(aApi:TSqlDbApi);
begin
 inherited Create;
 if Assigned(aApi) then begin
  myApi:=aApi;
  myApi.Master:=@myApi;
 end;
 myCommander:=TSqlDbCommander.Create(Self);
 myCommander.Master:=@myCommander;
 myRecorder:=TSqlDbRecorder.Create(Self);
 myRecorder.Master:=@myRecorder;
 mySaveNullSubs:=DefSaveNullSubs;
 myPropertiesWanted:='';
end;

destructor TSqlDbConnecter.Destroy;
begin
 myPropertiesWanted:='';
 mySaveNullSubs:='';
 Kill(myCommander);
 Kill(myRecorder);
 Kill(myApi);
 inherited Destroy;
end;

procedure TSqlDbConnecter.AfterConstruction;
begin
 inherited AfterConstruction;
 AllSqlDbConnecters.Add(Self);
 if Api.IsLinked then begin
  if DebugLogEnabled(dlc_SqlDbLog)
  then Api.Connection.OnLog:=OnDbLog;
  Api.Connection.LogEvents:=DefLogEvents;
 end;
end;

procedure TSqlDbConnecter.BeforeDestruction;
begin
 AllSqlDbConnecters.Remove(Self);
 inherited BeforeDestruction;
end;

function TSqlDbConnecter.GetApi:TSqlDbApi;
begin
 if Assigned(Self)
 then Result:=myApi
 else Result:=nil;
end;

function TSqlDbConnecter.GetRecorder:TSqlDbRecorder;
begin
 if Assigned(Self)
 then Result:=myRecorder
 else Result:=nil;
end;

function TSqlDbConnecter.GetCommander:TSqlDbCommander;
begin
 if Assigned(Self)
 then Result:=myCommander
 else Result:=nil;
end;

function TSqlDbConnecter.GetProvider:LongString;
begin
 if Assigned(Self)
 then Result:=myApi.Provider
 else Result:='';
end;

procedure TSqlDbConnecter.SetProvider(p:LongString);
begin
 if Assigned(Self) then
 try
  if Api.RaiseOnResetProvider
  then Api.ErrorNotSupported('SetProvider');
  Api.ParameterUnused(p);
 except
  on E:Exception do BugReport(E,Self,'SetProvider');
 end;
end;

function TSqlDbConnecter.Open(Opt:Integer=0):Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   if not Api.Connection.Connected
   then Api.Connection.Connected:=True;
   Result:=Api.Connection.Connected;
   if Result then
   if SqlDbAssistant.UsesPreloadLibraries then
   if not SqlDbAssistant.IsPreloadedLibrary(Api.Provider) then
   if (SqlDbAssistant.FindLoadedLibraryName(Api.Provider)<>'')
   then SqlDbAssistant.PreloadLibrary(Api.Provider);
  end;
  Api.ParameterUnused(Opt);
 except
  on E:Exception do BugReport(E,Self,'Open');
 end;
end;

function TSqlDbConnecter.Close:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   if Api.Query.Active then Api.Query.Active:=False;
   if Api.Trans.Active then Api.Trans.Active:=False;
   if Api.Connection.Connected then Api.Connection.Connected:=False;
   Result:=not Api.Connection.Connected;
  end;
 except
  on E:Exception do BugReport(E,Self,'Close');
 end;
end;

function TSqlDbConnecter.Cancel:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   if Api.Query.Active then begin
    if Api.DataSource.DataSet.Modified
    then Api.DataSource.DataSet.Cancel;
    Api.Query.CancelUpdates;
    Result:=true;
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'Cancel');
 end;
end;

function TSqlDbConnecter.BeginTrans:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Api.IsLinked then
  if Api.Connection.Connected then begin
   if not Api.Connection.Transaction.Active
   then Api.Connection.Transaction.Active:=True;
   Result:=BoolToInt(Api.Connection.Transaction.Active);
  end;
 except
  on E:Exception do BugReport(E,Self,'BeginTrans');
 end;
end;

function TSqlDbConnecter.CommitTrans(aRetaining:Boolean=False):Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then
  if Api.Connection.Connected then
  if Api.Connection.Transaction.Active then begin
   if not aRetaining
   then Api.Connection.Transaction.Commit
   else Api.Connection.Transaction.CommitRetaining;
   Result:=True;
  end;
 except
  on E:Exception do BugReport(E,Self,'CommitTrans');
 end;
end;

function TSqlDbConnecter.RollbackTrans(aRetaining:Boolean=False):Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then
  if Api.Connection.Connected then
  if Api.Connection.Transaction.Active then begin
   if not aRetaining
   then Api.Connection.Transaction.Rollback
   else Api.Connection.Transaction.RollbackRetaining;
   Result:=True;
  end;
 except
  on E:Exception do BugReport(E,Self,'RollbackTrans');
 end;
end;

function TSqlDbConnecter.Execute(arg:LongString; opt:Integer=0; rof:Boolean=True):TSqlDbRecorder;
var st:TStatementType;
begin
 Result:=nil;
 if Assigned(Self) then
 try
  if Api.IsLinked then
  if Api.Connection.Connected then begin
   QuerySQLText:=arg;
   st:=QuerySQLType;
   // Query.Open raise error on non-selectable SQL
   // So (re)open on selectable SQL or use ExecSQL
   if (st in SelectableStatementTypes) then begin
    if Api.Query.Active
    then Api.Query.Active:=False;
    Api.Query.ReadOnly:=rof;
    Api.Query.Active:=true;
   end else begin
    Api.Query.ExecSQL;
   end;
   Result:=myRecorder;
  end;
  Api.ParameterUnused(Opt);
 except
  on E:Exception do BugReport(E,Self,'Execute');
 end;
end;

function TSqlDbConnecter.State:Integer;
begin
 Result:=adStateClosed;
 if Assigned(Self) then
 try
  if Api.IsLinked then
  if Api.Connection.Connected then begin
   Result:=adStateOpen;
  end;
 except
  on E:Exception do BugReport(E,Self,'State');
 end;
end;

function TSqlDbConnecter.Active:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then
  if Api.Connection.Connected then begin
   Result:=True;
  end;
 except
  on E:Exception do BugReport(E,Self,'Active');
 end;
end;

function TSqlDbConnecter.GetQuerySQLText:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   Result:=Trim(Api.Query.SQL.Text);
  end;
 except
  on E:Exception do BugReport(E,Self,'GetQuerySQLText');
 end;
end;

procedure TSqlDbConnecter.SetQuerySQLText(const arg:LongString);
begin
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   Api.Query.SQL.Text:=Trim(arg);
   if not Api.Query.Prepared
   then Api.Query.Prepare;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetQuerySQLText');
 end;
end;

function TSqlDbConnecter.GetQuerySQLType:TStatementType;
begin
 Result:=stUnknown;
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   if not Api.Query.Prepared
   then Api.Query.Prepare;
   Result:=Api.Query.StatementType;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetQuerySQLType');
 end;
end;

function TSqlDbConnecter.CreateDatabase:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   Api.Connection.CreateDB;
   Result:=True;
  end;
 except
  on E:Exception do BugReport(E,Self,'CreateDatabase');
 end;
end;

procedure TSqlDbConnecter.OnDbLog(Sender:TSQLConnection; EventType:TDBEventType; const Msg:String);
var Evt:LongString;
begin
 if Assigned(Sender) then
 if DebugLogEnabled(dlc_SqlDbLog) then begin
  Evt:=DbCon.EventTypeToString(EventType);
  if StartsText('det',Evt) then Delete(Evt,1,3);
  DebugLog(dlc_SqlDbLog,Format('%s %s - %s',[Sender.ClassName,Evt,Msg]));
 end;
end;

function TSqlDbConnecter.GetDefaultDataBase:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   Result:=Api.Connection.DatabaseName;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetDefaultDataBase');
 end;
end;

procedure TSqlDbConnecter.SetDefaultDataBase(aDatabase:LongString);
begin
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   Api.Connection.DatabaseName:=aDatabase;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetDefaultDataBase');
 end;
end;

function TSqlDbConnecter.GetVersion:LongString;
begin
 if Assigned(Self)
 then Result:=db_engine_name_sqldb+' LCL '+lcl_version+' FPC '+GetFpcVersion
 else Result:='';
end;

function TSqlDbConnecter.GetProperties:LongString;
var Lines,Items,List:TStringList; i:Integer; sn,sv,id:LongString;
 procedure AddProp(aName,aValue:LongString);
 begin
  aName:=Trim(aName);
  aValue:=Trim(aValue);
  if (aName<>'') // and (aValue<>'')
  then Lines.Values[aName]:=aValue;
 end;
 function Wanted(const arg:LongString):Boolean;
 begin
  id:=arg; Result:=False;
  if (Items.Count>0) then begin
   if (Items.IndexOf(id)<0) then Exit;
   Result:=True;
  end else begin
   if SameText(id,'Connection.SchemaNames') then Exit;
   if SameText(id,'Connection.SequenceNames') then Exit;
   if SameText(id,'Connection.ProcedureNames') then Exit;
   Result:=True;
  end;
 end;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   List:=TStringList.Create;
   Lines:=TStringList.Create;
   Items:=TStringList.Create;
   try
    List.Delimiter:=','; id:='';
    if IsNonEmptyStr(myPropertiesWanted) then begin
     Items.Duplicates:=dupIgnore; Items.Sorted:=True;
     Items.Text:=StringReplace(Trim(myPropertiesWanted),';',EOL,[rfReplaceAll]);
     myPropertiesWanted:='';
    end;
    try
     if Wanted('Connection.EngineId') then AddProp(id,IntToStr(db_engine_code_sqldb));
     if Wanted('Connection.EngineName') then AddProp(id,db_engine_name_sqldb);
     if Wanted('Connection.EngineVersion') then AddProp(id,Version);
     if Wanted('Connection.Provider') then AddProp(id,Provider);
     if Wanted('Connection.Connected') then AddProp(id,IntToStr(BoolToInt(Api.Connection.Connected)));
     if Wanted('Connection.Database') then AddProp(id,Api.Connection.DatabaseName);
     if Wanted('Connection.UserName') then AddProp(id,Api.Connection.UserName);
     if Wanted('Connection.Password') then AddProp(id,Api.Connection.Password);
     if Wanted('Connection.HostName') then AddProp(id,Api.Connection.HostName);
     if Wanted('Connection.Catalog') then AddProp(id,Api.Connection.Directory);
     if Wanted('Connection.CharSet') then AddProp(id,Api.Connection.CharSet);
     if Wanted('Connection.Role') then AddProp(id,Api.Connection.Role);
     if (Api.Connection is TODBCConnection) then begin
      if Wanted('Connection.Driver') then AddProp(id,(Api.Connection as TODBCConnection).Driver);
      if Wanted('Connection.FileDSN') then AddProp(id,(Api.Connection as TODBCConnection).FileDSN);
     end;
     if Wanted('Connection.ReadOnly') then AddProp(id,BoolToStr(Api.Query.ReadOnly,'1','0'));
     if Wanted('Connection.Options') then AddProp(id,UnifySection(DbCon.SQLConnectionOptionsToString(Api.Connection.Options),0));
     if Wanted('Connection.ConnOptions') then AddProp(id,UnifySection(DbCon.ConnOptionsToString(Api.Connection.ConnOptions),0));
     if Wanted('Connection.ConnectionInfo') then AddProp(id,Api.Connection.GetConnectionInfo(citAll));
     if Wanted('Connection.KeepConnection') then AddProp(id,BoolToStr(Api.Connection.KeepConnection,'1','0'));
     if Wanted('Connection.IsSQLBased') then AddProp(id,BoolToStr(Api.Connection.IsSQLBased,'1','0'));
     if Wanted('Transaction.Active') then AddProp(id,BoolToStr(Api.Trans.Active,'1','0'));
     if Wanted('Query.Active') then AddProp(id,BoolToStr(Api.Query.Active,'1','0'));
     if Wanted('Query.Options') then AddProp(id,DbCon.SqlQueryOptionsToString(Api.Query.Options));
     if Wanted('Query.SQLText') then AddProp(id,QuerySQLText);
     if Wanted('Query.StatementType') then AddProp(id,DbCon.StatementTypeToString(Api.Query.StatementType));
     if Wanted('Query.TableName') then AddProp(id,SqlDbAssistant.GetStatementInfo(QuerySQLText).TableName);
     if Wanted('Query.UpdateMode') then AddProp(id,DbCon.UpdateModeToString(Api.Query.UpdateMode));
     if Wanted('Query.SchemaType') then AddProp(id,DbCon.SchemaTypeToString(Api.Query.SchemaType));
     if Wanted('Query.RowsAffected') then AddProp(id,IntToStr(Api.Query.RowsAffected));
     if Wanted('Query.Filter') then AddProp(id,Api.Query.ServerFilter);
     if Wanted('Query.Filtered') then AddProp(id,BoolToStr(Api.Query.ServerFiltered,'1','0'));
     if Wanted('Query.IndexName') then AddProp(id,Api.Query.IndexName);
     if Wanted('DataSource.State') then AddProp(id,DbCon.DataSetStateToString(Api.DataSource.State));
     if Wanted('Connection.SaveNullSubs') then AddProp(id,SaveNullSubs);
     if Wanted('Connection.TableNames') then begin
      if Api.Connection.Connected then
      try
       List.Clear;
       Api.Connection.GetTableNames(List);
       AddProp(id,List.CommaText);
       List.Clear;
      except
       on E:Exception do BugReport(E,Self,id);
      end;
     end;
     if Wanted('Connection.ProcedureNames') then begin
      if Api.Connection.Connected then
      try
       List.Clear;
       Api.Connection.GetProcedureNames(List);
       AddProp(id,List.CommaText);
       List.Clear;
      except
       on E:Exception do BugReport(E,Self,id);
      end;
     end;
     if Wanted('Connection.SequenceNames') then begin
      if Api.Connection.Connected then
      try
       List.Clear;
       Api.Connection.GetSequenceNames(List);
       AddProp(id,List.CommaText);
       List.Clear;
      except
       on E:Exception do BugReport(E,Self,id);
      end;
     end;
     if Wanted('Connection.SchemaNames') then begin
      if Api.Connection.Connected then
      try
       List.Clear;
       Api.Connection.GetSchemaNames(List);
       AddProp(id,List.CommaText);
       List.Clear;
      except
       on E:Exception do BugReport(E,Self,id);
      end;
     end;
     for i:=0 to Api.Connection.Params.Count-1 do begin
      if (ExtractNameValuePair(Api.Connection.Params[i],sn,sv)>0) then
      if Wanted('Connection.Params.'+sn) then AddProp(id,sv);
     end;
    except
     on E:Exception do BugReport(E,Self,'GetProperties:'+id);
    end;
    Result:=Lines.Text;
   finally
    Kill(Items);
    Kill(Lines);
    Kill(List);
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetProperties');
 end;
end;

procedure TSqlDbConnecter.SetProperties(p:LongString);
var Lines:TStringList; sn,sv:LongString; i,iv:Integer;
 function Wanted(const id:LongString):Boolean; overload;
 begin
  Result:=SameText(sn,id);
 end;
 function Wanted(const id:LongString; var iv:Integer):Boolean; overload;
 begin
  Result:=SameText(sn,id) and TryStrToInt(sv,iv);
 end;
begin
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   Lines:=TStringList.Create;
   try
    myPropertiesWanted:=''; iv:=0;
    Lines.Text:=StringReplace(Trim(p),';',EOL,[rfReplaceAll]);
    for i:=0 to Lines.Count-1 do begin
     if (ExtractNameValuePair(Lines[i],sn,sv)>0) then begin
      myPropertiesWanted:=myPropertiesWanted+sn+EOL;
      sv:=Trim(sv); if IsEmptyStr(sv) then continue;
      if Wanted('Connection.Connected',iv) then Api.Connection.Connected:=(iv<>0);
      if Wanted('Connection.Database') then Api.Connection.DatabaseName:=sv;
      if Wanted('Connection.UserName') then Api.Connection.UserName:=sv;
      if Wanted('Connection.Password') then Api.Connection.Password:=sv;
      if Wanted('Connection.HostName') then Api.Connection.HostName:=sv;
      if Wanted('Connection.Catalog') then Api.Connection.Directory:=sv;
      if Wanted('Connection.CharSet') then Api.Connection.CharSet:=sv;
      if Wanted('Connection.Role') then Api.Connection.Role:=sv;
      if (Api.Connection is TODBCConnection) then begin
       if Wanted('Connection.Driver') then (Api.Connection as TODBCConnection).Driver:=sv;
       if Wanted('Connection.FileDSN') then (Api.Connection as TODBCConnection).FileDSN:=sv;
      end;
      if Wanted('Connection.ReadOnly',iv) then Api.Query.ReadOnly:=(iv<>0);
      if Wanted('Connection.Options') then Api.Connection.Options:=DbCon.StringToSQLConnectionOptions(sv);
      if Wanted('Connection.KeepConnection',iv) then Api.Connection.KeepConnection:=(iv<>0);
      if Wanted('Query.Active',iv) then Api.Query.Active:=(iv<>0);
      if Wanted('Query.SQLText') then QuerySQLText:=sv;
      if Wanted('Query.Options') then Api.Query.Options:=DbCon.StringToSQLQueryOptions(sv);
      if Wanted('Query.Filter') then Api.Query.ServerFilter:=sv;
      if Wanted('Query.Filtered',iv) then Api.Query.ServerFiltered:=(iv<>0);
      if Wanted('Query.IndexName') then Api.Query.IndexName:=sv;
      if Wanted('Transaction.Active',iv) then Api.Connection.Transaction.Active:=(iv<>0);
      if StartsText('Connection.Params.',sn) and (Length(sn)>Length('Connection.Params.'))
      then Api.Connection.Params.Values[Copy(sn,1+Length('Connection.Params.'),MaxInt)]:=sv;
      if Wanted('Connection.SaveNullSubs') then SaveNullSubs:=sv;
     end else begin
      sn:=TrimChars(Lines[i],ScanSpaces,ScanSpaces);
      if IsEmptyStr(sn) then continue;
      myPropertiesWanted:=myPropertiesWanted+sn+EOL;
     end;
    end;
   finally
    Kill(Lines);
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetProperties');
 end;
end;

function TSqlDbConnecter.GetConnectionTimeout:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbConnecter.SetConnectionTimeout(t:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then Api.ParameterUnused(t);
end;

function TSqlDbConnecter.GetCommandTimeout:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbConnecter.SetCommandTimeout(t:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then Api.ParameterUnused(t);
end;

function TSqlDbConnecter.GetIsolationLevel:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbConnecter.SetIsolationLevel(t:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then Api.ParameterUnused(t);
end;

function TSqlDbConnecter.GetMode:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbConnecter.SetMode(m:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then Api.ParameterUnused(m);
end;

function TSqlDbConnecter.GetCursorLocation:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbConnecter.SetCursorLocation(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then Api.ParameterUnused(p);
end;

function TSqlDbConnecter.GetAttributes:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbConnecter.SetAttributes(a:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then Api.ParameterUnused(a);
end;

function TSqlDbConnecter.GetErrors(aClear:Boolean=False):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if Assigned(Api) then begin
  Result:=Api.ErrorsList.Text;
  if aClear then GetErrorsCount(aClear);
 end;
end;

function TSqlDbConnecter.GetErrorsCount(aClear:Boolean=False):Integer;
begin
 Result:=0;
 if Assigned(Self) then
 if Assigned(Api) then begin
  Result:=Api.ErrorsCount;
  if aClear then Api.ErrorsCount:=0;
  if aClear and Api.Verbose then Api.ErrorsList.Count:=0;
 end;
end;

class function TSqlDbConnecter.FieldTypeToCode(T:TFieldType):Integer;
begin
 Result:=Ord(T);
end;

function TSqlDbConnecter.GetSaveNullSubs:LongString;
begin
 if Assigned(Self)
 then Result:=mySaveNullSubs
 else Result:='';
end;

procedure TSqlDbConnecter.SetSaveNullSubs(s:LongString);
begin
 if Assigned(Self)
 then mySaveNullSubs:=s;
end;

/////////////////////////////////
// TSqlDbCommander implementation
/////////////////////////////////

constructor TSqlDbCommander.Create(aConnecter:TSqlDbConnecter);
begin
 inherited Create;
 myConnecter:=aConnecter;
end;

function TSqlDbCommander.GetConnecter:TSqlDbConnecter;
begin
 if Assigned(Self)
 then Result:=myConnecter
 else Result:=nil;
end;

function TSqlDbCommander.State:Integer;
begin
 Result:=adStateClosed;
 if Assigned(Self) then
 try
  Result:=Connecter.State;
 except
  on E:Exception do BugReport(E,Self,'State');
 end;
end;

function TSqlDbCommander.Active:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  Result:=Connecter.Active;
 except
  on E:Exception do BugReport(E,Self,'Active');
 end;
end;

function TSqlDbCommander.Cancel:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if Connecter.Api.DataSource.DataSet.Modified
   then Connecter.Api.DataSource.DataSet.Cancel;
   Connecter.Api.Query.CancelUpdates;
   Result:=true;
  end;
 except
  on E:Exception do BugReport(E,Self,'Cancel');
 end;
end;

function TSqlDbCommander.GetCommandType:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
 if Assigned(Self) then Result:=adCmdText;
end;

procedure TSqlDbCommander.SetCommandType(t:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then Connecter.Api.ParameterUnused(t);
end;

function TSqlDbCommander.GetCommandText:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Result:=Connecter.QuerySQLText;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetCommandText');
 end;
end;

procedure TSqlDbCommander.SetCommandText(aCommand:LongString);
begin
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Connecter.QuerySQLText:=aCommand;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetCommandText');
 end;
end;

////////////////////////////////
// TSqlDbRecorder implementation
////////////////////////////////

constructor TSqlDbRecorder.Create(aConnecter:TSqlDbConnecter);
begin
 inherited Create;
 myConnecter:=aConnecter;
end;

function TSqlDbRecorder.GetConnecter:TSqlDbConnecter;
begin
 if Assigned(Self)
 then Result:=myConnecter
 else Result:=nil;
end;

function TSqlDbRecorder.BOF:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Result:=Connecter.Api.DataSource.DataSet.BOF;
  end;
 except
  on E:Exception do BugReport(E,Self,'BOF');
 end;
end;

function TSqlDbRecorder.EOF:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Result:=Connecter.Api.DataSource.DataSet.EOF;
  end;
 except
  on E:Exception do BugReport(E,Self,'EOF');
 end;
end;

function TSqlDbRecorder.GetFieldsCount:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Result:=Connecter.Api.DataSource.DataSet.FieldCount;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFieldsCount');
 end;
end;

function TSqlDbRecorder.GetFieldsNames(i:Integer):LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Result:=Connecter.Api.DataSource.DataSet.Fields[i].FieldName;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFieldsNames');
 end;
end;

function TSqlDbRecorder.GetFieldsTypes(i:Integer):Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Result:=Connecter.FieldTypeToCode(Connecter.Api.DataSource.DataSet.Fields[i].DataType);
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFieldsTypes');
 end;
end;

function TSqlDbRecorder.IndexOfField(const id:LongString):Integer;
var Field:TField;
begin
 Result:=-1;
 if (id<>'') then
 if Assigned(Self) then
 try
  Field:=nil;
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Field:=Connecter.Api.DataSource.DataSet.Fields.FindField(id);
   if Assigned(Field) then Result:=Field.Index;
  end;
 except
  on E:Exception do BugReport(E,Self,'IndexOfField');
 end;
end;

function TSqlDbRecorder.MoveFirst:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Connecter.Api.DataSource.DataSet.First;
   Result:=true;
  end;
 except
  on E:Exception do BugReport(E,Self,'MoveFirst');
 end;
end;

function TSqlDbRecorder.MoveLast:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Connecter.Api.DataSource.DataSet.Last;
   Result:=true;
  end;
 except
  on E:Exception do BugReport(E,Self,'MoveLast');
 end;
end;

function TSqlDbRecorder.MoveNext:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Connecter.Api.DataSource.DataSet.Next;
   Result:=true;
  end;
 except
  on E:Exception do BugReport(E,Self,'MoveNext');
 end;
end;

function TSqlDbRecorder.MovePrevious:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Connecter.Api.DataSource.DataSet.Prior;
   Result:=true;
  end;
 except
  on E:Exception do BugReport(E,Self,'MovePrevious');
 end;
end;

function TSqlDbRecorder.Close:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   if Connecter.Api.Query.Active
   then Connecter.Api.Query.Active:=False;
   Result:=not Connecter.Api.Query.Active;
  end;
 except
  on E:Exception do BugReport(E,Self,'Close');
 end;
end;

function TSqlDbRecorder.Open(opt:Integer):Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   if not Connecter.Api.Connection.Connected
   then Connecter.Api.Connection.Connected:=True;
   if not Connecter.Api.Query.Active
   then Connecter.Api.Query.Active:=True;
   Result:=Connecter.Api.Query.Active;
   Connecter.Api.ParameterUnused(opt);
  end;
 except
  on E:Exception do BugReport(E,Self,'Open');
 end;
end;

function TSqlDbRecorder.Update:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if Connecter.Api.Query.Modified then begin
    Connecter.Api.Query.Post;
    Connecter.Api.Query.ApplyUpdates;
    Result:=true;
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'Update');
 end;
end;

function TSqlDbRecorder.Cancel:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if Connecter.Api.DataSource.DataSet.Modified
   then Connecter.Api.DataSource.DataSet.Cancel;
   Connecter.Api.Query.CancelUpdates;
   Result:=true;
  end;
 except
  on E:Exception do BugReport(E,Self,'Cancel');
 end;
end;

function TSqlDbRecorder.CancelUpdate:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if Connecter.Api.DataSource.DataSet.Modified
   then Connecter.Api.DataSource.DataSet.Cancel;
   Connecter.Api.Query.CancelUpdates;
   Result:=true;
  end;
 except
  on E:Exception do BugReport(E,Self,'CancelUpdate');
 end;
end;

function TSqlDbRecorder.State:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Result:=Ord(Connecter.Api.Query.State);
  end;
 except
  on E:Exception do BugReport(E,Self,'State');
 end;
end;

function TSqlDbRecorder.Active:Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Connection.Connected then begin
   Result:=Connecter.Api.Query.Active;
  end;
 except
  on E:Exception do BugReport(E,Self,'Active');
 end;
end;

function TSqlDbRecorder.Requery(opt:Integer=0):Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Connection.Connected then
  if Connecter.Api.Query.SQL.Count>0 then begin
   if Connecter.Api.Query.Active
   then Connecter.Api.Query.Active:=False;
   Connecter.Api.Query.Active:=True;
   Result:=Connecter.Api.Query.Active;
  end;
  Connecter.Api.ParameterUnused(opt);
 except
  on E:Exception do BugReport(E,Self,'Requery');
 end;
end;

function TSqlDbRecorder.Resync(aff,res:Integer):Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Connecter.Api.DataSource.DataSet.Refresh;
   Result:=True;
  end;
  Connecter.Api.ParameterUnused(aff);
  Connecter.Api.ParameterUnused(res);
 except
  on E:Exception do BugReport(E,Self,'Resync');
 end;
end;

function TSqlDbRecorder.Supports(CursorOptions:Integer):Boolean;
var yes,non:Integer;
begin
 Result:=False;
 if Assigned(Self) then
 try
  yes:=0; non:=0;
  if Connecter.Api.IsLinked then begin
   if HasFlags(CursorOptions,adHoldRecords) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adMovePrevious) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adAddNew) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adDelete) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adUpdate) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adBookmark) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adApproxPosition) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adUpdateBatch) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adResync) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adNotify) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adFind) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adSeek) then begin
    inc(yes);
   end;
   if HasFlags(CursorOptions,adIndex) then begin
    inc(yes);
   end;
   Result:=(yes>0) and (non=0);
  end;
 except
  on E:Exception do BugReport(E,Self,'Supports');
 end;
end;

function TSqlDbRecorder.AddNew(arg:LongString):Boolean;
var Lines:TStringList; i:Integer; sn,sv:LongString; Field:TField;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   if Connecter.Api.Query.Active then begin
    Connecter.Api.DataSource.DataSet.Append;
    Result:=true;
   end;
   arg:=Trim(arg);
   if (arg<>'') then
   if Result then begin
    Lines:=TStringList.Create;
    try
     Lines.Text:=arg;
     for i:=0 to Lines.Count-1 do begin
      if ExtractNameValuePair(Lines[i],sn,sv)>0 then begin
       Field:=Connecter.Api.DataSource.DataSet.FindField(sn);
       if not Assigned(Field) then Continue;
       Field.AsString:=sv;
      end;
     end;
    finally
     Kill(Lines);
    end;
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'AddNew');
 end;
end;

function TSqlDbRecorder.Delete(aff:Integer):Boolean;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   Connecter.Api.DataSource.DataSet.Delete;
   Result:=true;
  end;
  Connecter.Api.ParameterUnused(aff);
 except
  on E:Exception do BugReport(E,Self,'Delete');
 end;
end;

function TSqlDbRecorder.GetString(n:Integer; coldel,rowdel,NullSubs:LongString):LongString;
var i,rows,cols:Integer; line,item:LongString; Lines,Items:TStringList;
var bm:TBookMark;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   bm:=Connecter.Api.DataSource.DataSet.Bookmark;
   Lines:=TStringList.Create;
   Items:=TStringList.Create;
   try
    rows:=0;
    while (rows<n) and not Connecter.Api.DataSource.DataSet.EOF do begin
     Items.Clear;
     cols:=Connecter.Api.DataSource.DataSet.FieldCount;
     for i:=0 to cols-1 do begin
      if Connecter.Api.DataSource.DataSet.Fields[i].IsNull
      then item:=NullSubs
      else item:=Connecter.Api.DataSource.DataSet.Fields[i].AsString;
      Items.Add(item);
     end;
     line:=Items.Text;
     if EndsText(EOL,line)
     then line:=Copy(line,1,Length(line)-Length(EOL));
     line:=StringReplace(line,EOL,coldel,[rfReplaceAll]);
     Connecter.Api.DataSource.DataSet.Next;
     Lines.Add(line);
     inc(rows);
    end;
    Result:=Lines.Text;
    if (rowdel<>EOL) then Result:=StringReplace(Result,EOL,rowdel,[rfReplaceAll]);
   finally
    Kill(Lines);
    Kill(Items);
   end;
   Connecter.Api.DataSource.DataSet.Bookmark:=bm;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFieldsAsString');
 end;
end;

function TSqlDbRecorder.GetFieldsAsString(i:Integer):LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Result:=Connecter.Api.DataSource.DataSet.Fields[i].AsString;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFieldsAsString');
 end;
end;

procedure TSqlDbRecorder.SetFieldsAsString(i:Integer; const v:LongString);
begin
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Connecter.Api.DataSource.DataSet.Fields[i].AsString:=v;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetFieldsAsString');
 end;
end;

function TSqlDbRecorder.GetFieldsAsInteger(i:Integer):Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Result:=Connecter.Api.DataSource.DataSet.Fields[i].AsInteger;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFieldsAsInteger');
 end;
end;

procedure TSqlDbRecorder.SetFieldsAsInteger(i:Integer; v:Integer);
begin
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Connecter.Api.DataSource.DataSet.Fields[i].AsInteger:=v;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetFieldsAsInteger');
 end;
end;

function TSqlDbRecorder.GetFieldsAsFloat(i:Integer):Double;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Result:=Connecter.Api.DataSource.DataSet.Fields[i].AsFloat;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFieldsAsFloat');
 end;
end;

procedure TSqlDbRecorder.SetFieldsAsFloat(i:Integer; v:Double);
begin
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then
  if Connecter.Api.Query.Active then begin
   if InRange(i,0,Connecter.Api.DataSource.DataSet.FieldCount-1)
   then Connecter.Api.DataSource.DataSet.Fields[i].AsFloat:=v;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetFieldsAsFloat');
 end;
end;

function TSqlDbRecorder.GetSource:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Result:=Connecter.QuerySQLText;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetSource');
 end;
end;

procedure TSqlDbRecorder.SetSource(aSource:LongString);
begin
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Connecter.QuerySQLText:=aSource;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetSource');
 end;
end;

function TSqlDbRecorder.GetAbsolutePage:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetAbsolutePage(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetAbsolutePosition:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetAbsolutePosition(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetBookmark:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetBookmark(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetCacheSize:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetCacheSize(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetCursorType:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetCursorType(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetEditMode:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetEditMode(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetFilter:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Result:=Connecter.Api.Query.ServerFilter;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetFilter');
 end;
end;

procedure TSqlDbRecorder.SetFilter(v:LongString);
begin
 if Assigned(Self) then
 try
  v:=Trim(v);
  if Connecter.Api.IsLinked then begin
   Connecter.Api.Query.ServerFilter:=v;
   Connecter.Api.Query.ServerFiltered:=(v<>'');
  end;
 except
  on E:Exception do BugReport(E,Self,'SetFilter');
 end;
end;

function TSqlDbRecorder.GetIndex:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Result:=Connecter.Api.Query.IndexName;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetIndex');
 end;
end;

procedure TSqlDbRecorder.SetIndex(v:LongString);
begin
 if Assigned(Self) then
 try
  if Connecter.Api.IsLinked then begin
   Connecter.Api.Query.IndexName:=Trim(v);
  end;
 except
  on E:Exception do BugReport(E,Self,'SetIndex');
 end;
end;

function TSqlDbRecorder.GetLockType:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetLockType(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetMarshalOptions:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetMarshalOptions(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetMaxRecords:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetMaxRecords(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetPageCount:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

function TSqlDbRecorder.GetPageSize:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetPageSize(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.GetRecordCount:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

function TSqlDbRecorder.GetSort:LongString;
begin
 Result:='';
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetSort(v:LongString);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(v);
 end;
end;

function TSqlDbRecorder.GetStatus:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

function TSqlDbRecorder.GetStayInSync:Boolean;
begin
 Result:=False;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetStayInSync(v:Boolean);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(v);
 end;
end;

function TSqlDbRecorder.GetCursorLocation:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

procedure TSqlDbRecorder.SetCursorLocation(p:Integer);
begin
 // Stub for ADO simulation
 if Assigned(Self) then begin
  Connecter.Api.ParameterUnused(p);
 end;
end;

function TSqlDbRecorder.Save(Destination:LongString; PersistFormat:Integer):Boolean;
var df:TDataPacketFormat; ext:LongString;
const tabExts='.txt;.tab;';
const csvExts='.csv;';
const xmlExts='.xml;';
begin
 Result:=False;
 if Assigned(Self) then
 try
  if IsNonEmptyStr(Destination) then
  if Connecter.Api.IsLinked then begin
   if InRange(PersistFormat,Ord(Low(df)),Ord(High(df))) then begin
    df:=TDataPacketFormat(PersistFormat);
    if (df in [dfAny,dfDefault]) then begin
     ext:=ExtractFileExt(Destination);
     if WordIndex(ext,csvExts,ScanSpaces)>0 then begin
      Result:=SaveAsText(Destination,',',EOL,Connecter.SaveNullSubs,QuoteMark);
      Exit;
     end;
     if WordIndex(ext,tabExts,ScanSpaces)>0 then begin
      Result:=SaveAsText(Destination,ASCII_Tab,EOL,Connecter.SaveNullSubs,' ');
      Exit;
     end;
     if WordIndex(ext,xmlExts,ScanSpaces)>0 then begin
      Connecter.Api.Query.SaveToFile(Destination,dfXml);
      Result:=True;
      Exit;
     end;
    end;
    Connecter.Api.Query.SaveToFile(Destination,df);
    Result:=True;
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'Save');
 end;
end;

function TSqlDbRecorder.SaveAsText(Dest,coldel,rowdel,NullSubs:LongString;Quote:Char):Boolean;
var i,rows,cols,nerr,iores:Integer; line,item,Buff:LongString; Lines,Items:TStringList;
var bm:TBookMark; T:Text; NeedQuotes:Boolean; Field:TField;
const BuffSize=1024*48; PageLines=1024; ztime=' 0:00:00';
 procedure SaveLines(Cache:Integer);
 begin
  if (Lines.Count>Cache) then begin
   line:=lines.Text;
   if (rowdel<>EOL) then line:=StringReplace(line,EOL,rowdel,[rfReplaceAll]);
   write(T,line); if (IOResult<>0) then Inc(nerr);
   Lines.Clear; line:='';
  end;
 end;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if IsNonEmptyStr(Dest) then
  if Connecter.Api.Query.Active then begin
   bm:=Connecter.Api.DataSource.DataSet.Bookmark;
   Connecter.Api.DataSource.DataSet.First;
   iores:=IOResult;
   Assign(T,Dest); Rewrite(T);
   Buff:=StringBuffer(BuffSize);
   SetTextBuf(T,PChar(Buff)^,Length(Buff));
   Lines:=TStringList.Create;
   Items:=TStringList.Create;
   try
    rows:=0; nerr:=0;
    cols:=Connecter.Api.DataSource.DataSet.FieldCount;
    while (nerr=0) and not Connecter.Api.DataSource.DataSet.EOF do begin
     Items.Clear;
     for i:=0 to cols-1 do begin
      Field:=Connecter.Api.DataSource.DataSet.Fields[i];
      if Field.IsNull then item:=NullSubs else item:=Field.AsString;
      if HasFlags(Connecter.SaveAsModeFlags,1) then // Compact datetime format
      if DbCon.IsDateTimeFieldTypeCode(Ord(Field.DataType),db_engine_zeos) then
      if EndsText(ztime,item) then item:=Copy(item,1,Length(item)-Length(ztime));
      if Quote in [QuoteMark,Apostrophe] then begin
       NeedQuotes:=HasChars(item,ScanSpaces);
       if NeedQuotes then item:=AnsiQuotedStr(item,Quote);
      end;
      Items.Add(item);
     end;
     line:=Items.Text;
     if EndsText(EOL,line)
     then line:=Copy(line,1,Length(line)-Length(EOL));
     line:=StringReplace(line,EOL,coldel,[rfReplaceAll]);
     Connecter.Api.DataSource.DataSet.Next;
     Lines.Add(line); SaveLines(PageLines);
     inc(rows);
    end;
    SaveLines(0);
    Result:=(IOResult=0) and (nerr=0);
   finally
    Connecter.Api.DataSource.DataSet.Bookmark:=bm;
    SmartFileClose(T); SetInOutRes(iores);
    Kill(Lines);
    Kill(Items);
    Buff:='';
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'SaveAsText');
 end;
end;

////////////////////////////////////
// AllSqlDbConnecters implementation
////////////////////////////////////

const
 TheSqlDbConnecters:TObjectStorage=nil;

function AllSqlDbConnecters:TObjectStorage;
begin
 if (TheSqlDbConnecters=nil) then begin
  TheSqlDbConnecters:=NewObjectStorage(false);
  TheSqlDbConnecters.Master:=@TheSqlDbConnecters;
  TheSqlDbConnecters.OwnsObjects:=false;
 end;
 Result:=TheSqlDbConnecters;
end;

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

procedure Init_crw_dblaz;
begin
 dlc_SqlDbBug;
 dlc_SqlDbLog;
 SqlDbAssistant.Ok;
 AllSqlDbConnecters.Ok;
 TSqlDbApi.DefVerbose:=True;
 TSqlDbApi.RaiseOnNilReference:=True;
 TSqlDbApi.RaiseOnResetProvider:=True;
 TSqlDbApi.RaiseOnResetConStrng:=True;
 SqlDbAssistant.UsesPreloadLibraries:=True;
 TSqlDbConnecter.SelectableStatementTypes:=[stSelect];
 TSqlDbConnecter.DefLogEvents:=[Low(TDBEventType)..High(TDBEventType)];
 TSqlDbConnecter.DefSaveNullSubs:='';
 TSqlDbConnecter.SaveAsModeFlags:=0;
end;

procedure Free_crw_dblaz;
begin
 Kill(TObject(TheSqlDbConnecters));
 SqlDbAssistant.Free;
end;

initialization

 Init_crw_dblaz;

finalization

 Free_crw_dblaz;

end.

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

