From 6b32a6e0e599ed8d7efdfb6cfee43dde0d1d60a9 Mon Sep 17 00:00:00 2001
From: michael <michael@freepascal.org>
Date: Sat, 22 Nov 2014 16:28:09 +0000
Subject: [PATCH] * Transaction options and connection options

git-svn-id: trunk@29109 -
---
 packages/fcl-db/src/base/dbconst.pas          |  18 +-
 packages/fcl-db/src/sqldb/mysql/mysqlconn.inc |   2 +-
 .../fcl-db/src/sqldb/postgres/pqconnection.pp |  61 +++--
 packages/fcl-db/src/sqldb/sqldb.pp            | 103 +++++++--
 packages/fcl-db/tests/dbtestframework.pas     |   2 +
 packages/fcl-db/tests/sqldbtoolsunit.pas      |  43 ++--
 packages/fcl-db/tests/testsqldb.pas           | 212 ++++++++++++++----
 7 files changed, 350 insertions(+), 91 deletions(-)

diff --git a/packages/fcl-db/src/base/dbconst.pas b/packages/fcl-db/src/base/dbconst.pas
index ad57b90f39..62c778164d 100644
--- a/packages/fcl-db/src/base/dbconst.pas
+++ b/packages/fcl-db/src/base/dbconst.pas
@@ -105,14 +105,18 @@ Resourcestring
   SStreamNotRecognised     = 'The data-stream format is not recognized';
   SNoReaderClassRegistered = 'There is no TDatapacketReaderClass registered for this kind of data-stream';
   SErrCircularDataSourceReferenceNotAllowed = 'Circular datasource references are not allowed.';
-  SCommitting              = 'Committing transaction';
-  SRollingBack             = 'Rolling back transaction';
-  SCommitRetaining         = 'Commit and retaining transaction';
-  SRollBackRetaining       = 'Rollback and retaining transaction';
-  SErrNoFieldsDefined      = 'Can not create a dataset when there are no fielddefinitions or fields defined';
-  SErrApplyUpdBeforeRefresh= 'Must apply updates before refreshing data';
-  SErrNoDataset            = 'Missing (compatible) underlying dataset, can not open';
+  SCommitting               = 'Committing transaction';
+  SRollingBack              = 'Rolling back transaction';
+  SCommitRetaining          = 'Commit and retaining transaction';
+  SRollBackRetaining        = 'Rollback and retaining transaction';
+  SErrNoFieldsDefined       = 'Can not create a dataset when there are no fielddefinitions or fields defined';
+  SErrApplyUpdBeforeRefresh = 'Must apply updates before refreshing data';
+  SErrNoDataset             = 'Missing (compatible) underlying dataset, can not open';
   SErrDisconnectedPacketRecords = 'For disconnected TSQLQuery instances, packetrecords must be -1';
+  SErrImplicitNoRollBack      = 'Implicit use of transactions does not allow rollback.';
+  SErrNoImplicitTransaction   = 'Connection %s does not allow implicit transactions.';
+  SErrImplictTransactionStart = 'Error: attempt to implicitly start a transaction on Connection "%s", transaction "%s".';
+  SErrImplicitConnect         = 'Error: attempt to implicitly activate connection "%s".';
 
 Implementation
 
diff --git a/packages/fcl-db/src/sqldb/mysql/mysqlconn.inc b/packages/fcl-db/src/sqldb/mysql/mysqlconn.inc
index a054287146..646569b061 100644
--- a/packages/fcl-db/src/sqldb/mysql/mysqlconn.inc
+++ b/packages/fcl-db/src/sqldb/mysql/mysqlconn.inc
@@ -1122,7 +1122,7 @@ constructor TConnectionName.Create(AOwner: TComponent);
 const SingleBackQoutes: TQuoteChars = ('`','`');
 begin
   inherited Create(AOwner);
-  FConnOptions := FConnOptions + [sqEscapeRepeat] + [sqEscapeSlash];
+  FConnOptions := FConnOptions + [sqEscapeRepeat, sqEscapeSlash, sqImplicitTransaction];
   FieldNameQuoteChars:=SingleBackQoutes;
   FMySQL := Nil;
 end;
diff --git a/packages/fcl-db/src/sqldb/postgres/pqconnection.pp b/packages/fcl-db/src/sqldb/postgres/pqconnection.pp
index 48c24e8b58..d31ebbe238 100644
--- a/packages/fcl-db/src/sqldb/postgres/pqconnection.pp
+++ b/packages/fcl-db/src/sqldb/postgres/pqconnection.pp
@@ -116,6 +116,7 @@ type
     function RollBack(trans : TSQLHandle) : boolean; override;
     function Commit(trans : TSQLHandle) : boolean; override;
     procedure CommitRetaining(trans : TSQLHandle); override;
+    function StartImplicitTransaction(trans : TSQLHandle; AParams : string) : boolean; override;
     function StartdbTransaction(trans : TSQLHandle; AParams : string) : boolean; override;
     procedure RollBackRetaining(trans : TSQLHandle); override;
     procedure UpdateIndexDefs(IndexDefs : TIndexDefs;TableName : string); override;
@@ -263,7 +264,7 @@ constructor TPQConnection.Create(AOwner : TComponent);
 
 begin
   inherited;
-  FConnOptions := FConnOptions + [sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeRepeat, sqEscapeSlash];
+  FConnOptions := FConnOptions + [sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeRepeat, sqEscapeSlash, sqImplicitTransaction];
   FieldNameQuoteChars:=DoubleQuotes;
   VerboseErrors:=True;
   FConnectionPool:=TThreadlist.Create;
