{ This file is part of the Free Component Library. Copyright (c) 2017 Michael Van Canneyt, member of the Free Pascal development team Report Designer Data connector for SQLDB based data. See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit fpreportdatasqldb; {$mode objfpc}{$H+} interface uses Classes, SysUtils, strutils, sqldb, db, fpjson, fpreportdata; Const keyConnection = 'connection'; keySQL = 'sql'; keyType = 'dbtype'; keyHostName = 'host'; keyDatabaseName = 'database'; keyUserName = 'user'; keyPassword = 'pwd'; keyRole = 'role'; keyParams = 'params'; KeyCharSet = 'charset'; keyHash = 'FPCRulez'; Resourcestring SErrNoConnectionData = 'No connection data available'; SErrNoSQL = 'No SQL statement set'; Type { TFPReportConnector } TFPReportConnector = Class(TSQLConnector) Private FRefCount: Integer; Class procedure init; class procedure done; Class var FPool : TStringList; Public Procedure LoadFromConfig(aConfig : TJSONObject); class function CreateConnection(aConfig: TJSONObject): TFPReportConnector; virtual; Class Function TestConnection (aConfig : TJSONObject) : string; virtual; class function CreateDataset(aOwner: TComponent; aConfig: TJSONObject): TSQLQuery; class function CreateConfigHash(aConfig: TJSONObject): String; Class Procedure StartRender(ADataset : TDataset); virtual; Class Procedure EndRender(ADataset : TDataset); virtual; Class procedure CheckDBRelease; Property RefCount : Integer Read FRefCount; end; { TFPReportQuery } TFPReportQuery = class(TSQLQuery) Public Constructor Create(AOwner : TComponent); override; Destructor Destroy; override; end; { TReportSQLtransaction } TFPReportSQLtransaction = Class(TSQLTransaction) private FStartRefCount: Integer; Public Procedure StartRender; Procedure EndRender; Property StartRefCount : Integer Read FStartRefCount; end; { TSQLDBReportDataHandler } TSQLDBReportDataHandler = Class(TFPReportDataHandler) Function CreateDataset(AOwner : TComponent; AConfig : TJSONObject) : TDataset; override; Class Procedure StartRender(ADataset : TDataset); override; Class Procedure EndRender(ADataset : TDataset); override; Class Function CheckConfig(AConfig: TJSONObject): String; override; Class Function DataType : String; override; Class Function DataTypeDescription : String; override; Class Function AllowMasterDetail: Boolean; override; Class Procedure SetMasterDataset(ADetail, AMaster: TDataset); override; end; implementation { TFPReportSQLtransaction } procedure TFPReportSQLtransaction.StartRender; Var Start : Boolean; begin Start:=(FStartRefCount=0); Inc(FStartRefCount); if Start and not Active then StartTransaction; end; procedure TFPReportSQLtransaction.EndRender; begin if FStartRefCount>0 then begin Dec(FStartRefCount); If FStartRefCount=0 then RollBack; end; end; { TFPReportQuery } constructor TFPReportQuery.Create(AOwner: TComponent); begin inherited Create(AOwner); ReadOnly:=True; end; destructor TFPReportQuery.Destroy; begin If Database is TFPReportConnector then Dec(TFPReportConnector(Database).FRefCount); inherited Destroy; TFPReportConnector.CheckDBRelease; end; { TFPReportConnector } class procedure TFPReportConnector.init; begin FPool:=TStringList.Create; FPool.OwnsObjects:=True; FPool.Sorted:=True; FPool.Duplicates:=dupError; end; class procedure TFPReportConnector.done; begin FreeAndNil(FPool); end; Class Function TFPReportConnector.CreateConfigHash(aConfig : TJSONObject) : String; Procedure AH(N,V : String); begin if (V<>'') then Result:=Result+';'+N+'='+V; end; Procedure AH(N : String); begin AH(N,aConfig.get(N,'')); end; Var A : TJSONArray; I : Integer; begin AH(keyType); AH(keyHostName); AH(keyDatabaseName); AH(keyUserName); AH(keyPassword); AH(keyRole); A:=aConfig.get(keyParams,TJSONArray(Nil)); If Assigned(A) then For I:=0 to A.Count-1 do AH(IntToStr(I),A.Strings[i]); end; class procedure TFPReportConnector.StartRender(ADataset: TDataset); var Q : TFPReportQuery; T : TFPReportSQLTransaction; begin if (aDataset is TFPReportQuery) then begin Q:=aDataset as TFPReportQuery; if Q.Transaction is TFPReportSQLTransaction then begin T:=Q.Transaction as TFPReportSQLTransaction; T.StartRender; end; end; end; class procedure TFPReportConnector.EndRender(ADataset: TDataset); var Q : TFPReportQuery; T : TFPReportSQLTransaction; begin if (aDataset is TFPReportQuery) then begin Q:=aDataset as TFPReportQuery; if Q.Transaction is TFPReportSQLTransaction then begin T:=Q.Transaction as TFPReportSQLTransaction; T.EndRender; end; end; end; class procedure TFPReportConnector.CheckDBRelease; Var I : Integer; begin For I:=FPool.Count-1 downto 0 do begin // Writeln('Connection count for ',FPool[i], ' : ',TFPReportConnector(FPool.Objects[i]).FRefCount); if TFPReportConnector(FPool.Objects[i]).FRefCount=0 then FPool.Delete(I); end; end; procedure TFPReportConnector.LoadFromConfig(aConfig: TJSONObject); Var S : String; A : TJSONArray; I : Integer; begin ConnectorType:=aConfig.get(keyType,''); HostName:=aConfig.get(keyHostName,''); DatabaseName:=aConfig.get(keyDatabaseName,''); UserName:=aConfig.get(keyUserName,''); S:=aConfig.get(keyPassword,''); if (S<>'') then Password:=XORDecode(keyHash,S); Role:=aConfig.get(keyRole,''); Params.Clear; A:=aConfig.get(keyParams,TJSONArray(Nil)); If Assigned(A) then For I:=0 to A.Count-1 do Params.Add(A.Strings[i]); end; class function TFPReportConnector.CreateConnection(aConfig: TJSONObject): TFPReportConnector; begin Result:=Self.Create(Nil); Result.LoadFromConfig(aConfig); Result.LogEvents:=LogAllEventsExtra; Result.Transaction:=TFPReportSQLtransaction.Create(Result); end; class function TFPReportConnector.TestConnection(aConfig: TJSONObject): string; Var C : TFPReportConnector; begin Result:=''; C:=CreateConnection(aConfig); try C.Connected:=True; except On E : Exception do Result:=E.Message; end; C.free; end; class function TFPReportConnector.CreateDataset(aOwner: TComponent; aConfig: TJSONObject): TSQLQuery; Var S : String; C : TFPReportConnector; I : integer; O : TJSONObject; begin O:=aConfig.Get(keyConnection,TJSONObject(Nil)); if O=Nil then Raise EDatabaseError.Create(SErrNoConnectionData); S:=CreateConfigHash(o); i:=FPool.IndexOf(S); if (I<>-1) then C:=FPool.Objects[i] as TFPReportConnector else begin C:=CreateConnection(o); FPool.AddObject(S,C); end; Result:=TFPReportQuery.Create(aOwner); Result.Database:=C; Result.SQL.Text:=aConfig.get(keySQL,''); // Result.UniDirectional:=True; Result.PacketRecords:=-1; Result.UsePrimaryKeyAsKey:=False; Inc(C.FRefCount); end; { TSQLDBReportDataHandler } function TSQLDBReportDataHandler.CreateDataset(AOwner: TComponent; AConfig: TJSONObject): TDataset; begin Result:=TFPReportConnector.CreateDataset(aOwner,aConfig); end; class procedure TSQLDBReportDataHandler.StartRender(ADataset: TDataset); begin TFPReportConnector.StartRender(aDataset); end; class procedure TSQLDBReportDataHandler.EndRender(ADataset: TDataset); begin TFPReportConnector.EndRender(aDataset); end; class function TSQLDBReportDataHandler.CheckConfig(AConfig: TJSONObject): String; Var O : TJSONObject; begin O:=aConfig.Get(keyConnection,TJSONObject(Nil)); if (O=Nil) or (O.Count=0) then Result:=SErrNoConnectionData else if Trim(aConfig.Get(keySQL,''))='' then Result:=SErrNoSQL end; class function TSQLDBReportDataHandler.DataType: String; begin Result:='SQLDB'; end; class function TSQLDBReportDataHandler.DataTypeDescription: String; begin Result:='SQL Database server'; end; class function TSQLDBReportDataHandler.AllowMasterDetail: Boolean; begin Result:=True; end; class procedure TSQLDBReportDataHandler.SetMasterDataset(ADetail, AMaster: TDataset); Var Q : TSQLQuery; DS : TDatasource; begin Q:=(ADetail as TSQLQuery); DS:=Q.DataSource; if DS=Nil then begin DS:=TDatasource.Create(Q); Q.Datasource:=DS; end; DS.Dataset:=AMaster; end; initialization TSQLDBReportDataHandler.RegisterHandler; TFPReportConnector.Init; Finalization TFPReportConnector.Done; end.