From 301ad9bc6e99dd1b47acc1878298d3792a4949f5 Mon Sep 17 00:00:00 2001 From: Glaucos Ginez Date: Tue, 18 Feb 2020 23:22:36 -0300 Subject: [PATCH] Invalidate connections (#7) --- MiniREST.SQL.Base.pas | 31 ++++++++++++++++++++++ MiniREST.SQL.Intf.pas | 3 +++ MiniREST.SQL.SQLDb.pas | 23 +++++++++++++++++ unittest/SQL/Test.SQL.Default.pas | 41 +++++++++++++++++++++++++++++- unittest/TEST.FDB | Bin 1064960 -> 1064960 bytes 5 files changed, 97 insertions(+), 1 deletion(-) diff --git a/MiniREST.SQL.Base.pas b/MiniREST.SQL.Base.pas index 431105d..58cf8be 100644 --- a/MiniREST.SQL.Base.pas +++ b/MiniREST.SQL.Base.pas @@ -48,6 +48,7 @@ TMiniRESTSQLConnectionFactoryBase = class abstract(TInterfacedObject, IMiniRES function GetQueueCount: Integer; virtual; abstract; function GetConnection(const AIdentifier: string): IMiniRESTSQLConnection; overload; function GetSingletonConnection: IMiniRESTSQLConnection; + procedure InvalidateConnections; end; { TMiniRESTSQLConnectionBase } @@ -56,9 +57,11 @@ TMiniRESTSQLConnectionBase = class abstract(TInterfacedObject, IMiniRESTSQLCon strict private FOwner: TObject; FConnectionID: Integer; + FValid: Boolean; protected FName: string; FEstaNoPool: Boolean; + procedure SetValid(const AValid: Boolean); function _Release: Integer; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; function GetObject: TObject; virtual; abstract; procedure SetOwner(AOwner: Pointer); @@ -78,6 +81,8 @@ TMiniRESTSQLConnectionBase = class abstract(TInterfacedObject, IMiniRESTSQLCon function GetDatabaseInfo: IMiniRESTSQLDatabaseInfo; virtual; abstract; function InTransaction: Boolean; virtual; abstract; function GetConnectionID: Integer; + function IsValid: Boolean; + procedure Invalidate; virtual; abstract; end; TMiniRESTSQLPrimaryKeyInfo = class(TInterfacedObject, IMiniRESTSQLPrimaryKeyInfo) @@ -354,6 +359,7 @@ function TMiniRESTSQLConnectionFactoryBase.GetConnection(const AIdentifier: stri LConnection := InternalGetconnection.SetName('Connection' + IntToStr(FConnectionCounter)); Inc(FConnectionCounter); Result := LConnection; + TMiniRESTSQLConnectionBase(Result).SetValid(True); end else begin @@ -379,6 +385,7 @@ function TMiniRESTSQLConnectionFactoryBase.GetConnection(const AIdentifier: stri LConnection := InternalGetconnection.SetName('Connection' + IntToStr(FConnectionCounter) + ' ' + AIdentifier); Inc(FConnectionCounter); Result := LConnection; + TMiniRESTSQLConnectionBase(Result.GetObject).SetValid(True); end else begin @@ -484,6 +491,30 @@ function TMiniRESTSQLConnectionFactoryBase.GetSingletonConnection: IMiniRESTSQLC Result := FSingletonConnection; end; +procedure TMiniRESTSQLConnectionFactoryBase.InvalidateConnections; +var + I: Integer; + LConnectionObj: TMiniRESTSQLConnectionBase; + LConnection: IMiniRESTSQLConnection; +begin + for I := 0 to (FConnectionsToNotifyFree.Count - 1) do + begin + LConnectionObj := FConnectionsToNotifyFree.Items[I]; + if LConnectionObj.GetInterface(IMiniRESTSQLConnection, LConnection) then + LConnection.Invalidate; + end; +end; + +function TMiniRESTSQLConnectionBase.IsValid: Boolean; +begin + Result := FValid; +end; + +procedure TMiniRESTSQLConnectionBase.SetValid(const AValid: Boolean); +begin + FValid := AValid; +end; + initialization gConnectionIDCounter := 0; diff --git a/MiniREST.SQL.Intf.pas b/MiniREST.SQL.Intf.pas index 8d78af5..8a5ccff 100644 --- a/MiniREST.SQL.Intf.pas +++ b/MiniREST.SQL.Intf.pas @@ -55,6 +55,8 @@ interface function SetName(const AName: string): IMiniRESTSQLConnection; function GetDatabaseInfo: IMiniRESTSQLDatabaseInfo; function GetConnectionID: Integer; + function IsValid: Boolean; + procedure Invalidate; end; IMiniRESTSQLConnectionFactory = interface @@ -66,6 +68,7 @@ interface function GetObject: TObject; function GetConnectionsCount: Integer; function GetQueueCount: Integer; + procedure InvalidateConnections; property ConnectionsCount: Integer read GetConnectionsCount; property QueueCount: Integer read GetQueueCount; end; diff --git a/MiniREST.SQL.SQLDb.pas b/MiniREST.SQL.SQLDb.pas index 1994fe4..dcb8e2d 100644 --- a/MiniREST.SQL.SQLDb.pas +++ b/MiniREST.SQL.SQLDb.pas @@ -75,6 +75,7 @@ TMiniRESTSQLConnectionSQLDb = class(TMiniRESTSQLConnectionBase) function GetDriverName(const ADatabaseType: TMiniRESTSQLDatabaseType): string; procedure Log(Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String); procedure SetMiniRESTSQLParamToSQLParam(AMiniRESTSQLParam: IMiniRESTSQLParam; ASQLParam: TParam); + procedure CheckConnectionIsValid; public constructor Create(AOwner: IMiniRESTSQLConnectionFactory; AParams: IMiniRESTSQLConnectionFactoryParamsSQLDb); destructor Destroy; override; @@ -89,6 +90,7 @@ TMiniRESTSQLConnectionSQLDb = class(TMiniRESTSQLConnectionBase) function Execute(const ACommand: string; AParams: array of IMiniRESTSQLParam): Integer; override; function GetDatabaseInfo: IMiniRESTSQLDatabaseInfo; override; function InTransaction: Boolean; override; + procedure Invalidate; override; end; { TMiniRESTSQLQuerySQLDb } @@ -224,6 +226,7 @@ procedure TMiniRESTSQLConnectionSQLDb.Connect; LName: string; I: Integer; begin + CheckConnectionIsValid; LStringList := TStringList.Create; try if FSQLConnection.Connected then @@ -247,6 +250,7 @@ procedure TMiniRESTSQLConnectionSQLDb.Connect; procedure TMiniRESTSQLConnectionSQLDb.StartTransaction; begin + CheckConnectionIsValid; if FTransaction.Active then FTransaction.Rollback; FTransaction.StartTransaction; @@ -255,18 +259,21 @@ procedure TMiniRESTSQLConnectionSQLDb.StartTransaction; procedure TMiniRESTSQLConnectionSQLDb.Commit; begin + CheckConnectionIsValid; FTransaction.Commit; FInExplicitTransaction := False; end; procedure TMiniRESTSQLConnectionSQLDb.Rollback; begin + CheckConnectionIsValid; FTransaction.Rollback; FInExplicitTransaction := False; end; function TMiniRESTSQLConnectionSQLDb.GetQuery: IMiniRESTSQLQuery; begin + CheckConnectionIsValid; Result := TMiniRESTSQLQuerySQLDb.Create(Self); end; @@ -293,6 +300,7 @@ function TMiniRESTSQLConnectionSQLDb.Execute(const ACommand: string; AParams: ar LParam: TParam; LMiniRESTSQLParam: IMiniRESTSQLParam; begin + CheckConnectionIsValid; Self.Connect; LQry := TSQLQuery.Create(nil); try @@ -321,6 +329,7 @@ function TMiniRESTSQLConnectionSQLDb.Execute(const ACommand: string; AParams: ar function TMiniRESTSQLConnectionSQLDb.GetDatabaseInfo: IMiniRESTSQLDatabaseInfo; begin + CheckConnectionIsValid; Result := nil; case FConnectionParams.GetDatabaseType of dbtFirebird: Result := TMiniRESTSQLDatabaseInfoFirebird.Create(Self); @@ -523,6 +532,7 @@ procedure TMiniRESTSQLConnectionSQLDb.SetMiniRESTSQLParamToSQLParam(AMiniRESTSQL function TMiniRESTSQLConnectionSQLDb.InTransaction: Boolean; begin + CheckConnectionIsValid; Result := FSQLConnection.Transaction.Active; end; @@ -538,4 +548,17 @@ function TMiniRESTSQLConnectionFactorySQLDb.GetQueueCount: Integer; Result := 0; end; +procedure TMiniRESTSQLConnectionSQLDb.Invalidate; +begin + SetValid(False); + FreeAndNil(FSQLConnection); + FreeAndNil(FTransaction); +end; + +procedure TMiniRESTSQLConnectionSQLDb.CheckConnectionIsValid; +begin + if not IsValid then + raise Exception.Create('A conexão foi invalidada.'); +end; + end. diff --git a/unittest/SQL/Test.SQL.Default.pas b/unittest/SQL/Test.SQL.Default.pas index 1a24b13..347cfcf 100644 --- a/unittest/SQL/Test.SQL.Default.pas +++ b/unittest/SQL/Test.SQL.Default.pas @@ -100,6 +100,14 @@ TMiniRESTSQLTest = class({$IFNDEF FPC}TObject{$ELSE}TTestCase{$IFEND}) [Test] {$IFEND} procedure TestGetSingletonConnection; + {$IFNDEF FPC} + [Test] + {$IFEND} + procedure TestConnectionIsValid; + {$IFNDEF FPC} + [Test] + {$IFEND} + procedure TestConnectionIsNotValid; (* {$IFNDEF FPC} [Test] {$IFEND} @@ -150,8 +158,9 @@ procedure TMiniRESTSQLTest.TearDown; var LConnection: IMiniRESTSQLConnection; begin - LConnection := FConnectionFactory.GetConnection; +(* LConnection := FConnectionFactory.GetConnection; LConnection.Execute('DELETE FROM CUSTOMER', []); + *) FConnectionPoolEvents.Free; end; @@ -713,4 +722,34 @@ procedure TMiniRESTSQLTest.TestGetSingletonConnection; {$IFEND} end; +procedure TMiniRESTSQLTest.TestConnectionIsValid; +var + LConn1: IMiniRESTSQLConnection; +begin + LConn1 := FConnectionFactory.GetConnection; + {$IFNDEF FPC} + Assert.IsTrue(LConn1.IsValid, 'LCon1 não está válida.'); + {$ELSE} + CheckTrue(LConn1.IsValid, 'LCon1 não está válida.'); + {$IFEND} +end; + +procedure TMiniRESTSQLTest.TestConnectionIsNotValid; +var + LConn1: IMiniRESTSQLConnection; +begin + LConn1 := FConnectionFactory.GetConnection; + {$IFNDEF FPC} + Assert.IsTrue(LConn1.IsValid, 'LCon1 não está válida.'); + {$ELSE} + CheckTrue(LConn1.IsValid, 'LCon1 não está válida.'); + {$IFEND} + FConnectionFactory.InvalidateConnections; + {$IFNDEF FPC} + Assert.IsFalse(LConn1.IsValid, 'LCon1 está válida.'); + {$ELSE} + CheckFalse(LConn1.IsValid, 'LCon1 está válida.'); + {$IFEND} +end; + end. diff --git a/unittest/TEST.FDB b/unittest/TEST.FDB index e0cba94cf27af1316ab7e789d63a1819942954a4..945692e569ad65312dcdb0988688dd8e047c9759 100644 GIT binary patch delta 15902 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 delta 18297 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