////////////////////////////////////////////////////////////////////////////////
// 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:                                                                   //
// Engine agent for Lazarus ZEOSDBO.                                          //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20250707 - Created by A.K.                                                 //
// 20250718 - TZeosAssistant                                                  //
////////////////////////////////////////////////////////////////////////////////

unit _crw_dbzeo; //  DB Lazarus - interface for Lazarus ZEOSDBO.

{$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, lclversion, types,
 Forms, Controls, Graphics, Dialogs,
 db, sqldb, bufdataset, xmldatapacketreader,
 ZConnection, ZDataset, ZDbcIntfs, ZDbcLogging, ZSQLMonitor,
 _crw_alloc, _crw_ef, _crw_str, _crw_fio, _crw_dynar,
 _crw_dbglog, _crw_proc, _crw_regexp, _crw_uri, _crw_dbcon;

const /////////////////// ZEOS engine identifiers
 db_engine_code_zeos    = _crw_dbcon.db_engine_code_zeos; // ZEOS engine code
 db_engine_name_zeos    = _crw_dbcon.db_engine_name_zeos; // ZEOS engine name

 /////////////////////////////////////////////////////////////////////////
 // List of ZEOS supported databases: see ZeosAssistant.KnownProviders
 // ado,ASA,asa_capi,firebird,interbase,mariadb,mssql,mysql,odbc_a,odbc_w,
 // OleDb,oracle,pooled.*,postgresql,sqlite,sybase,WebServiceProxy
 /////////////////////////////////////////////////////////////////////////

type
 TSqlStatementTypes = set of TStatementType;

 ///////////////////////////////////////////////////////////////////////////////
 // TZeosConnecter - ZEOS connector, include SQL Connection,Query.
 ///////////////////////////////////////////////////////////////////////////////
type
 TZeosApi = class;
 TZeosRecorder = class;
 TZeosCommander = class;
 TZeosConnecter = class;
 EZeosApi = class(Exception);
 TZeosMasterObject = 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;
 TZeosApi = class(TZeosMasterObject)
 private
  myConnection  : TZConnection;
  myDataSource  : TDataSource;
  myQuery       : TZQuery;
  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:TZConnection;
  function  GetDataSource:TDataSource;
  function  GetQuery:TZQuery;
  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:TZConnection   read GetConnection;
  property DataSource:TDataSource    read GetDataSource;
  property Query:TZQuery             read GetQuery;
  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_Port: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;
 //////////////////
 // TZeosConnecter
 //////////////////
 TZeosConnecter = class(TZeosMasterObject)
 private
  myApi       : TZeosApi;
  myCommander : TZeosCommander;
  myRecorder  : TZeosRecorder;
 private
  function  GetApi:TZeosApi;
  function  GetProvider:LongString;
  procedure SetProvider(p:LongString);
  function  GetRecorder:TZeosRecorder;
  function  GetCommander:TZeosCommander;
 public
  constructor Create(aApi:TZeosApi);
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  property Api:TZeosApi read GetApi;
  property Provider:LongString read GetProvider write SetProvider;
  property Recorder:TZeosRecorder read GetRecorder;
  property Commander:TZeosCommander 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):TZeosRecorder;
  function State:Integer;
  function Active:Boolean;
 private
  function  GetQuerySQLText:LongString;
  procedure SetQuerySQLText(const arg:LongString);
  function  GetQuerySQLType:TStatementType;
 public
  function  CreateDatabase:Boolean;
 public
  property  QuerySQLText:LongString read GetQuerySQLText write SetQuerySQLText;
  property  QuerySQLType:TStatementType read GetQuerySQLType;
 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;
 //////////////////
 // TZeosCommander
 //////////////////
 TZeosCommander = class(TZeosMasterObject)
 private
  myConnecter:TZeosConnecter;
 private
  function  GetConnecter:TZeosConnecter;
 public
  constructor Create(aConnecter:TZeosConnecter);
 public
  property Connecter:TZeosConnecter 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;
 /////////////////
 // TZeosRecorder
 /////////////////
 TZeosRecorder = class(TZeosMasterObject)
 private
  myConnecter:TZeosConnecter;
 private
  function  GetConnecter:TZeosConnecter;
  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:TZeosConnecter);
 public
  property  Connecter:TZeosConnecter 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;
  function  SaveAsXml(Dest:LongString; NullSubs:LongString):Boolean;
 end;
 /////////////////
 // TZeosAssistant
 /////////////////
 TZeosAssistant = class(TDbEngineAssistant)
 private
  myProtocols:TStringList;
  myNickNames:TStringList;
  myCodePages:TStringList;
  myProtPages:TStringList;
  myKnownProtocols:LongString;
  myKnownCodePages:LongString;
  myMonitor:TZSQLMonitor;
 private
  procedure InitProtocols;
  procedure FreeProtocols;
  procedure InitCodePages;
  function  GetProtocolCodePages(aProtocol:LongString):LongString;
  procedure MonitorTrace(Sender:TObject; Event:TZLoggingEvent; var LogTrace:Boolean);
 public
  constructor Create;
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  function  KnownProtocols:LongString;
  function  FindProtocol(arg:LongString):LongString;
  property  KnownProviders:LongString read KnownProtocols;
  function  KnownCodePages(aProtocol:LongString=''; Sep:Char=#0):LongString;
  function  CheckMonitor(Mode:Integer=0):Boolean;
 end;

function  NewZeosConnecter(const aConnStr:LongString):TZeosConnecter;
procedure Kill(var TheObject:TZeosConnecter); overload;
procedure Kill(var TheObject:TZeosCommander); overload;
procedure Kill(var TheObject:TZeosRecorder); overload;
procedure Kill(var TheObject:TZeosApi); overload;

function AllZeosConnecters:TObjectStorage;

function ZeosAssistant:TZeosAssistant;

 ////////////////////
 // DebugLog channels
 ////////////////////
function dlc_ZeosBug:Integer;
function dlc_ZeosLog:Integer;

implementation

uses
 _crw_dbapi;

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

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

function dlc_ZeosLog:Integer;
const dlc:Integer=0;
begin
 if (dlc=0) then dlc:=RegisterDebugLogChannel('_ZeosLog');
 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:TZeosApi; 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 TZeosApi) then Api:=TZeosApi(O) else
  if (O is TZeosConnecter) then Api:=TZeosConnecter(O).Api else
  if (O is TZeosCommander) then Api:=TZeosCommander(O).Connecter.Api else
  if (O is TZeosRecorder) then Api:=TZeosRecorder(O).Connecter.Api else Api:=nil;
  Api.IncErrorsCount;
  verb:=BoolToInt(Api.Verbose)+BoolToInt(DebugLogEnabled(dlc_ZeosBug))*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_ZeosBug,msg);
  end;
 end;
