439 lines
16 KiB
ObjectPascal
439 lines
16 KiB
ObjectPascal
/// DB VCL read/only dataset from SynDB data access
|
|
// - this unit is a part of the freeware Synopse framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit SynDBVCL;
|
|
|
|
{
|
|
This file is part of Synopse framework.
|
|
|
|
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
|
|
Synopse Informatique - https://synopse.info
|
|
|
|
*** BEGIN LICENSE BLOCK *****
|
|
Version: MPL 1.1/GPL 2.0/LGPL 2.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 Synopse mORMot framework.
|
|
|
|
The Initial Developer of the Original Code is Arnaud Bouchez.
|
|
|
|
Portions created by the Initial Developer are Copyright (C) 2022
|
|
the Initial Developer. All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
- Alfred Glaenzer (alf)
|
|
- Murat Ak
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
in which case the provisions of the GPL or the LGPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of either the GPL or the LGPL, and not to allow others to
|
|
use your version of this file under the terms of the MPL, indicate your
|
|
decision by deleting the provisions above and replace them with the notice
|
|
and other provisions required by the GPL or the LGPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
}
|
|
|
|
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
|
|
|
|
interface
|
|
|
|
uses
|
|
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
|
|
Classes,
|
|
Contnrs,
|
|
SynCommons,
|
|
SynTable,
|
|
SynDB,
|
|
DB,
|
|
{$ifndef FPC}DBCommon,{$endif}
|
|
SynVirtualDataSet;
|
|
|
|
type
|
|
/// read-only virtual TDataSet able to access a binary buffer as returned
|
|
// by TSQLStatement.FetchAllToBinary method or directly a TSQLStatement
|
|
TSynBinaryDataSet = class(TSynVirtualDataSet)
|
|
protected
|
|
fData: RawByteString;
|
|
fDataAccess: TSQLDBProxyStatementRandomAccess;
|
|
fTemp64: Int64;
|
|
procedure InternalInitFieldDefs; override;
|
|
function GetRecordCount: Integer; override;
|
|
function GetRowFieldData(Field: TField; RowIndex: integer;
|
|
out ResultLen: Integer; OnlyCheckNull: boolean): Pointer; override;
|
|
public
|
|
/// initialize the virtual TDataSet from a FetchAllToBinary() buffer
|
|
// - by default, ColumnDataSize would be computed from the supplied data,
|
|
// unless you set IgnoreColumnDataSize=true to set the value to 0 (and
|
|
// force e.g. SynDBVCL TSynBinaryDataSet.InternalInitFieldDefs define the
|
|
// field as ftDefaultMemo) or you define some FieldDefs.Items[].Size values
|
|
// for ftUTF8 column sizes before calling this From() method
|
|
procedure From(const BinaryData: RawByteString;
|
|
DataRowPosition: PCardinalDynArray=nil; IgnoreColumnDataSize: boolean=false); overload; virtual;
|
|
/// initialize the virtual TDataSet from a SynDB TSQLDBStatement result set
|
|
// - the supplied ISQLDBRows instance can safely be freed by the caller,
|
|
// since a private binary copy will be owned by this instance (in Data)
|
|
// - by default, ColumnDataSize would be computed from the supplied data,
|
|
// unless you set IgnoreColumnDataSize=true to set the value to 0 (and
|
|
// force e.g. SynDBVCL TSynBinaryDataSet.InternalInitFieldDefs define the
|
|
// field as ftDefaultMemo) or you define some FieldDefs.Items[].Size values
|
|
// for ftUTF8 column sizes before calling this From() method
|
|
procedure From(Statement: TSQLDBStatement; MaxRowCount: cardinal=0;
|
|
IgnoreColumnDataSize: boolean=false); overload; virtual;
|
|
/// finalize the class instance
|
|
destructor Destroy; override;
|
|
/// read-only access to the internal binary buffer
|
|
property Data: RawByteString read fData;
|
|
/// read-only access to the internal SynDB data
|
|
property DataAccess: TSQLDBProxyStatementRandomAccess read fDataAccess;
|
|
end;
|
|
|
|
/// TDataSet able to execute any SQL as SynDB's TSQLStatement result set
|
|
// - this class is not meant to be used by itself, but via TSynDBDataSet,
|
|
// defined in SynDBMidasVCL.pas, as a data provider able to apply updates to
|
|
// the remote SynDB connection
|
|
// - typical usage may be for instance over a SynDBRemote connection:
|
|
// ! props := TSQLDBWinHTTPConnectionProperties.Create(....);
|
|
// ! ds := TSynDBSQLDataSet.Create(MainForm);
|
|
// ! ds.CommandText := 'select * from people';
|
|
// ! ds.Open;
|
|
// ! // ... use ds
|
|
// ! ds.Close;
|
|
// ! ds.CommandText := 'select * from customer where id=:(10):';
|
|
// ! ds.Open;
|
|
// ! // ... use ds
|
|
TSynDBSQLDataSet = class(TSynBinaryDataSet)
|
|
protected
|
|
fConnection: TSQLDBConnectionProperties;
|
|
fCommandText: string;
|
|
procedure InternalOpen; override;
|
|
procedure InternalClose; override;
|
|
// IProvider implementation
|
|
procedure PSSetCommandText(const ACommandText: string); override;
|
|
function PSGetTableName: string; override;
|
|
function PSUpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean; override;
|
|
function PSIsSQLBased: Boolean; override;
|
|
function PSIsSQLSupported: Boolean; override;
|
|
{$ifdef ISDELPHIXE3}
|
|
function PSExecuteStatement(const ASQL: string; AParams: TParams): Integer; overload; override;
|
|
function PSExecuteStatement(const ASQL: string; AParams: TParams; var ResultSet: TDataSet): Integer; overload; override;
|
|
{$else}
|
|
function PSExecuteStatement(const ASQL: string; AParams: TParams; ResultSet: Pointer=nil): Integer; overload; override;
|
|
{$endif}
|
|
public
|
|
/// initialize the internal TDataSet from a SynDB TSQLDBStatement result set
|
|
// - the supplied TSQLDBStatement can then be freed by the caller, since
|
|
// a private binary copy will be owned by this instance (in fDataSet.Data)
|
|
// - by default, ColumnDataSize would be computed from the supplied data,
|
|
// unless you set IgnoreColumnDataSize=true to set the value to 0 (and
|
|
// force e.g. SynDBVCL TSynBinaryDataSet.InternalInitFieldDefs define the
|
|
// field as ftDefaultMemo) or you define some FieldDefs.Items[].Size values
|
|
// for ftUTF8 column sizes before calling this From() method
|
|
procedure From(Statement: TSQLDBStatement; MaxRowCount: cardinal=0;
|
|
IgnoreColumnDataSize: boolean=false); override;
|
|
/// the associated connection properties
|
|
property Connection: TSQLDBConnectionProperties read fConnection write fConnection;
|
|
published
|
|
/// the SQL statement to be executed
|
|
// - since this statement will be executed via Connection.ExecuteInlined,
|
|
// you can specify optionally inlined parameters to this SQL text
|
|
property CommandText: string read fCommandText write fCommandText;
|
|
end;
|
|
|
|
|
|
/// fetch a SynDB's TQuery result into a VCL DataSet
|
|
// - if aMaxRowCount>0, will return up to the specified number of rows
|
|
// - current implementation will return a TSynSQLStatementDataSet instance,
|
|
// using an optimized internal binary buffer: the supplied TQuery can be released
|
|
// - if you need a writable TDataSet, you can use the slower ToClientDataSet()
|
|
// function as defined in SynDBMidasVCL.pas
|
|
function ToDataSet(aOwner: TComponent; aStatement: SynDB.TQuery;
|
|
aMaxRowCount: integer=0): TSynBinaryDataSet; overload;
|
|
|
|
/// fetch a SynDB's TSQLDBStatement result into a VCL DataSet
|
|
// - just a wrapper around TSynSQLStatementDataSet.Create + Open
|
|
// - if aMaxRowCount>0, will return up to the specified number of rows
|
|
// - current implementation will return a TSynSQLStatementDataSet instance, using
|
|
// an optimized internal binary buffer: the supplied statement can be released
|
|
// - if you need a writable TDataSet, you can use the slower ToClientDataSet()
|
|
// function as defined in SynDBMidasVCL.pas
|
|
function ToDataSet(aOwner: TComponent; aStatement: TSQLDBStatement;
|
|
aMaxRowCount: integer=0): TSynBinaryDataSet; overload;
|
|
|
|
/// fetch a SynDB ISQLDBRows result set into a VCL DataSet
|
|
// - this overloaded function can use directly a result of the
|
|
// TSQLDBConnectionProperties.Execute() method, as such:
|
|
// ! ds1.DataSet := ToDataSet(self,props.Execute('select * from table',[]));
|
|
function ToDataSet(aOwner: TComponent; aStatement: ISQLDBRows;
|
|
aMaxRowCount: integer=0): TSynBinaryDataSet; overload;
|
|
|
|
/// fetch a SynDB's TSQLDBStatement.FetchAllToBinary buffer into a VCL DataSet
|
|
// - just a wrapper around TSynBinaryDataSet.Create + Open
|
|
// - if you need a writable TDataSet, you can use the slower ToClientDataSet()
|
|
// function as defined in SynDBMidasVCL.pas
|
|
function BinaryToDataSet(aOwner: TComponent;
|
|
const aBinaryData: RawByteString): TSynBinaryDataSet;
|
|
|
|
|
|
implementation
|
|
|
|
|
|
function ToDataSet(aOwner: TComponent; aStatement: SynDB.TQuery;
|
|
aMaxRowCount: integer): TSynBinaryDataSet;
|
|
begin
|
|
if aStatement=nil then
|
|
result := nil else
|
|
result := ToDataSet(aOwner,
|
|
aStatement.PreparedSQLDBStatement.Instance,aMaxRowCount);
|
|
end;
|
|
|
|
function ToDataSet(aOwner: TComponent; aStatement: TSQLDBStatement;
|
|
aMaxRowCount: integer): TSynBinaryDataSet;
|
|
begin
|
|
result := TSynBinaryDataSet.Create(aOwner);
|
|
result.From(aStatement,aMaxRowCount);
|
|
result.Open;
|
|
end;
|
|
|
|
function ToDataSet(aOwner: TComponent; aStatement: ISQLDBRows;
|
|
aMaxRowCount: integer): TSynBinaryDataSet;
|
|
begin
|
|
if aStatement=nil then
|
|
result := nil else
|
|
result := ToDataSet(aOwner,aStatement.Instance,aMaxRowCount);
|
|
end;
|
|
|
|
function BinaryToDataSet(aOwner: TComponent; const aBinaryData: RawByteString): TSynBinaryDataSet;
|
|
begin
|
|
result := TSynBinaryDataSet.Create(aOwner);
|
|
result.From(aBinaryData);
|
|
result.Open;
|
|
end;
|
|
|
|
|
|
{ TSynBinaryDataSet }
|
|
|
|
procedure TSynBinaryDataSet.From(const BinaryData: RawByteString;
|
|
DataRowPosition: PCardinalDynArray; IgnoreColumnDataSize: boolean);
|
|
begin
|
|
fData := BinaryData;
|
|
fDataAccess := TSQLDBProxyStatementRandomAccess.Create(
|
|
pointer(fData),length(fData),DataRowPosition,IgnoreColumnDataSize);
|
|
end;
|
|
|
|
procedure TSynBinaryDataSet.From(Statement: TSQLDBStatement; MaxRowCount: cardinal;
|
|
IgnoreColumnDataSize: boolean);
|
|
var DataStream: TRawByteStringStream;
|
|
DataRowPosition: TCardinalDynArray;
|
|
begin
|
|
DataStream := TRawByteStringStream.Create;
|
|
try
|
|
Statement.FetchAllToBinary(DataStream,MaxRowCount,@DataRowPosition);
|
|
From(DataStream.DataString,@DataRowPosition,IgnoreColumnDataSize);
|
|
finally
|
|
DataStream.Free;
|
|
end;
|
|
end;
|
|
|
|
destructor TSynBinaryDataSet.Destroy;
|
|
begin
|
|
inherited;
|
|
FreeAndNil(fDataAccess);
|
|
end;
|
|
|
|
function TSynBinaryDataSet.GetRecordCount: Integer;
|
|
begin
|
|
if fDataAccess=nil then
|
|
result := 0 else
|
|
result := fDataAccess.DataRowCount;
|
|
end;
|
|
|
|
procedure TSynBinaryDataSet.InternalInitFieldDefs;
|
|
var F,custom: integer;
|
|
DBType: TFieldType;
|
|
ExistingName: TRawUTF8DynArray; // FieldDefs.Items[].Name
|
|
ExistingSize: TIntegerDynArray; // FieldDefs.Items[].Size
|
|
begin
|
|
if FieldDefs.Count>0 then begin // custom column sizes
|
|
SetLength(ExistingName,FieldDefs.Count);
|
|
SetLength(ExistingSize,FieldDefs.Count);
|
|
for F := 0 to FieldDefs.Count-1 do
|
|
with FieldDefs.Items[F] do begin
|
|
ExistingName[F] := StringToUtf8(Name);
|
|
ExistingSize[F] := Size;
|
|
end;
|
|
end;
|
|
FieldDefs.Clear;
|
|
if fDataAccess=nil then
|
|
exit;
|
|
for F := 0 to fDataAccess.ColumnCount-1 do
|
|
with fDataAccess.Columns[F] do begin
|
|
if ExistingName<>nil then begin
|
|
custom := FindRawUTF8(ExistingName,ColumnName);
|
|
if custom>=0 then // retrieve custom max column length from FieldDefs
|
|
ColumnDataSize := ExistingSize[custom];
|
|
end;
|
|
case ColumnType of
|
|
SynTable.ftInt64:
|
|
DBType := ftLargeint;
|
|
SynTable.ftDate:
|
|
DBType := ftDateTime;
|
|
SynTable.ftUTF8:
|
|
if ColumnDataSize=0 then
|
|
DBType := ftDefaultMemo else // no size
|
|
DBType := ftWideString; // means UnicodeString for Delphi 2009+
|
|
SynTable.ftBlob:
|
|
DBType := ftBlob;
|
|
SynTable.ftDouble, SynTable.ftCurrency:
|
|
DBType := ftFloat;
|
|
else
|
|
raise EDatabaseError.CreateFmt(
|
|
'GetFieldData ColumnType=%s',[TSQLDBFieldTypeToString(ColumnType)]);
|
|
end;
|
|
FieldDefs.Add(UTF8ToString(ColumnName),DBType,ColumnDataSize);
|
|
end;
|
|
end;
|
|
|
|
function TSynBinaryDataSet.GetRowFieldData(Field: TField;
|
|
RowIndex: integer; out ResultLen: Integer; OnlyCheckNull: boolean): Pointer;
|
|
var F: integer;
|
|
begin
|
|
result := nil;
|
|
F := Field.Index;
|
|
if (fDataAccess=nil) or not fDataAccess.GotoRow(RowIndex) then
|
|
exit;
|
|
result := fDataAccess.ColumnData(F);
|
|
if (result<>nil) and not OnlyCheckNull then
|
|
case fDataAccess.Columns[F].ColumnType of
|
|
SynTable.ftInt64: begin
|
|
fTemp64 := FromVarInt64(PByte(result));
|
|
result := @fTemp64;
|
|
end;
|
|
SynTable.ftCurrency: begin // ftFloat expects a DOUBLE value
|
|
unaligned(PDouble(@fTemp64)^) := PCurrency(result)^;
|
|
result := @fTemp64;
|
|
end;
|
|
SynTable.ftUTF8, SynTable.ftBlob:
|
|
resultLen := FromVarUInt32(PByte(result));
|
|
end; // other ColumnTypes are already in the expected format
|
|
end;
|
|
|
|
|
|
{ TSynDBSQLDataSet }
|
|
|
|
procedure TSynDBSQLDataSet.From(Statement: TSQLDBStatement; MaxRowCount: cardinal;
|
|
IgnoreColumnDataSize: boolean);
|
|
begin
|
|
inherited From(Statement,MaxRowCount,IgnoreColumnDataSize);
|
|
fConnection := Statement.Connection.Properties;
|
|
end;
|
|
|
|
procedure TSynDBSQLDataSet.InternalClose;
|
|
begin
|
|
inherited InternalClose;
|
|
FreeAndNil(fDataAccess);
|
|
fData := '';
|
|
end;
|
|
|
|
procedure TSynDBSQLDataSet.InternalOpen;
|
|
var Rows: ISQLDBRows;
|
|
begin
|
|
if fCommandText='' then begin
|
|
if fData<>'' then // called e.g. after From() method
|
|
inherited InternalOpen;
|
|
exit;
|
|
end;
|
|
Rows := fConnection.ExecuteInlined(StringToUTF8(fCommandText),true);
|
|
if Rows<>nil then begin
|
|
From(Rows.Instance);
|
|
inherited InternalOpen;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef ISDELPHIXE3}
|
|
|
|
function TSynDBSQLDataSet.PSExecuteStatement(const ASQL: string;
|
|
AParams: TParams): Integer;
|
|
var DS: TDataSet;
|
|
begin
|
|
DS := nil;
|
|
result := PSExecuteStatement(ASQL,AParams,DS);
|
|
DS.Free;
|
|
end;
|
|
|
|
function TSynDBSQLDataSet.PSExecuteStatement(const ASQL: string;
|
|
AParams: TParams; var ResultSet: TDataSet): Integer;
|
|
{$else}
|
|
function TSynDBSQLDataSet.PSExecuteStatement(const ASQL: string;
|
|
AParams: TParams; ResultSet: Pointer): Integer;
|
|
{$endif}
|
|
var Stmt: ISQLDBStatement;
|
|
blob: TBlobData;
|
|
p: integer;
|
|
begin // only execute writes in current implementation
|
|
if fConnection=nil then
|
|
raise ESQLQueryException.CreateUTF8('%.PSExecuteStatement with Connection=nil',[self]);
|
|
Stmt := fConnection.NewThreadSafeStatementPrepared(StringToUTF8(ASQL),false);
|
|
if Stmt<>nil then
|
|
try
|
|
if AParams<>nil then
|
|
for p := 0 to AParams.Count-1 do
|
|
if aParams[p].DataType = ftBlob then begin
|
|
blob := aParams[p].AsBlob;
|
|
Stmt.BindBlob(p+1,pointer(blob),length(blob));
|
|
end else
|
|
Stmt.BindVariant(p+1,AParams[p].Value,False);
|
|
Stmt.ExecutePrepared;
|
|
result := Stmt.UpdateCount;
|
|
if result=0 then
|
|
result := 1; // optimistic result, even if SynDB returned 0
|
|
except
|
|
result := 0;
|
|
end else
|
|
result := 0;
|
|
end;
|
|
|
|
function TSynDBSQLDataSet.PSGetTableName: string;
|
|
begin
|
|
// ToDo We miss GetTableNameFromSQL in FPC, Delphi function from DBCommon
|
|
result := {$ifdef FPC}''{$else}GetTableNameFromSQL(fCommandText){$endif};
|
|
end;
|
|
|
|
function TSynDBSQLDataSet.PSIsSQLBased: Boolean;
|
|
begin
|
|
result := true;
|
|
end;
|
|
|
|
function TSynDBSQLDataSet.PSIsSQLSupported: Boolean;
|
|
begin
|
|
result := true;
|
|
end;
|
|
|
|
procedure TSynDBSQLDataSet.PSSetCommandText(const ACommandText: string);
|
|
begin
|
|
inherited;
|
|
fCommandText := ACommandText;
|
|
end;
|
|
|
|
function TSynDBSQLDataSet.PSUpdateRecord(UpdateKind: TUpdateKind;
|
|
Delta: TDataSet): Boolean;
|
|
begin
|
|
result := false;
|
|
end;
|
|
|
|
end.
|