Files
lazarus-ccr/components/flashfiler/sourcelaz/ffsqleng.pas
2016-12-07 13:31:59 +00:00

664 lines
22 KiB
ObjectPascal

{*********************************************************}
{* FlashFiler: SQL Engine class *}
{*********************************************************}
(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TurboPower FlashFiler
*
* The Initial Developer of the Original Code is
* TurboPower Software
*
* Portions created by the Initial Developer are Copyright (C) 1996-2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** *)
{$I ffdefine.inc}
{$DEFINE SQLSupported}
unit ffsqleng;
interface
uses
classes,
windows,
sysutils,
ffconst,
ffllbase,
fflleng,
ffllexcp,
fflldict,
ffsql,
ffsqlbas,
ffsrbase,
ffsrbde,
ffsrcvex,
ffsqldb, {!!.11}
fftbdict,
ffsreng;
type
{ A prepared statement is an SQL query in tokenized, parsed, executable form.
All SQL statements must become prepared statements before they can be
executed (whether the client specifically "prepares" them or not). Trigger
and stored procedure code is loaded from the relevant system tables into
a persistent prepared statement so that it can be executed on demand. }
TffSqlPreparedStatement = class(TffBasePreparedStmt) {!!.01}{!!.10}
protected {private}
spsParser : TffSQL;
// FTimeout : Longint; {Deleted !!.01}
spsDatabaseProxy : TFFSqlDatabaseProxy; {!!.11}
public
constructor Create2(anEngine : TffServerEngine; {!!.01}
aClientID : TffClientID;
aDatabaseID: TffDatabaseID;
aTimeout : Longint);
destructor Destroy; override;
procedure Bind; override; {!!.11}
function Execute(var aLiveResult: Boolean; {!!.10}
var aCursorID: TffCursorID;
var aRowsAffected: Integer;
var aRecordsRead: Integer): TffResult; override;{!!.10}
function Parse(aQuery: PChar): Boolean; override; {!!.10}
function SetParams(aNumParams: Word; aParamInfo: PffSqlParamInfoList; aDataBuffer: PffByteArray): TffResult;
end;
{ The SQL engine orchestrates all the activity in the server relevant to
SQL processing. The TffServerEngine class hands off SQL requests to the
SQL engine for processing, which in turn hands off results back to the
TffServerEngine for return to the client.
There is only one instance of TffSqlEngine and that is SQLEngine declared
within this unit. All communication with the SQL Engine can take place
through this global variable. }
TffSqlEngine = class(TffBaseSQLEngine)
protected { private}
protected
sqlPreparedStatementsList: TffSrStmtList;
public
constructor Create(aOwner : TComponent); override;
destructor Destroy; override;
function Alloc(anEngine : TffBaseServerEngine;
aClientID: TffClientID;
aDatabaseID: TffDatabaseID;
aTimeout : Longint;
var aStmtID: TffSqlStmtID): TffResult; override;
{- Implementation of DbiQAlloc }
{Begin !!.01}
procedure CollectGarbage; override;
{ Clears out SQL prepared statements that were still active when
the client sent a free statement request. }
{End !!.01}
function Exec(aStmtID: TffSqlStmtID;
aOpenMode: TffOpenMode;
var aCursorID: TffCursorID;
aStream: TStream): TffResult; override;
{- Implementation of DbiQExec. aCursorID is 0 if no result set
returned. aStream is used to pass back dictionary for the
result set (only if aCursorID <> 0). }
function ExecDirect(anEngine : TffBaseServerEngine;
aClientID : TffClientID;
aDatabaseID : TffDatabaseID;
aQueryText : PChar;
aOpenMode : TffOpenMode;
aTimeout : Longint;
var aCursorID : TffCursorID;
aStream : TStream): TffResult; override;
{- Implementation of DbiQExecDirect. aCursorID is 0 if no result set
is returned. aStream is used to pass back dictionary for the
result set. }
function FreeStmt(aStmtID: TffSqlStmtID): TffResult; override;
{- Implementation of DbiQFree }
function Prepare(aStmtID: TffSqlStmtID;
aQueryText: PChar;
aStream : TStream): TffResult; override;
{- Implementation of DbiQPrepare }
{Begin !!.03}
procedure RemoveForClient(const aClientID : TffClientID); override;
{- Remove the prepared statements associated with a particular client. }
procedure RequestClose; override;
{- Ask the remaining SQL prepared statements to close. This occurs
when preparing for shutdown with the goal of preventing a cursor
being freed before its SQL table proxy is freed. }
{End !!.03}
function SetParams(aStmtID: TffSqlStmtID;
aNumParams: Word;
aParamDescs: PffSqlParamInfoList;
aDataBuffer: PffByteArray;
aStream : TStream): TffResult; override;
{- Implementation of DbiQSetParams }
end;
implementation
uses
{$IFDEF DCC6OrLater}
Variants,
{$ENDIF}
ffstdate;
type
PComp = ^Comp;
{===TffSqlPreparedStatement==========================================}
constructor TffSqlPreparedStatement.Create2(anEngine : TffServerEngine; {!!.01}
aClientID : TffClientID;
aDatabaseID: TffDatabaseID;
aTimeout : Longint);
var {!!.10}
parentDB : TffSrDatabase; {!!.10}
begin
inherited Create(aTimeout); {!!.01}
bpsEngine := anEngine;
bpsDatabaseID := aDatabaseID;
spsParser := TffSql.Create(nil);
bpsClientID := aClientID;
soClient := TffSrClient(aClientID); {!!.01}{!!.10}
// FTimeout := aTimeout; {Deleted !!.01}
{Begin !!.10}
parentDB := TffSrDatabase(bpsDatabaseID);
parentDB.StmtList.BeginWrite;
try
parentDB.StmtList.AddStmt(Self);
finally
parentDB.StmtList.EndWrite;
end;
{End !!.10}
end;
{--------}
destructor TffSqlPreparedStatement.Destroy;
begin
spsDatabaseProxy.Free; {!!.12}
if assigned(spsParser) then begin
if spsParser.RootNode <> nil then begin
spsParser.RootNode.Free;
spsParser.RootNode := nil;
end;
spsParser.Free;
end;
{Begin !!.10}
{ Assumption: By this point, the SQL prepared statement has been removed
or is in the processor of being removed from the client's statement list. }
{End !!.10}
inherited Destroy;
end;
{Begin !!.11}
{--------}
procedure TffSqlPreparedStatement.Bind;
begin
if (spsParser <> nil) and
(spsParser.RootNode <> nil) then begin
spsDatabaseProxy := TFFSqlDatabaseProxy.Create(bpsEngine, bpsDatabaseID);
spsParser.RootNode.Bind(bpsClientID, 0, spsDatabaseProxy);
end;
end;
{--------}
function TffSqlPreparedStatement.Execute(var aLiveResult: Boolean;
var aCursorID: TffCursorID;
var aRowsAffected: Integer;
var aRecordsRead: Integer): TffResult;
begin
{try} {!!.12}
Result :=
spsParser.RootNode.Execute(aLiveResult, aCursorID,
aRowsAffected, aRecordsRead);
{ !!.12
finally
spsDatabaseProxy.Free;
end;
}
end;
{End !!.11}
{--------}
function TffSqlPreparedStatement.Parse(aQuery: PChar): Boolean;
begin
if spsParser.RootNode <> nil then begin
spsParser.RootNode.Free;
spsParser.RootNode := nil;
end;
spsParser.SourceStream.SetSize(StrLen(aQuery) + 1);
move(aQuery^, spsParser.SourceStream.Memory^, StrLen(aQuery) + 1);
spsParser.Execute;
Result := spsParser.Successful;
end;
{--------}
function TffSqlPreparedStatement.SetParams(aNumParams: Word;
aParamInfo: PffSqlParamInfoList;
aDataBuffer: PffByteArray): TffResult;
var
I: Integer;
Value : Variant;
FieldBuffer : PffByteArray;
D : double;
W : WideString;
WC : WideChar;
DT : TDateTime;
VPtr : PByte; {!!.13}
begin
Result := DBIERR_NONE;
for I := 0 to aNumParams - 1 do begin
with aParamInfo^[I] do begin
if piName = '' then { named parameter }
piName := ':' + IntToStr(piNum); { unnamed parameter }
FieldBuffer := PffByteArray(DWord(aDataBuffer) + piOffset);
case piType of
fftBoolean :
Value := Boolean(FieldBuffer^[0]);
fftChar :
Value := Char(FieldBuffer^[0]);
fftWideChar :
begin
WC := PWideChar(FieldBuffer)^;
W := WC;
Value := W;
end;
fftByte :
Value := PByte(FieldBuffer)^;
fftWord16 :
Value := PWord(FieldBuffer)^;
fftWord32 :
begin
D := PDWord(FieldBuffer)^;
Value := D;
end;
fftInt8 :
Value := PShortInt(FieldBuffer)^;
fftInt16 :
Value := PSmallInt(FieldBuffer)^;
fftInt32 :
Value := PInteger(FieldBuffer)^;
fftAutoInc :
Value := PInteger(FieldBuffer)^;
fftSingle :
Value := PSingle(FieldBuffer)^;
fftDouble :
Value := PDouble(FieldBuffer)^;
fftExtended :
Value := PExtended(FieldBuffer)^;
fftComp :
Value := PComp(FieldBuffer)^;
fftCurrency :
Value := PCurrency(FieldBuffer)^;
fftStDate :
Value := StDateToDateTime(PStDate(FieldBuffer)^);
fftStTime :
Value := StTimeToDateTime(PStTime(FieldBuffer)^);
fftDateTime :
begin
DT := PffDateTime(FieldBuffer)^;
VarCast(Value, DT - 693594, varDate); {!!.11}
end;
fftShortString :
Value := PShortString(FieldBuffer)^;
fftShortAnsiStr :
Value := PShortString(FieldBuffer)^;
fftNullString :
Value := StrPas(PChar(FieldBuffer));
fftNullAnsiStr :
Value := string(PChar(FieldBuffer));
fftWideString :
Value := WideString(PWideChar(FieldBuffer));
{Begin !!.13}
fftBLOB..fftBLOBTypedBin :
if piLength = 0 then
Value := ''
else begin
Value := VarArrayCreate([1, piLength], varByte);
VPtr := VarArrayLock(Value);
try
Move(FieldBuffer^, VPtr^, piLength);
finally
VarArrayUnlock(Value);
end;
end;
{End !!.13}
else
raise Exception.Create('Unsupported field type');
end;
spsParser.RootNode.SetParameter(I, Value);
end;
end;
end;
{====================================================================}
{===TffSqlEngine=====================================================}
constructor TffSqlEngine.Create(aOwner : TComponent);
begin
inherited Create(aOwner);
{create list for prepared statements}
sqlPreparedStatementsList := TffSrStmtList.Create;
end;
{--------}
destructor TffSqlEngine.Destroy;
begin
sqlPreparedStatementsList.Free;
inherited Destroy;
end;
{--------}
function TffSqlEngine.Exec(aStmtID: TffSqlStmtID;
aOpenMode: TffOpenMode;
var aCursorID: TffCursorID;
aStream: TStream) : TffResult;
var
L : Integer;
RowsAffected: Integer; {!!.10}
RecordsRead: Integer; {!!.10}
LiveResult: Boolean; {!!.10}
IndexID : Longint; {!!.11}
begin
{$IFNDEF SQLSupported}
Result := DBIERR_NOTSUPPORTED;
{$ELSE}
try
aCursorID := 0;
{Begin !!.01}
with sqlPreparedStatementsList.Stmt[ftFromID, aStmtID] do begin
Activate;
try
FFSetRetry(Timeout);
LiveResult := aOpenMode = omReadWrite; {!!.10}
Result := Execute(LiveResult, aCursorID, RowsAffected, {!!.10}
RecordsRead); {!!.10}
if Result <> 0 then Exit;
if Assigned(aStream) then begin
if (aCursorID <> 0) then begin
{query}
Assert(TObject(ACursorID) is TffSrBaseCursor);
aStream.Write(aCursorID, SizeOf(aCursorID));
TffSrBaseCursor(ACursorID).Dictionary.WriteToStream(aStream);
{Begin !!.11}
{ If this is a pre-2.11 client then write index ID 0 to the
stream. }
if TffSrClient(ClientID).ClientVersion < ffVersion2_10 then begin
IndexID := 0;
aStream.Write(IndexID, SizeOf(IndexID));
end
else begin
aStream.Write(LiveResult, SizeOf(LiveResult));
aStream.Write(RecordsRead, SizeOf(RecordsRead));
end
{End !!.11}
end else begin
{data manipulation}
aStream.Write(aCursorID, SizeOf(aCursorID)); {zero} {!!.10}
aStream.Write(RowsAffected, SizeOf(RowsAffected)); {!!.10}
aStream.Write(RecordsRead, SizeOf(RecordsRead)); {!!.10}
end;
end;
finally
Deactivate;
end;
end; { while }
{End !!.01}
except
on E : Exception do begin
Result := ConvertServerException(E, FEventLog);
L := length(E.Message);
aStream.Write(L, sizeof(L));
aStream.Write(E.Message[1], L);
end;
end;
{$ENDIF}
end;
{--------}
function TffSqlEngine.ExecDirect(anEngine : TffBaseServerEngine;
aClientID : TffClientID;
aDatabaseID : TffDatabaseID;
aQueryText : PChar;
aOpenMode : TffOpenMode;
aTimeout : Longint;
var aCursorID : TffCursorID;
aStream : TStream): TffResult;
var
Statement: TffSqlPreparedStatement;
L : Integer;
RowsAffected: Integer; {!!.10}
RecordsRead: Integer; {!!.10}
LiveResult: Boolean; {!!.10}
begin
{$IFNDEF SQLSupported}
Result := DBIERR_NOTSUPPORTED;
{$ELSE}
Result := DBIERR_NONE;
Assert(anEngine is TffServerEngine); {!!.01}
Statement := nil;
try
aCursorID := 0;
Statement := TffSqlPreparedStatement.Create2(TffServerEngine(anEngine), {!!.01}
aClientID, {!!.01}
aDatabaseID, aTimeout);
try
if Statement.Parse(aQueryText) then begin
LiveResult := aOpenMode = omReadWrite; {!!.10}
Result := Statement.Execute(LiveResult, {!!.10}
aCursorID, RowsAffected, RecordsRead); {!!.10}
if Assigned(aStream) then begin
if (aCursorID <> 0) then begin
{query}
aStream.Write(aCursorID, SizeOf(aCursorID));
TffSrBaseCursor(ACursorID).Dictionary.WriteToStream(aStream);
aStream.Write(LiveResult, SizeOf(LiveResult)); {!!.10}
aStream.Write(RecordsRead, SizeOf(RecordsRead)); {!!.10}
end else begin
{data manipulation}
aStream.Write(aCursorID, SizeOf(aCursorID)); {zero} {!!.10}
aStream.Write(RowsAffected, SizeOf(RowsAffected)); {!!.10}
aStream.Write(RecordsRead, SizeOf(RecordsRead)); {!!.10}
end;
end;
end else
raise Exception.Create('SQL Syntax error');
finally
Statement.Free;
end;
except
on E : Exception do begin
Result := ConvertServerException(E, FEventLog);
if Statement <> nil then begin
L := Statement.spsParser.ListStream.Size;
aStream.Write(L, sizeof(L));
Statement.spsParser.ListStream.Seek(0, 0);
aStream.CopyFrom(Statement.spsParser.ListStream, L);
end else begin
L := 0;
aStream.Write(L, sizeof(L));
end;
end;
end;
{$ENDIF}
end;
{--------}
function TffSqlEngine.FreeStmt(aStmtID: TffSqlStmtID) : TffResult;
var {!!.10}
parentDB : TffSrDatabase; {!!.10}
Stmt : TffBasePreparedStmt; {!!.13}
begin
{$IFNDEF SQLSupported}
Result := DBIERR_NOTSUPPORTED;
{$ELSE}
Result := DBIERR_NONE;
try
{Begin !!.01}
Stmt := sqlPreparedStatementsList.Stmt[ftFromID, aStmtID]; {!!.13}
if Stmt <> nil then {!!.13}
with Stmt do begin {!!.13}
if CanClose(True) then begin
sqlPreparedStatementsList.DeleteStmt(aStmtID);
parentDB := TffSrDatabase(DatabaseID); {!!.10}
parentDB.StmtList.BeginWrite; {!!.10}
try
parentDB.StmtList.DeleteStmt(aStmtID); {!!.10}
finally
parentDB.StmtList.EndWrite; {!!.10}
end;
end
else
RequestClose;
end; { with }
{End !!.01}
except
on E : Exception do begin
Result := ConvertServerException(E, FEventLog);
end;
end;
{$ENDIF}
end;
{--------}
function TffSqlEngine.Alloc(anEngine : TffBaseServerEngine;
aClientID : TffClientID;
aDatabaseID : TffDatabaseID;
aTimeout : Longint;
var aStmtID : TffSqlStmtID): TffResult;
var
Statement: TffSqlPreparedStatement;
begin
{$IFNDEF SQLSupported}
Result := DBIERR_NOTSUPPORTED;
{$ELSE}
aStmtID := 0;
Result := DBIERR_NONE;
Assert(anEngine is TffServerEngine); {!!.01}
try
Statement := TffSqlPreparedStatement.Create2(TffServerEngine(anEngine), {!!.01}
aClientID, {!!.01}
aDatabaseID, aTimeout);
try
sqlPreparedStatementsList.AddStmt(Statement); {!!.10}
aStmtID := Statement.Handle; {!!.10}
except
Statement.Free;
raise;
end;
except
on E : Exception do begin
Result := ConvertServerException(E, FEventLog);
end;
end;
{$ENDIF}
end;
{Begin !!.01}
{--------}
procedure TffSqlEngine.CollectGarbage;
begin
sqlPreparedStatementsList.RemoveUnused;
end;
{End !!.01}
{--------}
function TffSqlEngine.Prepare(aStmtID: TffSqlStmtID;
aQueryText: PChar;
aStream : TStream) : TffResult;
var
L : Integer;
Stmt : TffSqlPreparedStatement;
begin
{$IFNDEF SQLSupported}
Result := DBIERR_NOTSUPPORTED;
{$ELSE}
try
{Begin !!.01}
Result := DBIERR_NONE;
with sqlPreparedStatementsList.Stmt[ftFromID, aStmtID] do begin
Activate;
try
{Begin !!.11}
if Parse(aQueryText) then
Bind
else
raise Exception.Create('SQL syntax error');
{End !!.11}
finally
Deactivate;
end;
end;
{End !!.01}
except
on E : Exception do begin
Result := ConvertServerException(E, FEventLog);
Stmt := TffSqlPreparedStatement(sqlPreparedStatementsList.Stmt[ftFromID, aStmtID]);
L := Stmt.spsParser.ListStream.Size;
aStream.Write(L, sizeof(L));
Stmt.spsParser.ListStream.Seek(0, 0);
aStream.CopyFrom(Stmt.spsParser.ListStream, L);
end;
end;
{$ENDIF}
end;
{Begin !!.03}
{--------}
procedure TffSqlEngine.RemoveForClient(const aClientID : TffClientID);
begin
sqlPreparedStatementsList.RemoveForClient(aClientID);
end;
{--------}
procedure TffSqlEngine.RequestClose;
begin
{ Free up the remaining SQL prepared statements. }
sqlPreparedStatementsList.RequestClose;
sqlPreparedStatementsList.RemoveUnused;
end;
{End !!.03}
{--------}
function TffSqlEngine.SetParams(aStmtID: TffSqlStmtID;
aNumParams: Word;
aParamDescs: PffSqlParamInfoList;
aDataBuffer: PffByteArray;
aStream : TStream): TffResult;
var
Stmt : TffSQLPreparedStatement;
begin
{$IFNDEF SQLSupported}
Result := DBIERR_NOTSUPPORTED;
{$ELSE}
try
{Begin !!.01}
Stmt := TffSqlPreparedStatement(sqlPreparedStatementsList.Stmt[ftFromID, aStmtID]);
Stmt.Activate;
try
Result := Stmt.SetParams(aNumParams, aParamDescs, aDataBuffer);
finally
Stmt.Deactivate;
end;
{End !!.01}
except
on E : Exception do begin
Result := ConvertServerException(E, FEventLog);
end;
end;
{$ENDIF}
end;
{====================================================================}
end.