end;

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

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

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

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

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

////////////////////////////////
// TZeosAssistant implementation
////////////////////////////////

constructor TZeosAssistant.Create;
begin
 inherited Create;
 myKnownProtocols:='';
 myKnownCodePages:='';
 myProtocols:=TStringList.Create;
 myNickNames:=TStringList.Create;
 myCodePages:=TStringList.Create;
 myProtPages:=TStringList.Create;
 myMonitor:=TZSQLMonitor.Create(nil);
 myMonitor.OnTrace:=MonitorTrace;
end;

destructor TZeosAssistant.Destroy;
begin
 Kill(myProtocols);
 Kill(myNickNames);
 Kill(myCodePages);
 Kill(myProtPages);
 myKnownProtocols:='';
 myKnownCodePages:='';
 FreeAndNil(myMonitor);
 inherited Destroy;
end;

procedure TZeosAssistant.AfterConstruction;
begin
 inherited AfterConstruction;
 InitProtocols;
 InitCodePages;
 CheckMonitor(1);
end;

procedure TZeosAssistant.BeforeDestruction;
begin
 FreeProtocols;
 myMonitor.Active:=False;
 inherited BeforeDestruction;
end;

procedure TZeosAssistant.InitProtocols;
var i,j:Integer; Protos:TStringDynArray;
 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
  myProtocols.Clear;
  myNickNames.Clear;
  myKnownProtocols:='';
  myProtocols.Duplicates:=dupIgnore;
  Protos:=nil; // Makes compiler happy
  for i:=0 to DriverManager.GetDrivers.Count-1 do begin
   Protos:=(DriverManager.GetDrivers.Items[i] as IZDriver).GetSupportedProtocols;
   for j:=Low(Protos) to High(Protos) do myProtocols.Add(Trim(Protos[j]));
  end;
  myProtocols.Sorted:=True;
  myKnownProtocols:=myProtocols.Text;
  AddNickNames('odbc_a=ODBC;');
  AddNickNames('firebird=FB;');
  AddNickNames('interbase=IB;');
  AddNickNames('sqlite=SQLite3;');
  AddNickNames('postgresql=PQ;postgres;postgressql;');
  AddNickNames('mysql=MySQL40;MySQL41;MySQL50;MySQL51;MySQL55;MySQL56;MySQL57;MySQL80;');
  AddNickNames('firebird=.fdb;');
  AddNickNames('sqlite=.db;.sdb;.sqlite;.db3;.s3db;');
  AddNickNames('sqlite=.sqlite3;.sl3;.db2;.s2db;.sqlite2;.sl2;');
 except
  on E:Exception do BugReport(E,Self,'InitProtocols');
 end;
end;

procedure TZeosAssistant.FreeProtocols;
begin
 if Assigned(Self) then
 try
  myProtocols.Clear;
  myNickNames.Clear;
  myCodePages.Clear;
  myProtPages.Clear;
 except
  on E:Exception do BugReport(E,Self,'FreeProtocols');
 end;
end;