@@ -322,7 +323,8 @@ begin
 {$EndIf}
 end;
 
-procedure TPQConnection.GetExtendedFieldInfo(cursor: TPQCursor; Bindings: TFieldBindings);
+Procedure TPQConnection.GetExtendedFieldInfo(cursor: TPQCursor;
+  Bindings: TFieldBindings);
 
 Var
   tt,tc,Tn,S : String;
@@ -376,7 +378,7 @@ begin
     P.SQLDBData:=TPQCursor(C).GetFieldBinding(F.FieldDef);
 end;
 
-function TPQConnection.ErrorOnUnknownType: Boolean;
+Function TPQConnection.ErrorOnUnknownType: Boolean;
 begin
   Result:=False;
 end;
@@ -463,9 +465,8 @@ begin
   result := true;
 end;
 
-function TPQConnection.StartdbTransaction(trans : TSQLHandle; AParams : string) : boolean;
+function TPQConnection.StartImplicitTransaction(trans : TSQLHandle; AParams : string) : boolean;
 var
-  res : PPGresult;
   tr  : TPQTrans;
   i   : Integer;
   t : TPQTranConnection;
@@ -511,11 +512,6 @@ begin
     if CharSet <> '' then
       PQsetClientEncoding(tr.PGConn, pchar(CharSet));
     end;
-
-  res := PQexec(tr.PGConn, 'BEGIN');
-  CheckResultError(res,tr.PGConn,sErrTransactionFailed);
-
-  PQclear(res);
   result := true;
 end;
 
@@ -551,6 +547,24 @@ begin
   PQclear(res);
 end;
 
+function TPQConnection.StartDBTransaction(trans: TSQLHandle;
+  AParams: string): boolean;
+
+Var
+  res : PPGresult;
+  tr  : TPQTrans;
+
+begin
+  Result:=StartImplicitTransaction(trans, AParams);
+  if Result then
+    begin
+    tr := trans as TPQTrans;
+    res := PQexec(tr.PGConn, 'BEGIN');
+    CheckResultError(res,tr.PGConn,sErrTransactionFailed);
+    PQclear(res);
+    end;
+end;
+
 
 procedure TPQConnection.DoInternalConnect;
 var
@@ -648,9 +662,21 @@ var
   MESSAGE_DETAIL: string;
   MESSAGE_HINT: string;
   STATEMENT_POSITION: string;
+  P : Pchar;
+  haveError : Boolean;
+
 begin
-  if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
+  HaveError:=False;
+  if (Res=Nil) then
     begin
+    HaveError:=True;
+    P:=PQerrorMessage(conn);
+    If Assigned(p) then
+      ErrMsg:=StrPas(P);
+    end
+  else if (PQresultStatus(res) <> PGRES_COMMAND_OK) then
+    begin
+    HaveError:=True;
     SEVERITY:=PQresultErrorField(res,ord('S'));
     SQLSTATE:=PQresultErrorField(res,ord('C'));
     MESSAGE_PRIMARY:=PQresultErrorField(res,ord('M'));
@@ -667,6 +693,9 @@ begin
       MaybeAdd(sErr,'Hint',MESSAGE_HINT);
       MaybeAdd(sErr,'Character',STATEMENT_POSITION);
       end;
+    end;
+  if HaveError then
+    begin
     if (Self.Name='') then CompName := Self.ClassName else CompName := Self.Name;
     E:=EPQDatabaseError.CreateFmt('%s : %s  (PostgreSQL: %s)', [CompName, ErrMsg, sErr]);
     E.SEVERITY:=SEVERITY;
@@ -675,7 +704,6 @@ begin
     E.MESSAGE_DETAIL:=MESSAGE_DETAIL;
     E.MESSAGE_HINT:=MESSAGE_HINT;
     E.STATEMENT_POSITION:=STATEMENT_POSITION;
-
     PQclear(res);
     res:=nil;
     if assigned(conn) then
@@ -688,7 +716,7 @@ begin
 end;
 
 function TPQConnection.TranslateFldType(res: PPGresult; Tuple: integer; out
-  Size: integer; out ATypeOID: oid): TFieldType;
+  Size: integer; Out ATypeOID: oid): TFieldType;
 
 const
   VARHDRSZ=sizeof(longint);
@@ -769,18 +797,18 @@ begin
   end;
 end;
 
-function TPQConnection.AllocateCursorHandle: TSQLCursor;
+Function TPQConnection.AllocateCursorHandle: TSQLCursor;
 
 begin
   result := TPQCursor.create;
 end;
 
-procedure TPQConnection.DeAllocateCursorHandle(var cursor: TSQLCursor);
+Procedure TPQConnection.DeAllocateCursorHandle(var cursor: TSQLCursor);
 begin
   FreeAndNil(cursor);
 end;
 
-function TPQConnection.AllocateTransactionHandle: TSQLHandle;
+Function TPQConnection.AllocateTransactionHandle: TSQLHandle;
 
 begin
   result := TPQTrans.create;
@@ -838,6 +866,7 @@ var
   i : integer;
   P : TParam;
   PQ : TSQLDBParam;
+  r          : PPGresult;
 
 begin
   with (cursor as TPQCursor) do
