fpc/fcl/db/sqldb/sqldb.pp

1113 lines
30 KiB
ObjectPascal

{
Copyright (c) 2004 by Joost van der Sluis
SQL database & dataset
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 sqldb;
{$mode objfpc}
{$H+}
{$M+} // ### remove this!!!
interface
uses SysUtils, Classes, DB;
type TSchemaType = (stNoSchema, stTables, stSysTables, stProcedures, stColumns, stProcedureParams, stIndexes, stPackages);
TConnOption = (sqSupportParams);
TConnOptions= set of TConnOption;
type
TSQLConnection = class;
TSQLTransaction = class;
TSQLQuery = class;
TStatementType = (stNone, stSelect, stInsert, stUpdate, stDelete,
stDDL, stGetSegment, stPutSegment, stExecProcedure,
stStartTrans, stCommit, stRollback, stSelectForUpd);
TSQLHandle = Class(TObject)
end;
TSQLCursor = Class(TSQLHandle)
public
FPrepared : Boolean;
FInitFieldDef : Boolean;
FStatementType : TStatementType;
end;
const
StatementTokens : Array[TStatementType] of string = ('(none)', 'select',
'insert', 'update', 'delete',
'create', 'get', 'put', 'execute',
'start','commit','rollback', '?'
);
SQLDelimiterCharacters = [';',',',' ','(',')',#13,#10,#9];
{ TSQLConnection }
type
{ TSQLConnection }
TSQLConnection = class (TDatabase)
private
FPassword : string;
FTransaction : TSQLTransaction;
FUserName : string;
FHostName : string;
FCharSet : string;
FRole : String;
procedure SetTransaction(Value : TSQLTransaction);
procedure GetDBInfo(const SchemaType : TSchemaType; const SchemaObjectName, ReturnField : string; List: TStrings);
protected
FConnOptions : TConnOptions;
function StrToStatementType(s : string) : TStatementType; virtual;
procedure DoInternalConnect; override;
procedure DoInternalDisconnect; override;
function GetAsSQLText(Field : TField) : string; virtual;
function GetHandle : pointer; virtual; abstract;
Function AllocateCursorHandle : TSQLCursor; virtual; abstract;
Procedure DeAllocateCursorHandle(var cursor : TSQLCursor); virtual; abstract;
Function AllocateTransactionHandle : TSQLHandle; virtual; abstract;
procedure PrepareStatement(cursor: TSQLCursor;ATransaction : TSQLTransaction;buf : string; AParams : TParams); virtual; abstract;
procedure Execute(cursor: TSQLCursor;atransaction:tSQLtransaction; AParams : TParams); virtual; abstract;
function Fetch(cursor : TSQLCursor) : boolean; virtual; abstract;
procedure AddFieldDefs(cursor: TSQLCursor; FieldDefs : TfieldDefs); virtual; abstract;
procedure UnPrepareStatement(cursor : TSQLCursor); virtual; abstract;
procedure FreeFldBuffers(cursor : TSQLCursor); virtual; abstract;
function LoadField(cursor : TSQLCursor;FieldDef : TfieldDef;buffer : pointer) : boolean; virtual; abstract;
function GetTransactionHandle(trans : TSQLHandle): pointer; virtual; abstract;
function Commit(trans : TSQLHandle) : boolean; virtual; abstract;
function RollBack(trans : TSQLHandle) : boolean; virtual; abstract;
function StartdbTransaction(trans : TSQLHandle; aParams : string) : boolean; virtual; abstract;
procedure CommitRetaining(trans : TSQLHandle); virtual; abstract;
procedure RollBackRetaining(trans : TSQLHandle); virtual; abstract;
procedure UpdateIndexDefs(var IndexDefs : TIndexDefs;TableName : string); virtual;
function GetSchemaInfoSQL(SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string; virtual;
function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; virtual;abstract;
public
property Handle: Pointer read GetHandle;
destructor Destroy; override;
property ConnOptions: TConnOptions read FConnOptions;
procedure ExecuteDirect(SQL : String); overload; virtual;
procedure ExecuteDirect(SQL : String; Transaction : TSQLTransaction); overload; virtual;
procedure GetTableNames(List : TStrings; SystemTables : Boolean = false); virtual;
procedure GetProcedureNames(List : TStrings); virtual;
procedure GetFieldNames(const TableName : string; List : TStrings); virtual;
published
property Password : string read FPassword write FPassword;
property Transaction : TSQLTransaction read FTransaction write SetTransaction;
property UserName : string read FUserName write FUserName;
property CharSet : string read FCharSet write FCharSet;
property HostName : string Read FHostName Write FHostName;
property Connected;
Property Role : String read FRole write FRole;
property DatabaseName;
property KeepConnection;
property LoginPrompt;
property Params;
property OnLogin;
end;
{ TSQLTransaction }
TCommitRollbackAction = (caNone, caCommit, caCommitRetaining, caRollback,
caRollbackRetaining);
TSQLTransaction = class (TDBTransaction)
private
FTrans : TSQLHandle;
FAction : TCommitRollbackAction;
FParams : TStringList;
protected
function GetHandle : Pointer; virtual;
Procedure SetDatabase (Value : TDatabase); override;
public
procedure Commit; virtual;
procedure CommitRetaining; virtual;
procedure Rollback; virtual;
procedure RollbackRetaining; virtual;
procedure StartTransaction; override;
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
property Handle: Pointer read GetHandle;
procedure EndTransaction; override;
published
property Action : TCommitRollbackAction read FAction write FAction;
property Database;
property Params : TStringList read FParams write FParams;
end;
{ TSQLQuery }
TSQLQuery = class (Tbufdataset)
private
FCursor : TSQLCursor;
FUpdateable : boolean;
FTableName : string;
FSQL : TStringList;
FIsEOF : boolean;
FLoadingFieldDefs : boolean;
FIndexDefs : TIndexDefs;
FReadOnly : boolean;
FUpdateMode : TUpdateMode;
FParams : TParams;
FusePrimaryKeyAsKey : Boolean;
FSQLBuf : String;
FFromPart : String;
FWhereStartPos : integer;
FWhereStopPos : integer;
FParseSQL : boolean;
// FSchemaInfo : TSchemaInfo;
procedure FreeFldBuffers;
procedure InitUpdates(SQL : string);
function GetIndexDefs : TIndexDefs;
function GetStatementType : TStatementType;
procedure SetIndexDefs(AValue : TIndexDefs);
procedure SetReadOnly(AValue : Boolean);
procedure SetParseSQL(AValue : Boolean);
procedure SetUsePrimaryKeyAsKey(AValue : Boolean);
procedure SetUpdateMode(AValue : TUpdateMode);
procedure OnChangeSQL(Sender : TObject);
procedure Execute;
Procedure SQLParser(var SQL : string);
procedure ApplyFilter;
Function AddFilter(SQLstr : string) : string;
protected
// abstract & virtual methods of TBufDataset
function Fetch : boolean; override;
function LoadField(FieldDef : TFieldDef;buffer : pointer) : boolean; override;
// abstract & virtual methods of TDataset
procedure UpdateIndexDefs; override;
procedure SetDatabase(Value : TDatabase); override;
Procedure SetTransaction(Value : TDBTransaction); override;
procedure InternalAddRecord(Buffer: Pointer; AAppend: Boolean); override;
procedure InternalClose; override;
procedure InternalInitFieldDefs; override;
procedure InternalOpen; override;
function GetCanModify: Boolean; override;
function ApplyRecUpdate(UpdateKind : TUpdateKind) : boolean; override;
Function IsPrepared : Boolean; virtual;
procedure SetFiltered(Value: Boolean); override;
procedure SetFilterText(const Value: string); override;
public
procedure Prepare; virtual;
procedure UnPrepare; virtual;
procedure ExecSQL; virtual;
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
procedure SetSchemaInfo( SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string); virtual;
function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
property Prepared : boolean read IsPrepared;
published
// redeclared data set properties
property Active;
property Filter;
property Filtered;
// property FilterOptions;
property BeforeOpen;
property AfterOpen;
property BeforeClose;
property AfterClose;
property BeforeInsert;
property AfterInsert;
property BeforeEdit;
property AfterEdit;
property BeforePost;
property AfterPost;
property BeforeCancel;
property AfterCancel;
property BeforeDelete;
property AfterDelete;
property BeforeScroll;
property AfterScroll;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnFilterRecord;
property OnNewRecord;
property OnPostError;
property AutoCalcFields;
property Database;
property Transaction;
property ReadOnly : Boolean read FReadOnly write SetReadOnly;
property SQL : TStringlist read FSQL write FSQL;
property IndexDefs : TIndexDefs read GetIndexDefs;
property Params : TParams read FParams write FParams;
property UpdateMode : TUpdateMode read FUpdateMode write SetUpdateMode;
property UsePrimaryKeyAsKey : boolean read FUsePrimaryKeyAsKey write SetUsePrimaryKeyAsKey;
property StatementType : TStatementType read GetStatementType;
property ParseSQL : Boolean read FParseSQL write SetParseSQL;
// property SchemaInfo : TSchemaInfo read FSchemaInfo default stNoSchema;
end;
implementation
uses dbconst, strutils;
{ TSQLConnection }
function TSQLConnection.StrToStatementType(s : string) : TStatementType;
var T : TStatementType;
begin
S:=Lowercase(s);
For t:=stselect to strollback do
if (S=StatementTokens[t]) then
Exit(t);
end;
procedure TSQLConnection.SetTransaction(Value : TSQLTransaction);
begin
if FTransaction<>value then
begin
if Assigned(FTransaction) and FTransaction.Active then
DatabaseError(SErrAssTransaction);
if Assigned(Value) then
Value.Database := Self;
FTransaction := Value;
end;
end;
procedure TSQLConnection.UpdateIndexDefs(var IndexDefs : TIndexDefs;TableName : string);
begin
// Empty abstract
end;
procedure TSQLConnection.DoInternalConnect;
begin
// Empty abstract
end;
procedure TSQLConnection.DoInternalDisconnect;
begin
end;
destructor TSQLConnection.Destroy;
begin
inherited Destroy;
end;
Procedure TSQLConnection.ExecuteDirect(SQL: String);
begin
ExecuteDirect(SQL,FTransaction);
end;
Procedure TSQLConnection.ExecuteDirect(SQL: String; Transaction : TSQLTransaction);
var Cursor : TSQLCursor;
begin
if not assigned(Transaction) then
DatabaseError(SErrTransactionnSet);
if not Connected then Open;
if not Transaction.Active then Transaction.StartTransaction;
try
Cursor := AllocateCursorHandle;
SQL := TrimRight(SQL);
if SQL = '' then
DatabaseError(SErrNoStatement);
Cursor.FStatementType := stNone;
PrepareStatement(cursor,Transaction,SQL,Nil);
execute(cursor,Transaction, Nil);
UnPrepareStatement(Cursor);
finally;
DeAllocateCursorHandle(Cursor);
end;
end;
procedure TSQLConnection.GetDBInfo(const SchemaType : TSchemaType; const SchemaObjectName, ReturnField : string; List: TStrings);
var qry : TSQLQuery;
begin
if not assigned(Transaction) then
DatabaseError(SErrConnTransactionnSet);
qry := tsqlquery.Create(nil);
qry.transaction := Transaction;
qry.database := Self;
with qry do
begin
ParseSQL := False;
SetSchemaInfo(SchemaType,SchemaObjectName,'');
open;
List.Clear;
while not eof do
begin
List.Append(fieldbyname(ReturnField).asstring);
Next;
end;
end;
qry.free;
end;
procedure TSQLConnection.GetTableNames(List: TStrings; SystemTables: Boolean);
begin
if not systemtables then GetDBInfo(stTables,'','table_name',List)
else GetDBInfo(stSysTables,'','table_name',List);
end;
procedure TSQLConnection.GetProcedureNames(List: TStrings);
begin
GetDBInfo(stProcedures,'','proc_name',List);
end;
procedure TSQLConnection.GetFieldNames(const TableName: string; List: TStrings);
begin
GetDBInfo(stColumns,TableName,'column_name',List);
end;
function TSQLConnection.GetAsSQLText(Field : TField) : string;
begin
if not assigned(field) then Result := 'Null'
else case field.DataType of
ftString : Result := '''' + field.asstring + '''';
ftDate : Result := '''' + FormatDateTime('yyyy-mm-dd',Field.AsDateTime) + '''';
ftDateTime : Result := '''' + FormatDateTime('yyyy-mm-dd hh:mm:ss',Field.AsDateTime) + ''''
else
Result := field.asstring;
end; {case}
end;
function TSQLConnection.GetSchemaInfoSQL( SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string;
begin
DatabaseError(SMetadataUnavailable);
end;
{ TSQLTransaction }
procedure TSQLTransaction.EndTransaction;
begin
rollback;
end;
function TSQLTransaction.GetHandle: pointer;
begin
Result := (Database as tsqlconnection).GetTransactionHandle(FTrans);
end;
procedure TSQLTransaction.Commit;
begin
if active then
begin
closedatasets;
if (Database as tsqlconnection).commit(FTrans) then
begin
closeTrans;
FreeAndNil(FTrans);
end;
end;
end;
procedure TSQLTransaction.CommitRetaining;
begin
if active then
(Database as tsqlconnection).commitRetaining(FTrans);
end;
procedure TSQLTransaction.Rollback;
begin
if active then
begin
closedatasets;
if (Database as tsqlconnection).RollBack(FTrans) then
begin
CloseTrans;
FreeAndNil(FTrans);
end;
end;
end;
procedure TSQLTransaction.RollbackRetaining;
begin
if active then
(Database as tsqlconnection).RollBackRetaining(FTrans);
end;
procedure TSQLTransaction.StartTransaction;
var db : TSQLConnection;
begin
if Active then
DatabaseError(SErrTransAlreadyActive);
db := (Database as tsqlconnection);
if Db = nil then
DatabaseError(SErrDatabasenAssigned);
if not Db.Connected then
Db.Open;
if not assigned(FTrans) then FTrans := Db.AllocateTransactionHandle;
if Db.StartdbTransaction(FTrans,FParams.CommaText) then OpenTrans;
end;
constructor TSQLTransaction.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FParams := TStringList.Create;
end;
destructor TSQLTransaction.Destroy;
begin
Rollback;
FreeAndNil(FParams);
inherited Destroy;
end;
Procedure TSQLTransaction.SetDatabase(Value : TDatabase);
begin
If Value<>Database then
begin
CheckInactive;
If Assigned(Database) then
with Database as TSqlConnection do
if Transaction = self then Transaction := nil;
inherited SetDatabase(Value);
end;
end;
{ TSQLQuery }
procedure TSQLQuery.OnChangeSQL(Sender : TObject);
var s : string;
i : integer;
p : pchar;
ParamName : String;
begin
UnPrepare;
if (FSQL <> nil) then
begin
if assigned(FParams) then FParams.Clear;
s := FSQL.Text;
i := posex(':',s);
while i > 0 do
begin
inc(i);
p := @s[i];
repeat
inc(p);
until (p^ in SQLDelimiterCharacters);
if not assigned(FParams) then FParams := TParams.create(self);
ParamName := copy(s,i,p-@s[i]);
if FParams.FindParam(ParamName) = nil then
FParams.CreateParam(ftUnknown, ParamName, ptInput);
i := posex(':',s,i);
end;
end
end;
Procedure TSQLQuery.SetTransaction(Value : TDBTransaction);
begin
UnPrepare;
inherited;
end;
procedure TSQLQuery.SetDatabase(Value : TDatabase);
var db : tsqlconnection;
begin
if (Database <> Value) then
begin
UnPrepare;
if assigned(FCursor) then (Database as TSQLConnection).DeAllocateCursorHandle(FCursor);
db := value as tsqlconnection;
inherited setdatabase(value);
if assigned(value) and (Transaction = nil) and (Assigned(db.Transaction)) then
transaction := Db.Transaction;
end;
end;
Function TSQLQuery.IsPrepared : Boolean;
begin
Result := Assigned(FCursor) and FCursor.FPrepared;
end;
Function TSQLQuery.AddFilter(SQLstr : string) : string;
begin
if FWhereStartPos = 0 then
SQLstr := SQLstr + ' where (' + Filter + ')'
else if FWhereStopPos > 0 then
system.insert(' and ('+Filter+') ',SQLstr,FWhereStopPos+1)
else
system.insert(' where ('+Filter+') ',SQLstr,FWhereStartPos);
Result := SQLstr;
end;
procedure TSQLQuery.ApplyFilter;
var S : String;
begin
FreeFldBuffers;
(Database as tsqlconnection).UnPrepareStatement(FCursor);
FIsEOF := False;
inherited internalclose;
s := FSQLBuf;
if Filtered then s := AddFilter(s);
(Database as tsqlconnection).PrepareStatement(Fcursor,(transaction as tsqltransaction),S,FParams);
Execute;
inherited InternalOpen;
First;
end;
procedure TSQLQuery.SetFiltered(Value: Boolean);
begin
if Value and not FParseSQL then DatabaseErrorFmt(SNoParseSQL,['Filtering ']);
if (Filtered <> Value) then
begin
inherited setfiltered(Value);
if active then ApplyFilter;
end;
end;
procedure TSQLQuery.SetFilterText(const Value: string);
begin
if Value <> Filter then
begin
inherited SetFilterText(Value);
if active then ApplyFilter;
end;
end;
procedure TSQLQuery.Prepare;
var
db : tsqlconnection;
sqltr : tsqltransaction;
begin
if not IsPrepared then
begin
db := (Database as tsqlconnection);
sqltr := (transaction as tsqltransaction);
if not assigned(Db) then
DatabaseError(SErrDatabasenAssigned);
if not assigned(sqltr) then
DatabaseError(SErrTransactionnSet);
if not Db.Connected then db.Open;
if not sqltr.Active then sqltr.StartTransaction;
// if assigned(fcursor) then FreeAndNil(fcursor);
if not assigned(fcursor) then
FCursor := Db.AllocateCursorHandle;
FSQLBuf := TrimRight(FSQL.Text);
if FSQLBuf = '' then
DatabaseError(SErrNoStatement);
SQLParser(FSQLBuf);
if filtered then
Db.PrepareStatement(Fcursor,sqltr,AddFilter(FSQLBuf),FParams)
else
Db.PrepareStatement(Fcursor,sqltr,FSQLBuf,FParams);
if (FCursor.FStatementType = stSelect) then
begin
FCursor.FInitFieldDef := True;
if not ReadOnly then InitUpdates(FSQLBuf);
end;
end;
end;
procedure TSQLQuery.UnPrepare;
begin
CheckInactive;
if IsPrepared then with Database as TSQLConnection do
UnPrepareStatement(FCursor);
end;
procedure TSQLQuery.FreeFldBuffers;
begin
if assigned(FCursor) then (Database as tsqlconnection).FreeFldBuffers(FCursor);
end;
function TSQLQuery.Fetch : boolean;
begin
if not (Fcursor.FStatementType in [stSelect]) then
Exit;
if not FIsEof then FIsEOF := not (Database as tsqlconnection).Fetch(Fcursor);
Result := not FIsEOF;
end;
procedure TSQLQuery.Execute;
begin
(Database as tsqlconnection).execute(Fcursor,Transaction as tsqltransaction, FParams);
end;
function TSQLQuery.LoadField(FieldDef : TFieldDef;buffer : pointer) : boolean;
begin
result := (Database as tSQLConnection).LoadField(FCursor,FieldDef,buffer)
end;
procedure TSQLQuery.InternalAddRecord(Buffer: Pointer; AAppend: Boolean);
begin
// not implemented - sql dataset
end;
procedure TSQLQuery.InternalClose;
begin
if StatementType = stSelect then FreeFldBuffers;
if not IsPrepared then (database as TSQLconnection).UnPrepareStatement(FCursor);
if DefaultFields then
DestroyFields;
FIsEOF := False;
// FRecordSize := 0;
inherited internalclose;
end;
procedure TSQLQuery.InternalInitFieldDefs;
begin
if FLoadingFieldDefs then
Exit;
FLoadingFieldDefs := True;
try
FieldDefs.Clear;
(Database as tsqlconnection).AddFieldDefs(fcursor,FieldDefs);
finally
FLoadingFieldDefs := False;
end;
end;
procedure TSQLQuery.SQLParser(var SQL : string);
type TParsePart = (ppStart,ppSelect,ppWhere,ppFrom,ppOrder,ppComment,ppBogus);
Var
PSQL,CurrentP,
PhraseP, PStatementPart : pchar;
S : string;
ParsePart : TParsePart;
StrLength : Integer;
begin
PSQL:=Pchar(SQL);
ParsePart := ppStart;
CurrentP := PSQL-1;
PhraseP := PSQL;
FWhereStartPos := 0;
FWhereStopPos := 0;
repeat
begin
inc(CurrentP);
if CurrentP^ in [' ',#13,#10,#9,#0,'(',')',';'] then
begin
if (CurrentP-PhraseP > 0) or (CurrentP^ in [';',#0]) then
begin
strLength := CurrentP-PhraseP;
Setlength(S,strLength);
if strLength > 0 then Move(PhraseP^,S[1],(strLength));
s := uppercase(s);
case ParsePart of
ppStart : begin
FCursor.FStatementType := (Database as tsqlconnection).StrToStatementType(s);
if FCursor.FStatementType = stSelect then ParsePart := ppSelect
else break;
if not FParseSQL then break;
PStatementPart := CurrentP;
end;
ppSelect : begin
if s = 'FROM' then
begin
ParsePart := ppFrom;
PhraseP := CurrentP;
PStatementPart := CurrentP;
end;
end;
ppFrom : begin
if (s = 'WHERE') or (s = 'ORDER') or (CurrentP^=#0) or (CurrentP^=';') then
begin
if (s = 'WHERE') then
begin
ParsePart := ppWhere;
StrLength := PhraseP-PStatementPart;
end
else if (s = 'ORDER') then
begin
ParsePart := ppOrder;
StrLength := PhraseP-PStatementPart
end
else
begin
ParsePart := ppBogus;
StrLength := CurrentP-PStatementPart;
end;
Setlength(FFromPart,StrLength);
Move(PStatementPart^,FFromPart[1],(StrLength));
FFrompart := trim(FFrompart);
FWhereStartPos := PStatementPart-PSQL+StrLength+1;
PStatementPart := CurrentP;
end;
end;
ppWhere : begin
if (s = 'ORDER') or (CurrentP^=#0) or (CurrentP^=';') then
begin
ParsePart := ppBogus;
FWhereStartPos := PStatementPart-PSQL;
if s = 'ORDER' then
FWhereStopPos := PhraseP-PSQL+1
else
FWhereStopPos := CurrentP-PSQL+1;
end;
end;
end; {case}
end;
PhraseP := CurrentP+1;
end
end;
until CurrentP^=#0;
if (FWhereStartPos > 0) and (FWhereStopPos > 0) then
begin
system.insert('(',SQL,FWhereStartPos+1);
inc(FWhereStopPos);
system.insert(')',SQL,FWhereStopPos);
end
end;
procedure TSQLQuery.InitUpdates(SQL : string);
begin
if pos(',',FFromPart) > 0 then
FUpdateable := False // select-statements from more then one table are not updateable
else
begin
FUpdateable := True;
FTableName := FFromPart;
end;
end;
procedure TSQLQuery.InternalOpen;
var tel : integer;
f : TField;
s : string;
begin
try
Prepare;
if FCursor.FStatementType in [stSelect] then
begin
Execute;
if FCursor.FInitFieldDef then InternalInitFieldDefs;
if DefaultFields then
begin
CreateFields;
if FUpdateable and FusePrimaryKeyAsKey then
begin
UpdateIndexDefs;
for tel := 0 to indexdefs.count-1 do {with indexdefs[tel] do}
begin
if ixPrimary in indexdefs[tel].options then
begin
// Todo: If there is more then one field in the key, that must be parsed
s := indexdefs[tel].fields;
F := Findfield(s);
if F <> nil then
F.ProviderFlags := F.ProviderFlags + [pfInKey];
end;
end;
end;
end;
end
else
DatabaseError(SErrNoSelectStatement,Self);
except
on E:Exception do
raise;
end;
inherited InternalOpen;
end;
// public part
procedure TSQLQuery.ExecSQL;
begin
try
Prepare;
Execute;
finally
if not IsPrepared then (database as TSQLConnection).UnPrepareStatement(Fcursor);
end;
end;
constructor TSQLQuery.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FSQL := TStringList.Create;
FSQL.OnChange := @OnChangeSQL;
FIndexDefs := TIndexDefs.Create(Self);
FReadOnly := false;
FParseSQL := True;
// Delphi has upWhereAll as default, but since strings and oldvalue's don't work yet
// (variants) set it to upWhereKeyOnly
FUpdateMode := upWhereKeyOnly;
FUsePrimaryKeyAsKey := True;
end;
destructor TSQLQuery.Destroy;
begin
if Active then Close;
UnPrepare;
if assigned(FCursor) then (Database as TSQLConnection).DeAllocateCursorHandle(FCursor);
FreeAndNil(FParams);
FreeAndNil(FSQL);
FreeAndNil(FIndexDefs);
inherited Destroy;
end;
procedure TSQLQuery.SetReadOnly(AValue : Boolean);
begin
CheckInactive;
if not AValue then
begin
if FParseSQL then FReadOnly := False
else DatabaseErrorFmt(SNoParseSQL,['Updating ']);
end
else FReadOnly := True;
end;
procedure TSQLQuery.SetParseSQL(AValue : Boolean);
begin
CheckInactive;
if not AValue then
begin
FReadOnly := True;
Filtered := False;
FParseSQL := False;
end
else
FParseSQL := True;
end;
procedure TSQLQuery.SetUsePrimaryKeyAsKey(AValue : Boolean);
begin
if not Active then FusePrimaryKeyAsKey := AValue
else
begin
// Just temporary, this should be possible in the future
DatabaseError(SActiveDataset);
end;
end;
Procedure TSQLQuery.UpdateIndexDefs;
begin
if assigned(DataBase) then
(DataBase as TSQLConnection).UpdateIndexDefs(FIndexDefs,FTableName);
end;
function TSQLQuery.ApplyRecUpdate(UpdateKind : TUpdateKind) : boolean;
var
s : string;
procedure UpdateWherePart(var sql_where : string;x : integer);
begin
if (pfInKey in Fields[x].ProviderFlags) or
((FUpdateMode = upWhereAll) and (pfInWhere in Fields[x].ProviderFlags)) or
((FUpdateMode = UpWhereChanged) and (pfInWhere in Fields[x].ProviderFlags) and (fields[x].value <> fields[x].oldvalue)) then
begin
// This should be converted to something like GetAsSQLText, but better wait until variants (oldvalue) are working for strings
s := fields[x].oldvalue; // This directly int the line below raises a variant-error
sql_where := sql_where + '(' + fields[x].FieldName + '=' + s + ') and ';
end;
end;
function ModifyRecQuery : string;
var x : integer;
sql_set : string;
sql_where : string;
begin
sql_set := '';
sql_where := '';
for x := 0 to Fields.Count -1 do
begin
UpdateWherePart(sql_where,x);
if (pfInUpdate in Fields[x].ProviderFlags) then
if fields[x].IsNull then // check for null
sql_set := sql_set + fields[x].FieldName + '=' + (Database as TSQLConnection).GetAsSQLText(nil) + ','
else
sql_set := sql_set + fields[x].FieldName + '=' + (Database as TSQLConnection).GetAsSQLText(fields[x]) + ',';
end;
setlength(sql_set,length(sql_set)-1);
setlength(sql_where,length(sql_where)-5);
result := 'update ' + FTableName + ' set ' + sql_set + ' where ' + sql_where;
end;
function InsertRecQuery : string;
var x : integer;
sql_fields : string;
sql_values : string;
begin
sql_fields := '';
sql_values := '';
for x := 0 to Fields.Count -1 do
begin
if not fields[x].IsNull then
begin
sql_fields := sql_fields + fields[x].DisplayName + ',';
sql_values := sql_values + (Database as TSQLConnection).GetAsSQLText(fields[x]) + ',';
end;
end;
setlength(sql_fields,length(sql_fields)-1);
setlength(sql_values,length(sql_values)-1);
result := 'insert into ' + FTableName + ' (' + sql_fields + ') values (' + sql_values + ')';
end;
function DeleteRecQuery : string;
var x : integer;
sql_where : string;
begin
sql_where := '';
for x := 0 to Fields.Count -1 do
UpdateWherePart(sql_where,x);
setlength(sql_where,length(sql_where)-5);
result := 'delete from ' + FTableName + ' where ' + sql_where;
end;
begin
Result := True;
case UpdateKind of
ukModify : s := ModifyRecQuery;
ukInsert : s := InsertRecQuery;
ukDelete : s := DeleteRecQuery;
end; {case}
try
(Database as TSQLConnection).ExecuteDirect(s,Transaction as TSQLTransaction);
except
on EDatabaseError do Result := False
else
raise;
end;
end;
Function TSQLQuery.GetCanModify: Boolean;
begin
if FCursor.FStatementType = stSelect then
Result:= Active and FUpdateable and (not FReadOnly)
else
Result := False;
end;
function TSQLQuery.GetIndexDefs : TIndexDefs;
begin
Result := FIndexDefs;
end;
procedure TSQLQuery.SetIndexDefs(AValue : TIndexDefs);
begin
FIndexDefs := AValue;
end;
procedure TSQLQuery.SetUpdateMode(AValue : TUpdateMode);
begin
FUpdateMode := AValue;
end;
procedure TSQLQuery.SetSchemaInfo( SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string);
begin
ReadOnly := True;
SQL.Clear;
SQL.Add((DataBase as tsqlconnection).GetSchemaInfoSQL(SchemaType, SchemaObjectName, SchemaPattern));
end;
function TSQLQuery.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
begin
result := (DataBase as tsqlconnection).CreateBlobStream(Field, Mode);
end;
function TSQLQuery.GetStatementType : TStatementType;
begin
if assigned(FCursor) then Result := FCursor.FStatementType
else Result := stNone;
end;
end.