procedure TZeosAssistant.InitCodePages;
var i,j:Integer; sCodePages,sCP:LongString;
begin
 if Assigned(Self) then
 try
  myCodePages.Clear;
  myProtPages.Clear;
  myKnownCodePages:='';
  myCodePages.Duplicates:=dupIgnore;
  for i:=0 to myProtocols.Count-1 do begin
   sCodePages:=GetProtocolCodePages(myProtocols[i]);
   for j:=1 to WordCount(sCodePages,ScanSpaces) do begin
    sCP:=ExtractWord(j,sCodePages,ScanSpaces);
    myCodePages.Add(sCP);
   end;
   myProtPages.Values[myProtocols[i]]:=StringReplace(Trim(sCodePages),EOL,',',[rfReplaceAll]);
  end;
  myCodePages.Sorted:=True;
  myKnownCodePages:=myCodePages.Text;
 except
  on E:Exception do BugReport(E,Self,'InitCodePages');
 end;
end;

function ZLoggingCategoryToString(lc:TZLoggingCategory):LongString;
begin
 Result:='Unknown';
 case lc of
  lcConnect:      Result:='Connect';
  lcDisconnect:   Result:='Disconnect';
  lcTransaction:  Result:='Transaction';
  lcExecute:      Result:='Execute';
  lcOther:        Result:='Other';
  lcPrepStmt:     Result:='PrepStmt';
  lcBindPrepStmt: Result:='BindPrepStmt';
  lcExecPrepStmt: Result:='ExecPrepStmt';
  lcUnprepStmt:   Result:='UnprepStmt';
  lcFetch:        Result:='Fetch';
  lcFetchDone:    Result:='FetchDone';
 end;
end;

procedure TZeosAssistant.MonitorTrace(Sender:TObject; Event:TZLoggingEvent; var LogTrace:Boolean);
var msg:LongString;
begin
 if Assigned(Self) and Assigned(Event) then
 if DebugLogEnabled(dlc_ZeosLog) or DebugLogEnabled(dlc_ZeosBug) then begin
  msg:=Event.Protocol+' '+ZLoggingCategoryToString(Event.Category);
  if (Event.Error<>'') then msg:=msg+' ERROR: '+QArg(Event.Error,'`');
  if (Event.Message<>'') then msg:=msg+' INFO: '+QArg(Event.Message,'`');
  if (Event.Error<>'')
  then DebugLog(dlc_ZeosBug,msg)
  else DebugLog(dlc_ZeosLog,msg);
  if (Sender=nil) then FakeNop(LogTrace); // To avoid compiler hints
 end;
end;

function TZeosAssistant.CheckMonitor(Mode:Integer=0):Boolean;
begin
 Result:=False;
 if Assigned(Self) then begin
  if (Mode>1) then myMonitor.Active:=True;
  if (Mode<0) then myMonitor.Active:=False;
  if (Mode=1) then myMonitor.Active:=DebugLogEnabled(dlc_ZeosLog) or DebugLogEnabled(dlc_ZeosBug);
  Result:=myMonitor.Active;
 end;
end;

function TZeosAssistant.GetProtocolCodePages(aProtocol:LongString):LongString;
var List:TStringList; i:Integer; SDyn:TStringDynArray; Url:TZURL; Driver:IZDriver;
begin
 Result:='';
 if Assigned(Self) then
 try
  aProtocol:=Trim(aProtocol);
  if IsEmptyStr(aProtocol) then Exit;
  List:=TStringList.Create;
  try
   SDyn:=nil;
   Url:=TZURL.Create;
   try
    Url.Protocol:=aProtocol;
    Driver:=DriverManager.GetDriver(Url.URL);
    if (Driver=nil) then Exit;
    if (Driver.GetPlainDriver(URL,False)=nil) then Exit;
    SDyn:=Driver.GetPlainDriver(URL,False).GetClientCodePages;
   finally
    Url.Free;
   end;
   for i:=0 to High(SDyn) do List.Add(SDyn[i]);
   Result:=List.Text;
  finally
   Kill(List);
  end;
 except
  on E:Exception do BugReport(E,Self,'GetProtocolCodePages');
 end;
end;

function TZeosAssistant.KnownProtocols:LongString;
begin
 if Assigned(Self)
 then Result:=myKnownProtocols
 else Result:='';
end;