diff --git a/packages/fcl-db/src/sqldb/sqldb.pp b/packages/fcl-db/src/sqldb/sqldb.pp
index 413471e23c..5cb7b76e1b 100644
--- a/packages/fcl-db/src/sqldb/sqldb.pp
+++ b/packages/fcl-db/src/sqldb/sqldb.pp
@@ -27,6 +27,9 @@ type
   TConnOption = (sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeSlash, sqEscapeRepeat, sqImplicitTransaction);
   TConnOptions= set of TConnOption;
 
+  TConnectionOption = (coExplicitConnect);
+  TConnectionOptions = Set of TConnectionOption;
+
   TConnInfoType=(citAll=-1, citServerType, citServerVersion, citServerVersionString, citClientName, citClientVersion);
   TStatementType = (stUnknown, stSelect, stInsert, stUpdate, stDelete,
     stDDL, stGetSegment, stPutSegment, stExecProcedure,
@@ -148,6 +151,7 @@ type
   TSQLConnection = class (TDatabase)
   private
     FFieldNameQuoteChars : TQuoteChars;
+    FOptions: TConnectionOptions;
     FPassword            : string;
     FTransaction         : TSQLTransaction;
     FUserName            : string;
@@ -159,6 +163,7 @@ type
     FOnLog: TDBLogNotifyEvent;
     FInternalTransaction : TSQLTransaction;
     function GetPort: cardinal;
+    procedure SetOptions(AValue: TConnectionOptions);
     procedure SetPort(const AValue: cardinal);
   protected
     FConnOptions         : TConnOptions;
@@ -204,6 +209,7 @@ type
     function GetTransactionHandle(trans : TSQLHandle): pointer; virtual; abstract;
     function Commit(trans : TSQLHandle) : boolean; virtual; abstract;
     function RollBack(trans : TSQLHandle) : boolean; virtual; abstract;
+    function StartImplicitTransaction(trans : TSQLHandle; aParams : string) : boolean; virtual;
     function StartdbTransaction(trans : TSQLHandle; aParams : string) : boolean; virtual; abstract;
     procedure CommitRetaining(trans : TSQLHandle); virtual; abstract;
     procedure RollBackRetaining(trans : TSQLHandle); virtual; abstract;
@@ -211,6 +217,8 @@ type
     procedure UpdateIndexDefs(IndexDefs : TIndexDefs; TableName : string); virtual;
     function GetSchemaInfoSQL(SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string; virtual;
 
+    Procedure MaybeConnect;
+
     Property Statements : TFPList Read FStatements;
     property Port: cardinal read GetPort write SetPort;
   public
@@ -239,6 +247,7 @@ type
     property HostName : string Read FHostName Write FHostName;
     Property OnLog : TDBLogNotifyEvent Read FOnLog Write FOnLog;
     Property LogEvents : TDBEventTypes Read FLogEvents Write FLogEvents Default LogAllEvents;
+    Property Options : TConnectionOptions Read FOptions Write SetOptions;
     property Connected;
     Property Role :  String read FRole write FRole;
     property DatabaseName;
@@ -252,16 +261,21 @@ type
 
   TCommitRollbackAction = (caNone, caCommit, caCommitRetaining, caRollback,
     caRollbackRetaining);
+  TTransactionOption = (toUseImplicit, toExplicitStart);
+  TTransactionOptions = Set of TTransactionOption;
 
   TSQLTransaction = class (TDBTransaction)
   private
+    FOptions: TTransactionOptions;
     FTrans               : TSQLHandle;
     FAction              : TCommitRollbackAction;
     FParams              : TStringList;
     function GetSQLConnection: TSQLConnection;
+    procedure SetOptions(AValue: TTransactionOptions);
     procedure SetParams(const AValue: TStringList);
     procedure SetSQLConnection(AValue: TSQLConnection);
   protected
+    Procedure MaybeStartTransaction;
     Function AllowClose(DS: TDBDataset): Boolean; override;
     function GetHandle : Pointer; virtual;
     Procedure SetDatabase (Value : TDatabase); override;
@@ -282,6 +296,7 @@ type
     property Action : TCommitRollbackAction read FAction write FAction Default caRollBack;
     property Database;
     property Params : TStringList read FParams write SetParams;
+    Property Options : TTransactionOptions Read FOptions Write SetOptions;
   end;
 
 
@@ -987,10 +1002,9 @@ begin
     DatabaseError(SErrDatabasenAssigned);
   if not assigned(Transaction) then
     DatabaseError(SErrTransactionnSet);
-  if not Database.Connected then
-    Database.Open;
+  Database.MaybeConnect;
   if not Transaction.Active then
-    Transaction.StartTransaction;
+    Transaction.MaybeStartTransaction;
   try
     DoPrepare;
   except
@@ -1145,7 +1159,8 @@ begin
     DatabaseError(SErrTransactionnSet);
 
   if not Connected then Open;
-  if not ATransaction.Active then ATransaction.StartTransaction;
+  if not (ATransaction.Active or (toUseImplicit in ATransaction.Options)) then
+    ATransaction.MaybeStartTransaction;
 
   try
     SQL := TrimRight(SQL);
@@ -1171,6 +1186,12 @@ begin
   result := StrToIntDef(Params.Values['Port'],0);
 end;
 
+procedure TSQLConnection.SetOptions(AValue: TConnectionOptions);
+begin
+  if FOptions=AValue then Exit;
+  FOptions:=AValue;
+end;
+
 
 procedure TSQLConnection.SetPort(const AValue: cardinal);
 begin
@@ -1657,6 +1678,12 @@ begin
   // empty
 end;
 
+function TSQLConnection.StartImplicitTransaction(trans: TSQLHandle;
+  aParams: string): boolean;
+begin
+  // Do nothing
+end;
+
 function TSQLConnection.GetSchemaInfoSQL( SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string;
 
 begin
@@ -1667,6 +1694,16 @@ begin
   end;
 end;
 
+Procedure TSQLConnection.MaybeConnect;
+begin
+  If Not Connected then
+    begin
+    If (coExplicitConnect in Options) then
+      DatabaseErrorFmt(SErrImplicitConnect,[Name]);
+    Connected:=True;
+    end;
+end;
+
 procedure TSQLConnection.CreateDB;
 
 begin
@@ -1689,7 +1726,10 @@ begin
       Commit;
     caNone,
     caRollback, caRollbackRetaining :
-      RollBack;
+      if not (toUseImplicit in Options) then
+        RollBack
+      else
+        CloseTrans;
   end;
 end;
 
@@ -1703,11 +1743,29 @@ begin
   Result:=Database as TSQLConnection;
 end;
 
+procedure TSQLTransaction.SetOptions(AValue: TTransactionOptions);
+begin
+  if FOptions=AValue then Exit;
+  if (toUseImplicit in Avalue) and Assigned(SQLConnection) And Not (sqImplicitTransaction in SQLConnection.ConnOptions) then
+    DatabaseErrorFmt(SErrNoImplicitTransaction,[SQLConnection.ClassName]);
+  FOptions:=AValue;
+end;
+
 procedure TSQLTransaction.SetSQLConnection(AValue: TSQLConnection);
 begin
   Database:=AValue;
 end;
 
+Procedure TSQLTransaction.MaybeStartTransaction;
+begin
+  if not Active then
+    begin
+    if (toExplicitStart in Options) then
+      DatabaseErrorFmt(SErrImplictTransactionStart,[Database.Name,Name]);
+    StartTransaction;
+    end;
+end;
+
 function TSQLTransaction.GetHandle: Pointer;
 begin
   Result := SQLConnection.GetTransactionHandle(FTrans);
@@ -1723,12 +1781,12 @@ end;
 
 procedure TSQLTransaction.Commit;
 begin
-  if Active then
+  if Active  then
     begin
     CloseDataSets;
     If LogEvent(detCommit) then
       Log(detCommit,SCommitting);
-    if SQLConnection.Commit(FTrans) then
+    if (toUseImplicit in Options) or SQLConnection.Commit(FTrans) then
       begin
       CloseTrans;
       FreeAndNil(FTrans);
@@ -1750,6 +1808,8 @@ procedure TSQLTransaction.Rollback;
 begin
   if Active then
     begin
+    if (toUseImplicit in Options) then
+      DatabaseError(SErrImplicitNoRollBack);
     CloseDataSets;
     If LogEvent(detRollback) then
       Log(detRollback,SRollingBack);
@@ -1765,6 +1825,8 @@ procedure TSQLTransaction.RollbackRetaining;
 begin
   if Active then
     begin
+    if (toUseImplicit in Options) then
+      DatabaseError(SErrImplicitNoRollBack);
     If LogEvent(detRollback) then
       Log(detRollback,SRollBackRetaining);
     SQLConnection.RollBackRetaining(FTrans);
@@ -1784,11 +1846,20 @@ begin
   if Db = nil then
     DatabaseError(SErrDatabasenAssigned);
 
-  if not Db.Connected then
-    Db.Open;
+  Db.MaybeConnect;
+
   if not assigned(FTrans) then FTrans := Db.AllocateTransactionHandle;
 
-  if Db.StartdbTransaction(FTrans,FParams.CommaText) then OpenTrans;
+  if (toUseImplicit in Options) then
+    begin
+    if Db.StartImplicitTransaction(FTrans,FParams.CommaText) then
+      OpenTrans
+    end
+  else
+    begin
+    if Db.StartdbTransaction(FTrans,FParams.CommaText) then
+      OpenTrans
+    end;
 end;
 
 constructor TSQLTransaction.Create(AOwner : TComponent);
@@ -1806,17 +1877,21 @@ begin
   inherited Destroy;
 end;
 
-procedure TSQLTransaction.SetDatabase(Value: TDatabase);
+Procedure TSQLTransaction.SetDatabase(Value: TDatabase);
 
 begin
   If Value<>Database then
     begin
-    if assigned(value) and not (Value is TSQLConnection) then
+    if Assigned(Value) and not (Value is TSQLConnection) then
       DatabaseErrorFmt(SErrNotASQLConnection,[value.Name],self);
     CheckInactive;
+    if (toUseImplicit in Options) and Assigned(Value) and Not (sqImplicitTransaction in TSQLConnection(Value).ConnOptions) then
+           DatabaseErrorFmt(SErrNoImplicitTransaction,[Value.ClassName]);
     If Assigned(Database) then
+      begin
       with SQLConnection do
         if Transaction = self then Transaction := nil;
+      end;
     inherited SetDatabase(Value);
     If Assigned(Database) and not (csLoading in ComponentState) then
       If (SQLConnection.Transaction=Nil) then
@@ -1824,12 +1899,12 @@ begin
     end;
 end;
 
-function TSQLTransaction.LogEvent(EventType: TDBEventType): Boolean;
+Function TSQLTransaction.LogEvent(EventType: TDBEventType): Boolean;
 begin
   Result:=Assigned(Database) and SQLConnection.LogEvent(EventType);
 end;
 
-procedure TSQLTransaction.Log(EventType: TDBEventType; const Msg: String);
+Procedure TSQLTransaction.Log(EventType: TDBEventType; Const Msg: String);
 
 Var
   M : String;
diff --git a/packages/fcl-db/tests/dbtestframework.pas b/packages/fcl-db/tests/dbtestframework.pas
index d0c37fa6d4..7499826a14 100644
--- a/packages/fcl-db/tests/dbtestframework.pas
+++ b/packages/fcl-db/tests/dbtestframework.pas
@@ -9,6 +9,7 @@ program dbtestframework;
 uses
   SysUtils,
   fpcunit,  testreport, testregistry,
+  ibase60dyn,
   DigestTestReport,
   toolsunit,
 // List of supported database connectors
@@ -63,6 +64,7 @@ Var
   Application : TTestRunner;  
   
 begin
+  ibase60dyn.InitialiseIBase60('libfbclient.so.2.5.2');
   InitialiseDBConnector;
   Try
     Application:=TTestRunner.Create(nil);
diff --git a/packages/fcl-db/tests/sqldbtoolsunit.pas b/packages/fcl-db/tests/sqldbtoolsunit.pas
index 84f3fd7f36..db910533e1 100644
--- a/packages/fcl-db/tests/sqldbtoolsunit.pas
+++ b/packages/fcl-db/tests/sqldbtoolsunit.pas
@@ -54,13 +54,14 @@ type
     procedure DropFieldDataset; override;
     Function InternalGetNDataset(n : integer) : TDataset; override;
     Function InternalGetFieldDataset : TDataSet; override;
-    procedure TryDropIfExist(ATableName : String);
   public
+    procedure TryDropIfExist(ATableName : String);
     destructor Destroy; override;
     constructor Create; override;
     procedure ExecuteDirect(const SQL: string);
     // Issue a commit(retaining) for databases that need it (e.g. in DDL)
     procedure CommitDDL;
+    Procedure FreeTransaction;
     property Connection : TSQLConnection read FConnection;
     property Transaction : TSQLTransaction read FTransaction;
     property Query : TSQLQuery read FQuery;
@@ -400,7 +401,7 @@ begin
       testValues[ftFixedChar,i] := PadRight(testValues[ftFixedChar,i], 10);
 end;
 
-function TSQLDBConnector.CreateQuery: TSQLQuery;
+Function TSQLDBConnector.CreateQuery: TSQLQuery;
 
 begin
   Result := TSQLQuery.create(nil);
@@ -555,7 +556,7 @@ begin
 end;
 
 procedure TSQLDBConnector.DoLogEvent(Sender: TSQLConnection;
-  EventType: TDBEventType; const Msg: String);
+  EventType: TDBEventType; Const Msg: String);
 var
   Category: string;
 begin
@@ -609,7 +610,7 @@ begin
     end;
 end;
 
-function TSQLDBConnector.InternalGetNDataset(n: integer): TDataset;
+Function TSQLDBConnector.InternalGetNDataset(n: integer): TDataset;
 begin
   Result := CreateQuery;
   with (Result as TSQLQuery) do
@@ -620,7 +621,7 @@ begin
     end;
 end;
 
-function TSQLDBConnector.InternalGetFieldDataset: TDataSet;
+Function TSQLDBConnector.InternalGetFieldDataset: TDataSet;
 begin
   Result := CreateQuery;
   with (Result as TSQLQuery) do
@@ -660,6 +661,11 @@ begin
         begin
         FConnection.ExecuteDirect('drop table if exists ' + ATableName);
         end;
+      ssPostgresql:
+        begin
+        FConnection.ExecuteDirect('drop table if exists ' + ATableName);
+        FTransaction.CommitRetaining;
+        end;
       ssOracle:
         begin
           FConnection.ExecuteDirect(
@@ -702,23 +708,34 @@ begin
     Transaction.CommitRetaining;
 end;
 
+Procedure TSQLDBConnector.FreeTransaction;
+begin
+  FreeAndNil(FTransaction);
+end;
+
 destructor TSQLDBConnector.Destroy;
 begin
+  FreeAndNil(FQuery);
   if assigned(FTransaction) then
     begin
     try
-      if Ftransaction.Active then Ftransaction.Rollback;
-      Ftransaction.StartTransaction;
-      Fconnection.ExecuteDirect('DROP TABLE FPDEV2');
-      Ftransaction.Commit;
+      if not (toUseImplicit in Transaction.Options) then
+        begin
+        if Ftransaction.Active then
+          Ftransaction.Rollback;
+        Ftransaction.StartTransaction;
+        end;
+      TryDropIfExist('FPDEV2');
+      if not (toUseImplicit in Transaction.Options) then
+        Ftransaction.Commit;
     Except
-      if Ftransaction.Active then Ftransaction.Rollback;
+      if Ftransaction.Active and not (toUseImplicit in Transaction.Options) then
+        Ftransaction.Rollback;
     end; // try
     end;
-  inherited Destroy;
-  FreeAndNil(FQuery);
-  FreeAndNil(FTransaction);
+  FreeTransaction;
   FreeAndNil(FConnection);
+  inherited Destroy;
 end;
 
 constructor TSQLDBConnector.Create;
diff --git a/packages/fcl-db/tests/testsqldb.pas b/packages/fcl-db/tests/testsqldb.pas
index c98737ceb6..04cca84c06 100644
--- a/packages/fcl-db/tests/testsqldb.pas
+++ b/packages/fcl-db/tests/testsqldb.pas
@@ -10,24 +10,27 @@ interface
 
 uses
   Classes, sqldb, SysUtils, fpcunit, testregistry,
-  db;
+  sqldbtoolsunit,toolsunit, db;
 
 type
 
   { TSQLDBTestCase }
 
   TSQLDBTestCase = class(TTestCase)
+  private
+    function GetDBC: TSQLDBConnector;
     protected
       procedure SetUp; override;
       procedure TearDown; override;
+      Property SQLDBConnector : TSQLDBConnector Read GetDBC;
   end;
 
   { TTestTSQLQuery }
 
   TTestTSQLQuery = class(TSQLDBTestCase)
-    procedure DoAfterPost(DataSet: TDataSet);
   private
     FMyQ: TSQLQuery;
+    procedure DoAfterPost(DataSet: TDataSet);
     Procedure Allow;
     Procedure SetQueryOPtions;
     Procedure TrySetPacketRecords;
@@ -45,8 +48,16 @@ type
 
   TTestTSQLConnection = class(TSQLDBTestCase)
   private
+    procedure SetImplicit;
+    procedure TestImplicitTransaction;
+    procedure TestImplicitTransaction2;
+    procedure TestImplicitTransactionNotAssignable;
+    procedure TestImplicitTransactionOK;
+    procedure TryOpen;
   published
-    procedure ReplaceMe;
+    procedure TestUseImplicitTransaction;
+    procedure TestUseExplicitTransaction;
+    procedure TestExplicitConnect;
   end;
 
   { TTestTSQLScript }
@@ -60,7 +71,6 @@ type
 
 implementation
 
-uses sqldbtoolsunit, toolsunit;
 
 
 { TTestTSQLQuery }
@@ -79,7 +89,7 @@ procedure TTestTSQLQuery.TestMasterDetail;
 var MasterQuery, DetailQuery: TSQLQuery;
     MasterSource: TDataSource;
 begin
-  with TSQLDBConnector(DBConnector) do
+  with SQLDBConnector do
   try
     MasterQuery := GetNDataset(10) as TSQLQuery;
     MasterSource := TDatasource.Create(nil);
@@ -107,7 +117,7 @@ begin
   // For ODBC Firebird/Interbase we must define primary key as named constraint and
   //  in ODBC driver must be set: "quoted identifiers" and "sensitive identifier"
   // See also: TTestFieldTypes.TestUpdateIndexDefs
-  with TSQLDBConnector(DBConnector) do
+  with SQLDBConnector do
   begin
     // SQLite ignores case-sensitivity of quoted table names
     // MS SQL Server case-sensitivity of identifiers depends on the case-sensitivity of default collation of the database
@@ -130,7 +140,7 @@ begin
   end;
 
   try
-    Q := TSQLDBConnector(DBConnector).Query;
+    Q := SQLDBConnector.Query;
     Q.SQL.Text:='select * from '+name1;
     Q.Prepare;
     Q.ServerIndexDefs.Update;
@@ -151,7 +161,7 @@ begin
     CheckTrue(Q.ServerIndexDefs[0].Options=[ixPrimary,ixUnique], '3.3');
   finally
     Q.UnPrepare;
-    with TSQLDBConnector(DBConnector) do
+    with SQLDBConnector do
     begin
       ExecuteDirect('DROP TABLE '+name1);
       ExecuteDirect('DROP TABLE '+name2);
@@ -167,7 +177,7 @@ var Q: TSQLQuery;
 begin
   // Test that for a disconnected SQL query, calling commit does not close the dataset.
   // Test also that an edit still works.
-  with TSQLDBConnector(DBConnector) do
+  with SQLDBConnector do
     begin
     try
       ExecuteDirect('DROP table testdiscon');
@@ -179,7 +189,7 @@ begin
     for I:=1 to 20 do
       ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[i,i]));
     Transaction.COmmit;
-    Q := TSQLDBConnector(DBConnector).Query;
+    Q := SQLDBConnector.Query;
     Q.SQL.Text:='select * from testdiscon';
     Q.QueryOptions:=[sqoDisconnected];
     AssertEquals('PacketRecords forced to -1',-1,Q.PacketRecords);
@@ -211,9 +221,9 @@ end;
 
 Procedure TTestTSQLQuery.TestDisconnectedPacketRecords;
 begin
-  with TSQLDBConnector(DBConnector) do
+  with SQLDBConnector do
     begin
-    FMyQ := TSQLDBConnector(DBConnector).Query;
+    FMyQ := SQLDBConnector.Query;
     FMyQ.QueryOptions:=[sqoDisconnected];
     AssertException('Cannot set packetrecords when sqoDisconnected is active',EDatabaseError,@TrySetPacketRecords);
     end;
@@ -228,7 +238,7 @@ end;
 Procedure TTestTSQLQuery.TestCheckSettingsOnlyWhenInactive;
 begin
   // Check that we can only set QueryOptions when the query is inactive.
-  with TSQLDBConnector(DBConnector) do
+  with SQLDBConnector do
     begin
     try
       ExecuteDirect('DROP table testdiscon');
@@ -239,9 +249,9 @@ begin
     Transaction.COmmit;
      ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[1,1]));
     Transaction.COmmit;
