Unit UTqbe; { Small DBdataset derived class and TREMOTEQBE }
{ David Berneda
100115,1155@compuserve.com }
{
NEW !!! TRemoteQBE to execute qbe queries remotely
(needs REMOQUE.EXE on the remote machine)
NEW !!! Function GetUserName:String (returns IDAPI user
name or Paradox)
}
{ NEW !!! AnswerType property allows to PARADOX,DBASE and ASCII
answer table types }
{ NEW !!! Query params can be defined (see demo.pas) }
interface
Uses Classes,DBTables,DB,SysUtils,DBConsts,LibConst,dbiProcs,dbiTypes,
DsgnIntf;
{ WARNING: READ CAREFULLY AND GOOD LUCK USING TQBE }
(*
Answer table name can be specified with or without alias.
Eg: :dbdemos:void.db
Assumed answer table Driver: PARADOX
The new property: AnswerType allows to PARADOX,DBASE and ASCII
answer table types
If the answer table exists, an atempt to delete it is made before
copying
the result cursor.
The phisical answer table must be opened in exclusive mode and
all related
family files are erased together with the table.
Other functions in this unit:
** Function GetAliasPath(Const Alias:String):String;
Returns the path for the "alias" or
a empty string if not found.
Eg: ('dbdemos') returns 'c:\delphi\demos\data'
** Function GetDBTablePath(Const TableName:String):String;
Returns the TableName with path instead
of alias (if it has an alias).
Eg: (':dbdemos:customer.db') returns
'c:\delphi\demos\data\customer.db'
*)
Const MaxParam = 5; { max number of query parameters
}
MaxParamLen=30; { max length of a substituted
param }
Type TQBE=Class(TDBDataSet)
private
FAnswerTable:String;
FAnswerType:TTableType;
FBlankasZero,
FAuxTables,
FRequestLive:Boolean;
protected
function CreateHandle: HDBICur;
override;
procedure GenerateAnswer(Var p:HDbiCur);
Function CreateSubstituted:TStrings;
public
FQBE: TStrings;
NumParam:Integer;
Param,Subst:Array[0..MaxParam]
of String[MaxParamLen];
procedure SetQBE(QBE: TStrings);
Constructor Create(AOwner:TComponent);
override;
destructor Destroy; override;
Procedure AddParam(Const tmpParam,tmpSubst:String);
Function ReplaceString(s:String):String;
Procedure ClearParams;
published
property QBE: TStrings read FQBE
write SetQBE;
property AnswerTable: String read
FAnswerTable write FAnswerTable;
property BlankasZero: Boolean
read FBlankasZero write FBlankAsZero;
property AuxTables: Boolean read
FAuxTables write FAuxTables;
property AnswerType:TTableType
read FAnswerType write FAnswerType;
property RequestLive: Boolean
read FRequestLive write FRequestLive;
End;
TRemoteStatus=( rsStart,
rsWaitingBegin,
rsWaitingExecution,
rsOK,
rsError,
rsTimeout,
rsIdle,
rsCancel
);
TNotifyRemote=procedure(Sender:TObject; what:TRemoteStatus) of object;
TRemoteQBE=Class(TQBE)
Private
FCancel:Boolean;
FOnWaiting:TNotifyRemote;
FSecondsTimeout:Longint;
FDoRemote:Boolean;
Procedure Notify(what:TRemoteStatus);
protected
function CreateHandle: HDBICur;
override;
public
Constructor Create(AOwner:TComponent);
override;
published
property OnWaiting:TNotifyRemote
read FOnWaiting write FOnWaiting;
property SecondsTimeout:Longint
read FSecondsTimeout write FSecondsTimeout default 60;
property DoRemote:Boolean read
FDoRemote write FDoRemote default TRUE;
property Cancel:Boolean read FCancel
write FCancel;
End;
Function GetAliasPath(Const Alias:String):String;
Function GetDBTablePath(Const TableName:String):String;
Procedure Register;
implementation
Uses WinProcs,IniFiles,Dialogs,dbiErrs,Forms;
Constructor TQBE.Create(AOwner:TComponent);
Begin
inherited Create(AOwner);
FQBE := TStringList.Create;
NumParam:=0;
FAnswerType:=ttParadox; { by default, Paradox answer tables
}
end;
destructor TQBE.Destroy;
Begin
FQBE.Free;
inherited Destroy;
End;
Procedure TQBE.ClearParams;
Begin
NumParam:=0; { reset params to zero (no params) }
End;
Procedure TQBE.AddParam(Const tmpParam,tmpSubst:String);
Begin
if (tmpParam<>'') and (tmpParam<>tmpSubst) then
Begin
if NumParam<MaxParam then
Begin
Inc(NumParam);
Param[NumParam]:=tmpParam;
Subst[NumParam]:=tmpSubst;
End
Else Raise Exception.Create('Max number of query
parameters achieved');
End;
End;
Function TQBE.ReplaceString(s:String):String;
Var t,i:Integer;
Begin
for t:=1 to NumParam do
Repeat
i:=Pos(Param[t],s);
if i>0 then s:=Copy(s,1,i-1)+Subst[t]+Copy(s,i+Length(Param[t]),255);
Until i=0;
result:=s;
End;
Function TQBE.CreateSubstituted:TStrings;
Var NewQBE:TStrings;
t:Integer;
Begin
NewQBE:=TStringList.Create;
if Assigned(NewQBE) then
Begin
With FQBE do
for t:=0 to Count-1 do NewQBE.Add(ReplaceString(Strings[t]));
End;
result:=NewQBE;
End;
function TQBE.CreateHandle: HDBICur;
Var p:HDbiCur;
Stmt:hDBIStmt;
NewQBE:TStrings;
Begin
NewQBE:=CreateSubstituted;
try
Check(dbiQPrepare(DBHandle,qryLangQBE,NewQBE.GetText,Stmt));
if FRequestLive then Check(dbiSetProp(hDBIObj(Stmt),stmtLIVENESS,Longint(wantLive)))
Else Check(dbiSetProp(hDBIObj(Stmt),stmtLIVENESS,Longint(wantDefault)));
if FBlankAsZero then Check(dbiSetProp(hDBIObj(Stmt),stmtBLANKS,1));
if FAuxTables then Check(dbiSetProp(hDBIObj(Stmt),stmtAUXTBLS,1));
Check(dbiQExec(Stmt,@p));
Check(dbiQFree(Stmt));
GenerateAnswer(p);
finally
NewQBE.Free;
end;
Result:=p;
End;
procedure TQBE.GenerateAnswer(Var p:HDbiCur);
Var aBatTblDesc:BATTblDesc;
tmpType:String;
r:Longint;
dbiErr:DBIRESULT;
Begin
if (FAnswerTable<>'') And Assigned(p) then
try
Check(DbiSetToBegin(p));
With aBatTblDesc do
Begin
hDB:=DBHandle;
StrPCopy(szTblName,GetDBTablePath(FAnswerTable));
Case FAnswerType of
ttParadox: tmpType:=szParadox;
ttDbase : tmpType:=szDbase;
ttAscii : tmpType:=szAscii;
end;
StrPCopy(szTblType,tmpType);
szUsername[0]:=#0;
szPassword[0]:=#0;
End;
r:=0;
dbiErr:=dbiDeleteTable(DBHandle,aBatTblDesc.szTblName,aBatTblDesc.szTblType);
if dbiErr<>DBIERR_NOSUCHTABLE then Check(dbiErr);
Check(DbiBatchMove(nil,p,@aBatTblDesc,nil,batchCOPY,0,
nil, nil, nil, 0, nil, nil,
nil, nil, nil, nil, TRUE, TRUE,
r, TRUE));
Check(dbiCloseCursor(p));
Check(DbiOpenTable(DBHandle, aBatTblDesc.szTblName,
aBatTblDesc.szTblType,
nil, nil, 0,
dbiReadWrite,
dbiOpenShared,
xltField,
False,
nil,
p));
finally
End;
end;
procedure TQBE.SetQBE(QBE: TStrings);
begin
FQBE.Assign(QBE);
end;
Function HasAlias(Const TableName:String):Boolean;
Begin
Result:=Pos(':',TableName)>0;
End;
Function GetAliasPath(Const Alias:String):String;
Var AliasList:TStringList;
i:Longint;
DBPath:String;
Begin
Result:='';
AliasList:=TStringList.Create;
try
Session.GetAliasNames(AliasList);
i:=AliasList.IndexOf(Alias);
if i<0 then raise EDatabaseError.Create('Alias
'+Alias+' doesnt exist')
else
Begin
Session.GetAliasParams(Alias,AliasList);
DBPath := AliasList.Values['PATH'];
if DBPath='' then raise EDatabaseError.Create('Alias
path from '+Alias+' invalid')
else Result:=DBPath;
end;
finally
AliasList.Free;
end;
End;
Procedure SplitTableName(Const TableName:String; Var Alias,Name:String);
Var p1,p2:Integer;
Begin
Name:=TableName;
Alias:='';
p1:=Pos(':',TableName);
if p1>0 then
Begin
p2:=Pos(':',Copy(TableName,p1+1,255));
if p2>0 then
Begin
Alias:=Copy(TableName,p1+1,p2-1);
Name:=Copy(TableName,p1+p2+1,255);
End;
End;
End;
Function GetDBTablePath(Const TableName:String):String;
Var Alias,Name:String;
Begin
if not HasAlias(TableName) then Result:=TableName
else
Begin
SplitTableName(TableName,Alias,Name);
if Alias<>'' then Result:=GetAliasPath(Alias)+'\'+Name
else Result:=TableName;
End;
End;
Type TString=Array[0..255] of char;
Function GetUserName:String;
Var St:TString;
Begin
result:='Unknown';
if DbiGetNetUserName(St)=DBIERR_NONE then
result:=StrPas(St)
else
With TIniFile.Create('win.ini') do
try
result:=ReadString('PDOXWIN','USERNAME','UNKNOWN');
finally
Free;
End;
end;
{ TREMOTE QBE }
Constructor TRemoteQBE.Create(AOwner:TComponent);
Begin
inherited Create(AOwner);
FSecondsTimeout:=60;
FDoRemote:=TRUE;
FCancel:=FALSE;
End;
Procedure TRemoteQBE.Notify(what:TRemoteStatus);
Begin
if Assigned(FOnWaiting) then
try
FOnWaiting(Self,what);
except
on Exception do ;
end;
End;
function TRemoteQBE.CreateHandle: HDBICur;
Function WaitServer(Table:TTable; NumQuery:Longint;
Const WaitFor:String):String;
Var Timeout:Longint;
Begin
result:='';
Timeout:=GetTickCount+SecondsTimeout*1000;
Repeat
Notify(rsIdle);
Table.Refresh;
Application.ProcessMessages;
if Table.FindKey([NumQuery])
then
result:=Table.FieldByName('Estado').AsString
else
result:='';
Until (Pos(result,WaitFor)>0) or
(GetTickCount>Timeout) or
FCancel or
Application.Terminated;
if FCancel then result:='C'; { canceled
}
End;
Var Status:String;
SAnswer:TString;
t:TTable;
NewQBE:TStrings;
NumQuery:Longint;
Begin
FCancel:=False;
if not FDoRemote then
Begin
result:=Inherited CreateHandle;
exit;
end;
result:=nil;
Notify(rsStart);
t:=TTable.Create(Self);
with t do
try
DatabaseName:='REMOQUE';
TableName:='remoque.db';
IndexFieldNames:='NumeroConsulta';
Open;
Append;
FieldByName('Fecha').AsDateTime:=SysUtils.Date;
FieldByName('Hora').AsDateTime:=Now;
FieldByName('Cliente').AsString:='DELPHI';
FieldByName('Cliente').AsString:='DELPHI';
FieldByName('Usuario').AsString:=GetUserName;
FieldByName('TipoConsulta').AsString:='Q';
NewQBE:=CreateSubstituted;
try
FieldByName('Consulta').Assign(NewQBE);
finally
NewQBE.Free;
end;
FieldByName('Estado').AsString:='P';
FieldByName('Error').AsString:='';
Post;
NumQuery:=FieldByName('NumeroConsulta').AsInteger;
Notify(rsWaitingBegin);
Status:=WaitServer(t,NumQuery,'EOM');
if Status='E' then
Begin
Notify(rsWaitingExecution);
Status:=WaitServer(t,NumQuery,'OM');
end;
if Status='O' then
Begin
Notify(rsOK);
StrPCopy(SAnswer,GetAliasPath('REMOQUE')+'\PERSONAL\'+FieldByName('NumeroConsulta').AsString);
Check(DbiOpenTable(DBHandle, SAnswer,
szParadox,
nil, nil, 0,
dbiReadWrite,
dbiOpenShared,
xltField,
False,
nil,
Result));
GenerateAnswer(result);
end
else
if Status='M' then
Begin
Notify(rsError);
raise Exception.Create(FieldByName('Error').AsString);
end
else
if Status='P' then
Begin
Notify(rsTimeout);
if FindKey([NumQuery]) then
try
Edit;
FieldByName('Estado').AsString:='T';
FieldByName('Error').AsString:='Timeout';
Post;
finally
raise Exception.Create('Timeout
while waiting RemoQUE Server');
end;
end
else
if Status='C' then { cancelled query }
try
Notify(rsCancel);
if FindKey([NumQuery]) then
try
Edit;
FieldByName('Estado').AsString:='C';
FieldByName('Error').AsString:='Canceled';
Post;
finally
end;
finally
SysUtils.Abort;
End;
finally
Free;
end;
End;
Procedure Register;
Begin
RegisterComponents(LoadStr(srDAccess),[TQBE,TRemoteQBE]);
End;
end.