function TZeosAssistant.FindProtocol(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:=myProtocols.IndexOf(arg);
   if (i>=0) then Exit(myProtocols[i]);
   if (myNickNames.IndexOfName(arg)>=0)
   then arg:=myNickNames.Values[arg]
   else break;
  end;
 except
  on E:Exception do BugReport(E,Self,'FindProtocol');
 end;
end;

function TZeosAssistant.KnownCodePages(aProtocol:LongString=''; Sep:Char=#0):LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if (myKnownCodePages='') then InitCodePages;
  if IsEmptyStr(aProtocol) then Exit(myKnownCodePages);
  aProtocol:=FindProtocol(aProtocol);
  if (aProtocol='') then Exit;
  if (myProtPages.IndexOfName(aProtocol)>=0)
  then Result:=myProtPages.Values[aProtocol] else Exit;
  Result:=ValidateEOL(StringReplace(Result,',',EOL,[rfReplaceAll]),1);
  if (Sep<>#0) then Result:=StringReplace(Trim(Result),EOL,Sep,[rfReplaceAll]);
 except
  on E:Exception do BugReport(E,Self,'KnownCodePages');
 end;
end;

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

////////////////////////////////////
// TZeosMasterObject implementation
////////////////////////////////////

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

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

procedure TZeosMasterObject.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_dbzeo.BugReport(E,O,S)
 else _crw_dbzeo.BugReport(E,O,BgPref+S);
end;

///////////////////////////
// TZeosApi implementation
///////////////////////////

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

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

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

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

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

procedure TZeosApi.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 TZeosApi.GetErrorsList:TText;
begin
 if Assigned(Self)
 then Result:=myErrorsList
 else Result:=nil;
end;

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

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

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

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

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

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

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

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

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

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

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

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

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

function TZeosApi.MakeConnection(const aConnStr:LongString):Boolean;
var sProvider,sDriver,sDataBase,sHostName,sUserName,sPassword,sPort:LongString;
var sCharset,sVerbose,sConStr,sParams,sProtocol,sFileDSN,sCatalog:LongString;
var nPort:Integer; sn,sv,uConnStr,uErrors:LongString;
begin
 Result:=false;
 if Assigned(Self) then
 try
  FreeSqlObjects;
  sn:=''; sv:=''; uErrors:='';
  ZeosAssistant.CheckMonitor(1);
  // Try to detect DB URI and convert to cookies
  uConnStr:=UriMan.Copy_DB_URI_As_Params(aConnStr,uErrors);
  if IsNonEmptyStr(uErrors) then DebugLog(dlc_ZeosLog,uErrors);
  // Parse connection string, extract general parameters
  myConStrng:=ZeosAssistant.ValidateParams(uConnStr,'','');
  sConStr:=ZeosAssistant.ValidateParams(uConnStr,sia_Internals,'');
  sParams:=ZeosAssistant.ValidateParams(uConnStr,'',sia_Internals);
  sDriver:=ZeosAssistant.FetchParam(sConStr,sia_Driver);
  sProvider:=ZeosAssistant.FetchParam(sConStr,sia_Provider);
  sDataBase:=ZeosAssistant.FetchParam(sConStr,sia_DataBase);
  sHostName:=ZeosAssistant.FetchParam(sConStr,sia_HostName);
  sUserName:=ZeosAssistant.FetchParam(sConStr,sia_UserName);
  sPassword:=ZeosAssistant.FetchParam(sConStr,sia_Password);
  sCharset:=ZeosAssistant.FetchParam(sConStr,sia_Charset);
  sVerbose:=ZeosAssistant.FetchParam(sConStr,sia_Verbose);
  sFileDSN:=ZeosAssistant.FetchParam(sConStr,sia_FileDSN);
  sCatalog:=ZeosAssistant.FetchParam(sConStr,sia_Catalog);
  sPort:=ZeosAssistant.FetchParam(sConStr,sia_Port);
  // 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
   sProtocol:=ZeosAssistant.FindProtocol(sProvider);
   if IsNonEmptyStr(sProtocol) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:=sProtocol;
    myProvider:=sProtocol;
   end else
   ////////////
   // Fallback:
   ////////////
   // ado
   if (WordIndex(sProvider,'ado',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='ado';
    myProvider:='ado';
   end else
   // ASA
   if (WordIndex(sProvider,'ASA',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='ASA';
    myProvider:='ASA';
   end else
   // asa_capi
   if (WordIndex(sProvider,'asa_capi',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='asa_capi';
    myProvider:='asa_capi';
   end else
   // firebird
   if (WordIndex(sProvider,'firebird',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='firebird';
    myProvider:='firebird';
   end else
   // interbase
   if (WordIndex(sProvider,'interbase ib',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='interbase';
    myProvider:='interbase';
   end else
   // mariadb
   if (WordIndex(sProvider,'mariadb',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='mariadb';
    myProvider:='mariadb';
   end else
   // mssql
   if (WordIndex(sProvider,'mssql',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='mssql';
    myProvider:='mssql';
   end else
   // mysql
   if (WordIndex(sProvider,'mysql',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='mysql';
    myProvider:='mysql';
   end else
   // odbc_a
   if (WordIndex(sProvider,'odbc odbc_a',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    //if (sDriver<>'') then myConnection.Driver:=sDriver;
    myConnection.Protocol:='odbc_a';
    myProvider:='odbc_a';
   end else
   // odbc_w
   if (WordIndex(sProvider,'odbc_w',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='odbc_w';
    myProvider:='odbc_w';
   end else
   // OleDb
   if (WordIndex(sProvider,'OleDb',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='OleDb';
    myProvider:='OleDb';
   end else
   // oracle
   if (WordIndex(sProvider,'oracle',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='oracle';
    myProvider:='oracle';
   end else
   // pooled.*
   if (WordIndex(sProvider,'pooled.*',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='pooled.*';
    myProvider:='pooled.*';
   end else
   // postgresql
   if (WordIndex(sProvider,'postgresql pq',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='postgresql';
    myProvider:='postgresql';
   end else
   // sqlite
   if (WordIndex(sProvider,'sqlite SQLite3',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='sqlite';
    myProvider:='sqlite';
   end else
   // sybase
   if (WordIndex(sProvider,'sybase',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='sybase';
    myProvider:='sybase';
   end else
   // WebServiceProxy
   if (WordIndex(sProvider,'WebServiceProxy',ScanSpaces)>0) then begin
    myConnection:=TZConnection.Create(nil);
    myConnection.Protocol:='WebServiceProxy';
    myProvider:='WebServiceProxy';
   end;
   if not Assigned(myConnection)
   then Raise EZeosApi.CreateFmt('Bad ZEOS 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:=ZeosAssistant.GetProviderEnv(myProvider,'UserName');
   if (sPassword='') then sPassword:=ZeosAssistant.GetProviderEnv(myProvider,'Password');
   if (sHostName='') then sHostName:=ZeosAssistant.GetProviderEnv(myProvider,'HostName');
   if (sPort='') then sPort:=ZeosAssistant.GetProviderEnv(myProvider,'Port');
   if (sDataBase<>'') then myConnection.Database:=sDataBase;
   if (sHostName<>'') then myConnection.HostName:=sHostName;
   if (sUserName<>'') then myConnection.User:=sUserName;
   if (sPassword<>'') then myConnection.Password:=sPassword;
   if (sCharset<>'') then begin
    if WordIndex(sCharset,ZeosAssistant.KnownCodePages(Provider),ScanSpaces)=0
    then DebugLog(dlc_ZeosLog,'Unsupported Charset='+sCharset)
    else myConnection.ClientCodepage:=sCharset;
   end;
   if (sCatalog<>'') then myConnection.Catalog:=sCatalog;
   if (sVerbose<>'') then Verbose:=(StrToIntDef(sVerbose,BoolToInt(Verbose))>0);
   if (sPort<>'') and TryStrToInt(sPort,nPort) and InRange(nPort,1,$FFFF) then myConnection.Port:=nPort;
   if (sDriver<>'') then sParams:='Driver='+sDriver+EOL+sParams;
   if (sFileDSN<>'') then sParams:='FILEDSN='+sFileDSN+EOL+sParams;
   if (sParams<>'') then myConnection.Properties.Text:=sParams;
   if (myDataSource=nil) then myDataSource:=TDataSource.Create(nil);
   if (myQuery=nil) then myQuery:=TZQuery.Create(nil);
   Result:=LinkSqlObjects;
  end;
 except
  on E:Exception do BugReport(E,Self,'MakeConnection');
 end;
end;

class function TZeosApi.sia_Driver:LongString;
begin
 Result:=ZeosAssistant.sia_Driver;
end;
class function TZeosApi.sia_Provider:LongString;
begin
 Result:=ZeosAssistant.sia_Provider;
end;
class function TZeosApi.sia_DataBase:LongString;
begin
 Result:=ZeosAssistant.sia_DataBase;
end;
class function TZeosApi.sia_HostName:LongString;
begin
 Result:=ZeosAssistant.sia_HostName;
end;
class function TZeosApi.sia_UserName:LongString;
begin
 Result:=ZeosAssistant.sia_UserName;
end;
class function TZeosApi.sia_Password:LongString;
begin
 Result:=ZeosAssistant.sia_Password;
end;
class function TZeosApi.sia_Charset:LongString;
begin
 Result:=ZeosAssistant.sia_Charset;
end;
class function TZeosApi.sia_FileDSN:LongString;
begin
 Result:=ZeosAssistant.sia_FileDSN;
end;
class function TZeosApi.sia_Catalog:LongString;
begin
 Result:=ZeosAssistant.sia_Catalog;
end;
class function TZeosApi.sia_Port:LongString;
begin
 Result:=ZeosAssistant.sia_Port;
end;
class function TZeosApi.sia_Verbose:LongString;
begin
 Result:=ZeosAssistant.sia_Verbose;
end;
class function TZeosApi.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_Port+Sep
        +sia_FileDSN+Sep+sia_Charset+Sep+sia_Verbose+Sep;
end;

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

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

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

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

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

/////////////////////////////////
// TZeosConnecter implementation
/////////////////////////////////

constructor TZeosConnecter.Create(aApi:TZeosApi);
begin
 inherited Create;
 if Assigned(aApi) then begin
  myApi:=aApi;
  myApi.Master:=@myApi;
 end;
 myCommander:=TZeosCommander.Create(Self);
 myCommander.Master:=@myCommander;
 myRecorder:=TZeosRecorder.Create(Self);
 myRecorder.Master:=@myRecorder;
 mySaveNullSubs:=DefSaveNullSubs;
 myPropertiesWanted:='';
end;

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

procedure TZeosConnecter.AfterConstruction;
begin
 inherited AfterConstruction;
 AllZeosConnecters.Add(Self);
 if Api.IsLinked then begin
  ZeosAssistant.CheckMonitor(1);
 end;
end;

procedure TZeosConnecter.BeforeDestruction;
begin
 AllZeosConnecters.Remove(Self);
 inherited BeforeDestruction;
end;

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

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

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

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

procedure TZeosConnecter.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 TZeosConnecter.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;
  end;
  Api.ParameterUnused(Opt);
 except
  on E:Exception do BugReport(E,Self,'Open');
 end;
end;

function TZeosConnecter.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.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 TZeosConnecter.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 TZeosConnecter.BeginTrans:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Api.IsLinked then
  if Api.Connection.Connected then begin
   Result:=Api.Connection.StartTransaction;
  end;
 except
  on E:Exception do BugReport(E,Self,'BeginTrans');
 end;
end;

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

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

function TZeosConnecter.Execute(arg:LongString; opt:Integer=0; rof:Boolean=True):TZeosRecorder;
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 TZeosConnecter.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 TZeosConnecter.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 TZeosConnecter.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 TZeosConnecter.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 TZeosConnecter.GetQuerySQLType:TStatementType;
begin
 Result:=Low(TStatementType); // stUnknown;
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   if not Api.Query.Prepared then Api.Query.Prepare;
   Result:=ZeosAssistant.GetStatementType(Api.Query.SQL.Text);
  end;
 except
  on E:Exception do BugReport(E,Self,'GetQuerySQLType');
 end;
end;

function TZeosConnecter.CreateDatabase:Boolean;
var cs,ps,db:LongString;
begin
 Result:=False;
 if Assigned(Self) then
 try
  if Api.IsLinked then begin
   if WordIndex(Api.Provider,'firebird,interbase',ScanSpaces)>0 then begin
    if Api.Connection.Connected then Api.Connection.Connected:=False;
    cs:='CREATE DATABASE ';
    if (Api.Connection.HostName<>'')
    then cs:=cs+AnsiQuotedStr(Api.Connection.HostName+':'+Api.Connection.Database,Apostrophe)
    else cs:=cs+AnsiQuotedStr(Api.Connection.Database,Apostrophe);
    if (Api.Connection.User<>'')
    then cs:=cs+' USER '+AnsiQuotedStr(Api.Connection.User,Apostrophe);
    if (Api.Connection.Password<>'')
    then cs:=cs+' PASSWORD '+AnsiQuotedStr(Api.Connection.Password,Apostrophe);
    ps:=Api.Connection.Properties.Values['PAGE_SIZE'];
    if (ps<>'') then cs:=cs+' PAGE_SIZE '+ps;
    if (Api.Connection.ClientCodepage<>'')
    then cs:=cs+' DEFAULT CHARACTER SET '+Api.Connection.ClientCodepage;
    Api.Connection.Properties.Add('CreateNewDatabase='+cs);
    Api.Connection.Connected:=True;
    Result:=True;
   end else
   if WordIndex(Api.Provider,'mssql',ScanSpaces)>0 then begin
    cs:='CREATE DATABASE '+Api.Connection.Database;
    Api.Connection.ExecuteDirect(cs);
    Result:=True;
   end else
   if WordIndex(Api.Provider,'postgresql',ScanSpaces)>0 then begin
    cs:='CREATE DATABASE '+Api.Connection.Database;
    Api.Connection.ExecuteDirect(cs);
    Result:=True;
   end else
   if WordIndex(Api.Provider,'sqlite,sqlite3',ScanSpaces)>0 then begin
    db:=Api.Connection.Database;
    if not FileExists(db) then FileClose(FileCreate(db));
    Result:=FileExists(db);
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'CreateDatabase');
 end;
end;

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

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

function TZeosConnecter.GetVersion:LongString;
begin
 if Assigned(Self) and Api.IsLinked
 then Result:=db_engine_name_zeos+' ZEOS '+Api.Connection.Version+' LCL '+lcl_version+' FPC '+GetFpcVersion
 else Result:='';
end;

function TZeosConnecter.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.TriggerNames') 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.Database);
     if Wanted('Connection.UserName') then AddProp(id,Api.Connection.User);
     if Wanted('Connection.Password') then AddProp(id,Api.Connection.Password);
     if Wanted('Connection.HostName') then AddProp(id,Api.Connection.HostName);
     if Wanted('Connection.Port') then AddProp(id,IntToStr(Api.Connection.Port));
     if Wanted('Connection.Catalog') then AddProp(id,Api.Connection.Catalog);
     if Wanted('Connection.CharSet') then AddProp(id,Api.Connection.ClientCodepage);
     if Wanted('Connection.LibLocation') then AddProp(id,Api.Connection.LibLocation);
     if Wanted('Connection.ClientVersion') then AddProp(id,Api.Connection.ClientVersionStr);
     if Wanted('Connection.ServerVersion') then AddProp(id,Api.Connection.ServerVersionStr);
     if Wanted('Connection.KnownCodePages') then AddProp(id,ZeosAssistant.KnownCodePages(Provider,','));
     if Wanted('Connection.AutoCommit') then AddProp(id,BoolToStr(Api.Connection.AutoCommit,'1','0'));
     if Wanted('Connection.ReadOnly') then AddProp(id,BoolToStr(Api.Connection.ReadOnly,'1','0'));
     if Wanted('Connection.Ping') then AddProp(id,BoolToStr(Api.Connection.Ping,'1','0'));
     if Wanted('Connection.PingServer') then AddProp(id,BoolToStr(Api.Connection.PingServer,'1','0'));
     if Wanted('Transaction.Active') then AddProp(id,BoolToStr(Api.Connection.InTransaction,'1','0'));
     if Wanted('Query.Active') then AddProp(id,BoolToStr(Api.Query.Active,'1','0'));
     if Wanted('Query.SQLText') then AddProp(id,QuerySQLText);
     if Wanted('Query.StatementType') then AddProp(id,DbCon.StatementTypeToString(QuerySQLType));
     if Wanted('Query.TableName') then AddProp(id,ZeosAssistant.GetStatementInfo(QuerySQLText).TableName);
     if Wanted('Query.RowsAffected') then AddProp(id,IntToStr(Api.Query.RowsAffected));
     if Wanted('Query.Filter') then AddProp(id,Api.Query.Filter);
     if Wanted('Query.Filtered') then AddProp(id,BoolToStr(Api.Query.Filtered,'1','0'));
     if Wanted('Query.IndexName') then AddProp(id,Api.Query.IndexFieldNames);
     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.GetStoredProcNames('',List);
       AddProp(id,List.CommaText);
       List.Clear;
      except
       on E:Exception do BugReport(E,Self,id);
      end;
     end;
     if Wanted('Connection.TriggerNames') then begin
      if Api.Connection.Connected then
      try
       List.Clear;
       Api.Connection.GetTriggerNames('','',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.Properties.Count-1 do begin
      if (ExtractNameValuePair(Api.Connection.Properties[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 TZeosConnecter.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.Database:=sv;
      if Wanted('Connection.UserName') then Api.Connection.User:=sv;
      if Wanted('Connection.Password') then Api.Connection.Password:=sv;
      if Wanted('Connection.HostName') then Api.Connection.HostName:=sv;
      if Wanted('Connection.CharSet') then Api.Connection.ClientCodePage:=sv;
      if Wanted('Connection.Catalog') then Api.Connection.Catalog:=sv;
      if Wanted('Connection.Port',iv) then Api.Connection.Port:=iv;
      if Wanted('Connection.LibLocation') then Api.Connection.LibLocation:=sv;
      if Wanted('Connection.AutoCommit',iv) then Api.Connection.AutoCommit:=(iv<>0);
      if Wanted('Connection.ReadOnly',iv) then Api.Connection.ReadOnly:=(iv<>0);
      if Wanted('Query.Active',iv) then Api.Query.Active:=(iv<>0);
      if Wanted('Query.SQLText') then QuerySQLText:=sv;
      if Wanted('Query.Filter') then Api.Query.Filter:=sv;
      if Wanted('Query.Filtered',iv) then Api.Query.Filtered:=(iv<>0);
      if Wanted('Query.IndexName') then Api.Query.IndexFieldNames:=sv;
      if Wanted('Transaction.Active',iv) then begin
        if (iv=0) and Api.Connection.InTransaction then Api.Connection.Commit;
        if (iv>0) and not Api.Connection.InTransaction then Api.Connection.StartTransaction;
      end;
      if StartsText('Connection.Params.',sn) and (Length(sn)>Length('Connection.Params.'))
      then Api.Connection.Properties.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 TZeosConnecter.GetConnectionTimeout:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

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

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

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

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

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

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

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

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

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

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

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

function TZeosConnecter.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 TZeosConnecter.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 TZeosConnecter.FieldTypeToCode(T:TFieldType):Integer;
begin
 Result:=Ord(T);
end;

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

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

/////////////////////////////////
// TZeosCommander implementation
/////////////////////////////////

constructor TZeosCommander.Create(aConnecter:TZeosConnecter);
begin
 inherited Create;
 myConnecter:=aConnecter;
end;

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

function TZeosCommander.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 TZeosCommander.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 TZeosCommander.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 TZeosCommander.GetCommandType:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
 if Assigned(Self) then Result:=adCmdText;
end;

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

function TZeosCommander.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 TZeosCommander.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;

////////////////////////////////
// TZeosRecorder implementation
////////////////////////////////

constructor TZeosRecorder.Create(aConnecter:TZeosConnecter);
begin
 inherited Create;
 myConnecter:=aConnecter;
end;

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

function TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.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 TZeosRecorder.GetAbsolutePage:Integer;
begin
 Result:=0;
 // Stub for ADO simulation
end;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

function TZeosRecorder.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
      Result:=SaveAsXml(Destination,Connecter.SaveNullSubs);
      Exit;
     end;
    end;
    if (df in [dfXml]) then begin
     Result:=SaveAsXml(Destination,Connecter.SaveNullSubs);
    end;
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'Save');
 end;
end;

function TZeosRecorder.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;

function TZeosRecorder.SaveAsXml(Dest:LongString; NullSubs:LongString):Boolean;
var i,rows,cols,nerr,iores:Integer; id,line,item,Buff:LongString;
var bm:TBookMark; T:Text; Items:TStringList; Field:TField;
const BuffSize=1024*48; ztime=' 0:00:00';
 function FormatField(Field:TField):LongString;
 var ft:TFieldType; fn,dn,sft,sub:LongString; p,sz:Integer;
 begin
  Items.Clear;
  fn:=Field.FieldName; dn:=Field.DisplayName;
  ft:=Field.DataType; sz:=Field.FieldDef.Size;
  sft:=DbCon.XMLFieldTypeToString(ft); sub:=''; p:=Pos(':',sft);
  if (p>0) then begin sub:=Copy(sft,p+1,25); sft:=Copy(sft,1,p-1); end;
  if (sz<>0) then Items.Values['width']:=AnsiQuotedStr(IntToStr(sz),QuoteMark);
  if (dn<>'') then Items.Values['attrname']:=AnsiQuotedStr(dn,QuoteMark);
  if (fn<>'') then Items.Values['fieldname']:=AnsiQuotedStr(fn,QuoteMark);
  if (sft<>'') then Items.Values['fieldtype']:=AnsiQuotedStr(sft,QuoteMark);
  if (sub<>'') then Items.Values['subtype']:=AnsiQuotedStr(sub,QuoteMark);
  Result:=StringReplace(Trim(Items.Text),EOL,' ',[rfReplaceAll]);
 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));
   Items:=TStringList.Create;
   try
    rows:=0; nerr:=0;
    cols:=Connecter.Api.DataSource.DataSet.FieldCount;
    writeln(T,'<?xml version="1.0" encoding="utf-8"?>');
    writeln(T,'<DATAPACKET Version="2.0">');
    writeln(T,' <METADATA>');
    writeln(T,'  <FIELDS>');
    for i:=0 to cols-1 do begin
     Field:=Connecter.Api.DataSource.DataSet.Fields[i];
     line:='   <FIELD '+FormatField(Field)+'/>';
     writeln(T,line); if (IoResult<>0) then Inc(nerr);
    end;
    writeln(T,'  </FIELDS>');
    writeln(T,'  <PARAMS/>');
    writeln(T,' </METADATA>');
    writeln(T,' <ROWDATA>');
    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];
      id:=Field.FieldName;
      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));
      Items.Values[id]:=AnsiQuotedStr(item,QuoteMark);
     end;
     line:='  <ROW '+StringReplace(Trim(Items.Text),EOL,' ',[rfReplaceAll])+'/>';
     writeln(T,line); if (IoResult<>0) then Inc(nerr);
     Connecter.Api.DataSource.DataSet.Next;
     Inc(rows);
    end;
    writeln(T,' </ROWDATA>');
    writeln(T,'</DATAPACKET>');
    Result:=(IOResult=0) and (nerr=0);
   finally
    Connecter.Api.DataSource.DataSet.Bookmark:=bm;
    SmartFileClose(T); SetInOutRes(iores);
    Kill(Items);
    Buff:='';
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'SaveAsXml');
 end;
end;

////////////////////////////////////
// AllZeosConnecters implementation
////////////////////////////////////

const
 TheZeosConnecters:TObjectStorage=nil;

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

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

procedure Init_crw_dbzeo;
begin
 dlc_ZeosBug;
 dlc_ZeosLog;
 ZeosAssistant.Ok;
 AllZeosConnecters.Ok;
 TZeosApi.DefVerbose:=True;
 TZeosApi.RaiseOnNilReference:=True;
 TZeosApi.RaiseOnResetProvider:=True;
 TZeosApi.RaiseOnResetConStrng:=True;
 TZeosConnecter.SelectableStatementTypes:=[stSelect];
 TZeosConnecter.DefSaveNullSubs:='';
 TZeosConnecter.SaveAsModeFlags:=0;
end;

procedure Free_crw_dbzeo;
begin
 Kill(TObject(TheZeosConnecters));
 ZeosAssistant.Free;
end;

initialization

 Init_crw_dbzeo;

finalization

 Free_crw_dbzeo;

end.

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