-    FMyQ := TSQLDBConnector(DBConnector).Query;
+    FMyQ := SQLDBConnector.Query;
     FMyQ.SQL.Text:='select * from testdiscon';
-    FMyQ := TSQLDBConnector(DBConnector).Query;
+    FMyQ := SQLDBConnector.Query;
     FMyQ.OPen;
     AssertException('Cannot set packetrecords when sqoDisconnected is active',EDatabaseError,@SetQueryOptions);
     end;
@@ -253,7 +263,7 @@ var Q: TSQLQuery;
 begin
   // Test that if sqoAutoApplyUpdates is in QueryOptions, then POST automatically does an ApplyUpdates
   // Test also that POST afterpost event is backwards compatible.
-  with TSQLDBConnector(DBConnector) do
+  with SQLDBConnector do
     begin
     try
       ExecuteDirect('DROP table testdiscon');
@@ -265,7 +275,7 @@ begin
     for I:=1 to 2 do
       ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[i,i]));
     Transaction.COmmit;
-    Q := TSQLDBConnector(DBConnector).Query;
+    Q := SQLDBConnector.Query;
     FMyQ:=Q; // so th event handler can reach it.
     Q.SQL.Text:='select * from testdiscon';
     Q.QueryOptions:=[  sqoAutoApplyUpdates];
@@ -292,7 +302,7 @@ var Q: TSQLQuery;
     I, J : Integer;
 begin
   // Test that if sqoAutoApplyUpdates is in QueryOptions, then Delete automatically does an ApplyUpdates
-  with TSQLDBConnector(DBConnector) do
+  with SQLDBConnector do
     begin
     try
       ExecuteDirect('DROP table testdiscon');
@@ -304,7 +314,7 @@ begin
     for I:=1 to 2 do
       ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[i,i]));
     Transaction.COmmit;
-    Q := TSQLDBConnector(DBConnector).Query;
+    Q := SQLDBConnector.Query;
     FMyQ:=Q; // so th event handler can reach it.
     Q.SQL.Text:='select * from testdiscon';
     Q.QueryOptions:=[  sqoAutoApplyUpdates];
@@ -323,11 +333,127 @@ begin
     end;
 end;
 
+
 { TTestTSQLConnection }
 
-procedure TTestTSQLConnection.ReplaceMe;
+procedure TTestTSQLConnection.TestImplicitTransaction;
+
+Var
+  T : TSQLTransaction;
+
 begin
-  // replace this procedure with any test for TSQLConnection
+  T:=TSQLTransaction.Create(Nil);
+  try
+    T.Options:=[toUseImplicit];
+    T.DataBase:=SQLDBConnector.Connection;
+  finally
+    T.Free;
+  end;
+end;
+
+procedure TTestTSQLConnection.TestImplicitTransaction2;
+
+Var
+  T : TSQLTransaction;
+
+begin
+  T:=TSQLTransaction.Create(Nil);
+  try
+    T.Options:=[toUseImplicit];
+    SQLDBConnector.Connection.Transaction:=T;
+  finally
+    T.Free;
+  end;
+end;
+
+procedure TTestTSQLConnection.SetImplicit;
+
+begin
+  SQLDBConnector.Transaction.Options:=[toUseImplicit];
+end;
+
+procedure TTestTSQLConnection.TestImplicitTransactionNotAssignable;
+
+begin
+  AssertException('Cannot set toUseImplicit option if database does not allow it',EDatabaseError,@SetImplicit);
+  AssertException('Cannot assign database to transaction with toUseImplicit, if database does not allow it',EDatabaseError,@TestImplicitTransaction);
+  AssertException('Cannot assign transaction with toUseImplicit to database, if database does not allow it',EDatabaseError,@TestImplicitTransaction2);
+end;
+
+procedure TTestTSQLConnection.TestImplicitTransactionOK;
+
+
+var
+  Q : TSQLQuery;
+  T : TSQLTransaction;
+  I, J : Integer;
+begin
+  with SQLDBConnector do
+    begin
+    try
+      TryDropIfExist('testdiscon');
+    except
+      // Ignore
+    end;
+    ExecuteDirect('create table testdiscon (id integer not null, a varchar(10), constraint pk_testdiscon primary key(id))');
+    if Transaction.Active then
+      Transaction.Commit;
+    end;
+  SetImplicit;
+  Q:=SQLDBConnector.Query;
+  for I:=1 to 2 do
+    begin
+    Q.SQL.Text:=Format('INSERT INTO testdiscon values (%d,''%.6d'');',[i,i]);
+    Q.Prepare;
+    Q.ExecSQL;
+    // We do not commit anything explicitly.
+    end;
+  Q:=Nil;
+  T:=Nil;
+  try
+    T:=TSQLTransaction.Create(Nil);
+    Q:=TSQLQuery.Create(Nil);
+    Q.Transaction:=T;
+    Q.Database:=SQLDBConnector.Connection;
+    T.Database:=SQLDBConnector.Connection;
+    Q.SQL.text:='SELECT COUNT(*) from testdiscon';
+    Q.Open;
+    AssertEquals('Records have been committed to database',2,Q.Fields[0].AsInteger);
+  finally
+    Q.Free;
+    T.Free;
+  end;
+end;
+
+procedure TTestTSQLConnection.TestUseImplicitTransaction;
+begin
+  if (sqImplicitTransaction in SQLDBConnector.Connection.ConnOptions) then
+    TestImplicitTransactionOK
+  else
+    TestImplicitTransactionNotAssignable;
+end;
+
+procedure TTestTSQLConnection.TryOpen;
+
+begin
+  SQLDBConnector.Query.Open;
+end;
+
+procedure TTestTSQLConnection.TestUseExplicitTransaction;
+begin
+  SQLDBConnector.Transaction.Active:=False;
+  SQLDBConnector.Transaction.Options:=[toExplicitStart];
+  SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
+  AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
+end;
+
+procedure TTestTSQLConnection.TestExplicitConnect;
+begin
+  SQLDBConnector.Transaction.Active:=False;
+  SQLDBConnector.Connection.Options:=[coExplicitConnect];
+  SQLDBConnector.Connection.Connected:=False;
+  SQLDBConnector.Query.SQL.Text:='select * from FPDEV';
+  AssertException('toExplicitStart raises exception on implicit start',EDatabaseError,@TryOpen)
 end;
 
 { TTestTSQLScript }
@@ -339,21 +465,21 @@ begin
   try
     with Ascript do
       begin
-      DataBase := TSQLDBConnector(DBConnector).Connection;
-      Transaction := TSQLDBConnector(DBConnector).Transaction;
+      DataBase := SQLDBConnector.Connection;
+      Transaction := SQLDBConnector.Transaction;
       Script.Clear;
       Script.Append('create table FPDEV_A (id int);');
       Script.Append('create table FPDEV_B (id int);');
       ExecuteScript;
       // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
-      TSQLDBConnector(DBConnector).CommitDDL;
+      SQLDBConnector.CommitDDL;
       end;
   finally
     AScript.Free;
-    TSQLDBConnector(DBConnector).ExecuteDirect('drop table FPDEV_A');
-    TSQLDBConnector(DBConnector).ExecuteDirect('drop table FPDEV_B');
+    SQLDBConnector.ExecuteDirect('drop table FPDEV_A');
+    SQLDBConnector.ExecuteDirect('drop table FPDEV_B');
     // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
-    TSQLDBConnector(DBConnector).CommitDDL;
+    SQLDBConnector.CommitDDL;
   end;
 end;
 
@@ -368,8 +494,8 @@ begin
   try
     with Ascript do
       begin
-      DataBase := TSQLDBConnector(DBConnector).Connection;
-      Transaction := TSQLDBConnector(DBConnector).Transaction;
+      DataBase := SQLDBConnector.Connection;
+      Transaction := SQLDBConnector.Transaction;
       Script.Clear;
       UseSetTerm := true;
       // Example procedure that selects table names
@@ -392,13 +518,13 @@ begin
         );
       ExecuteScript;
       // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
-      TSQLDBConnector(DBConnector).CommitDDL;
+      SQLDBConnector.CommitDDL;
       end;
   finally
     AScript.Free;
-    TSQLDBConnector(DBConnector).ExecuteDirect('DROP PROCEDURE FPDEV_TESTCOLON');
+    SQLDBConnector.ExecuteDirect('DROP PROCEDURE FPDEV_TESTCOLON');
     // Firebird/Interbase need a commit after a DDL statement. Not necessary for the other connections
-    TSQLDBConnector(DBConnector).CommitDDL;
+    SQLDBConnector.CommitDDL;
   end;
 end;
 
@@ -416,8 +542,8 @@ begin
   try
     with Ascript do
       begin
-      DataBase := TSQLDBConnector(DBConnector).Connection;
-      Transaction := TSQLDBConnector(DBConnector).Transaction;
+      DataBase := SQLDBConnector.Connection;
+      Transaction := SQLDBConnector.Transaction;
       Script.Clear;
       UseCommit:=true;
       // Example procedure that selects table names
@@ -427,9 +553,9 @@ begin
       Script.Append('COMMIT;');
       ExecuteScript;
       // This line should not run, as the commit above should have taken care of it:
-      //TSQLDBConnector(DBConnector).CommitDDL;
+      //SQLDBConnector.CommitDDL;
       // Test whether second line of script executed, just to be sure
-      CheckQuery:=TSQLDBConnector(DBConnector).Query;
+      CheckQuery:=SQLDBConnector.Query;
       CheckQuery.SQL.Text:='SELECT logmessage FROM fpdev_scriptusecommit ';
       CheckQuery.Open;
       CheckEquals(TestValue, CheckQuery.Fields[0].AsString, 'Insert script line should have inserted '+TestValue);
@@ -437,13 +563,18 @@ begin
       end;
   finally
     AScript.Free;
-    TSQLDBConnector(DBConnector).ExecuteDirect('DROP TABLE fpdev_scriptusecommit');
-    TSQLDBConnector(DBConnector).Transaction.Commit;
+    SQLDBConnector.ExecuteDirect('DROP TABLE fpdev_scriptusecommit');
+    SQLDBConnector.Transaction.Commit;
   end;
 end;
 
 { TSQLDBTestCase }
 
+function TSQLDBTestCase.GetDBC: TSQLDBConnector;
+begin
+  Result:=DBConnector as TSQLDBConnector;
+end;
+
 procedure TSQLDBTestCase.SetUp;
 begin
   inherited SetUp;
@@ -455,8 +586,9 @@ procedure TSQLDBTestCase.TearDown;
 begin
   DBConnector.StopTest(TestName);
   if assigned(DBConnector) then
-    with TSQLDBConnector(DBConnector) do
-      Transaction.Rollback;
+    with SQLDBConnector do
+      if Assigned(Transaction) and not (toUseImplicit in Transaction.Options) then
+        Transaction.Rollback;
   FreeDBConnector;
   inherited TearDown;
 end;