682 lines
25 KiB
ObjectPascal
682 lines
25 KiB
ObjectPascal
/// FireDAC/AnyDAC-based classes for SynDB units
|
|
// - this unit is a part of the freeware Synopse framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit SynDBFireDAC;
|
|
|
|
{
|
|
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):
|
|
- delphinium (louisyeow)
|
|
- Oleg Tretyakov
|
|
|
|
|
|
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
|
|
Windows, SysUtils,
|
|
{$IFNDEF DELPHI5OROLDER}
|
|
Variants,
|
|
{$ENDIF}
|
|
Classes, Contnrs,
|
|
SynCommons,
|
|
SynTable,
|
|
SynLog,
|
|
SynDB,
|
|
SynDBDataset,
|
|
{$ifdef ISDELPHIXE5}
|
|
FireDAC.Comp.Client, FireDAC.Stan.Param;
|
|
{$else}
|
|
uADCompClient, uADStanParam;
|
|
{$endif}
|
|
|
|
|
|
|
|
{ -------------- FireDAC/AnyDAC database access }
|
|
|
|
type
|
|
/// Exception type associated to FireDAC/AnyDAC database access
|
|
ESQLDBFireDAC = class(ESQLDBDataset);
|
|
|
|
|
|
/// connection properties definition using FireDAC/AnyDAC database access
|
|
TSQLDBFireDACConnectionProperties = class(TSQLDBDatasetConnectionProperties)
|
|
protected
|
|
fFireDACOptions: TStringList;
|
|
/// initialize fForeignKeys content with all foreign keys of this DB
|
|
// - do nothing by now (FireDAC metadata may be used in the future)
|
|
procedure GetForeignKeys; override;
|
|
public
|
|
/// initialize the properties to connect via FireDAC/AnyDAC database access
|
|
// - aServerName shall contain the FireDAC provider DriverID, e.g. 'Ora', and
|
|
// some optional parameters (e.g. remote server name if needed), after a '?'
|
|
// and separated by ';' - for instance:
|
|
// ! Create('Ora','TNSNAME','User','Password');
|
|
// ! Create('Ora?CharacterSet=cl8mswin1251','TNSNAME','User','Password');
|
|
// ! Create('MSSQL?Server=127.0.0.1\SQLEXPRESS','Northwind','User','Password');
|
|
// ! Create('MSSQL?Server=.\SQLEXPRESS;OSAuthent=Yes','','','');
|
|
// ! Create('MSAcc','c:\data\access.mdb','','');
|
|
// ! Create('MySQL?Server=127.0.0.1;Port=3306','MyDB','User','Password');
|
|
// ! Create('SQLite','c:\data\myapp.db3','','');
|
|
// ! Create('SQLite',SQLITE_MEMORY_DATABASE_NAME,'','');
|
|
// ! Create('IB','127.0.0.1:C:\ib\ADDEMO_IB2007.IB','User','Password');
|
|
// ! Create('IB?Server=my_host/3055','C:\ib\ADDEMO_IB2007.IB','User','Password');
|
|
// ! Create('IB?CreateDatabase=Yes','127.0.0.1:C:\ib\ADDEMO_IB2007.IB','User','Password');
|
|
// ! Create('DB2?Server=localhost;Port=50000','SAMPLE','db2admin','db2Password');
|
|
// ! Create('PG?Server=localhost;Port=5432','postgres','postgres','postgresPassword');
|
|
// ! Create('MySQL?Server=localhost;Port=3306','test','root','');
|
|
// - aDatabaseName shall contain the database server name
|
|
// - note that you need to link the FireDAC driver by including the
|
|
// expected uADPhys*.pas / FireDAC.Phy.*.pas units into a uses clause
|
|
// of your application, e.g. uADPhysOracle, uADPhysMSSQL, uADPhysMSAcc,
|
|
// uADPhysMySQL, uADPhysSQLite, uADPhysIB or uADPhysDB2 (depending on the
|
|
// expected provider) - or FireDAC.Phys.Oracle, FireDAC.Phys.MSAcc,
|
|
// FireDAC.Phys.MSSQL, FireDAC.Phys.SQLite, FireDAC.Phys.IB, FireDAC.Phys.PG
|
|
// or FireDAC.Phys.DB2 since Delphi XE5 namespace modifications
|
|
constructor Create(const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8); override;
|
|
/// release internal structures
|
|
destructor Destroy; override;
|
|
/// create a new connection
|
|
// - caller is responsible of freeing this instance
|
|
// - this overridden method will create an TSQLDBFireDACConnection instance
|
|
function NewConnection: TSQLDBConnection; override;
|
|
|
|
/// retrieve the column/field layout of a specified table
|
|
// - this overridden method will use FireDAC metadata to retrieve the information
|
|
procedure GetFields(const aTableName: RawUTF8; out Fields: TSQLDBColumnDefineDynArray); override;
|
|
/// get all table names
|
|
// - this overridden method will use FireDAC metadata to retrieve the information
|
|
procedure GetTableNames(out Tables: TRawUTF8DynArray); override;
|
|
/// retrieve the advanced indexed information of a specified Table
|
|
// - this overridden method will use FireDAC metadata to retrieve the information
|
|
procedure GetIndexes(const aTableName: RawUTF8; out Indexes: TSQLDBIndexDefineDynArray); override;
|
|
|
|
/// allow to set the options specific to a FireDAC driver
|
|
// - by default, ServerName, DatabaseName, UserID and Password are set by
|
|
// the Create() constructor according to the underlying FireDAC driver
|
|
// - you can add some additional options here
|
|
property Parameters: TStringList read fFireDACOptions;
|
|
end;
|
|
|
|
|
|
/// implements a direct connection via FireDAC/AnyDAC database access
|
|
TSQLDBFireDACConnection = class(TSQLDBConnectionThreadSafe)
|
|
protected
|
|
fDatabase: {$ifdef ISDELPHIXE5}TFDConnection{$else}TADConnection{$endif};
|
|
public
|
|
/// prepare a connection for a specified FireDAC/AnyDAC database access
|
|
constructor Create(aProperties: TSQLDBConnectionProperties); override;
|
|
/// release memory and connection
|
|
destructor Destroy; override;
|
|
/// connect to the specified database server using FireDAC
|
|
// - should raise an ESQLDBFireDAC on error
|
|
procedure Connect; override;
|
|
/// stop connection to the specified database server using FireDAC
|
|
// - should raise an ESQLDBFireDAC on error
|
|
procedure Disconnect; override;
|
|
/// return TRUE if Connect has been already successfully called
|
|
function IsConnected: boolean; override;
|
|
/// create a new statement instance
|
|
function NewStatement: TSQLDBStatement; override;
|
|
/// begin a Transaction for this connection
|
|
procedure StartTransaction; override;
|
|
/// commit changes of a Transaction for this connection
|
|
// - StartTransaction method must have been called before
|
|
procedure Commit; override;
|
|
/// discard changes of a Transaction for this connection
|
|
// - StartTransaction method must have been called before
|
|
procedure Rollback; override;
|
|
/// access to the associated FireDAC connection instance
|
|
property Database: {$ifdef ISDELPHIXE5}TFDConnection{$else}TADConnection{$endif} read fDatabase;
|
|
end;
|
|
|
|
/// implements a statement via a FireDAC connection
|
|
// - this specific version will handle the FireDAC specific parameter classes
|
|
// - it will also handle Array DML commands, if possible
|
|
TSQLDBFireDACStatement = class(TSQLDBDatasetStatementAbstract)
|
|
protected
|
|
fQueryParams: {$ifdef ISDELPHIXE5}TFDParams{$else}TADParams{$endif};
|
|
fPreparedUseArrayDML: boolean;
|
|
/// initialize and set fQuery: TUniQuery internal field as expected
|
|
procedure DatasetCreate; override;
|
|
/// set fQueryParams internal field as expected
|
|
function DatasetPrepare(const aSQL: string): boolean; override;
|
|
/// execute underlying TUniQuery.ExecSQL
|
|
procedure DatasetExecSQL; override;
|
|
/// bind SQLDBParam to TQuery-like param using fQueryParams: DB.TParams
|
|
procedure DataSetBindSQLParam(const aArrayIndex, aParamIndex: integer;
|
|
const aParam: TSQLDBParam); override;
|
|
/// set the returned parameter after a stored proc execution
|
|
procedure DataSetOutSQLParam(const aParamIndex: integer;
|
|
var aParam: TSQLDBParam); override;
|
|
public
|
|
/// Prepare an UTF-8 encoded SQL statement
|
|
// - parameters marked as ? will be bound later, before ExecutePrepared call
|
|
// - if ExpectResults is TRUE, then Step() and Column*() methods are available
|
|
// to retrieve the data rows
|
|
// - raise an ESQLDBFireDAC on any error
|
|
procedure Prepare(const aSQL: RawUTF8; ExpectResults: boolean = false); overload; override;
|
|
end;
|
|
|
|
|
|
const
|
|
/// FireDAC DriverID values corresponding to SynDB recognized SQL engines
|
|
{$ifdef ISDELPHIXE5}
|
|
FIREDAC_PROVIDER: array[dOracle..high(TSQLDBDefinition)] of RawUTF8 = (
|
|
'Ora','MSSQL','MSAcc','MySQL','SQLite','FB','','PG','DB2','Infx');
|
|
|
|
{$else}
|
|
FIREDAC_PROVIDER: array[dOracle..high(TSQLDBDefinition)] of RawUTF8 = (
|
|
'Ora','MSSQL','MSAcc','MySQL','SQLite','IB','','PG','DB2','Infx');
|
|
{$endif}
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$ifdef ISDELPHIXE5}
|
|
FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.DApt, FireDAC.Stan.Async;
|
|
|
|
type
|
|
TADConnection = TFDConnection;
|
|
TADQuery = TFDQuery;
|
|
TADMetaInfoQuery = TFDMetaInfoQuery;
|
|
TADParam = TFDParam;
|
|
TADParams = TFDParams;
|
|
TADPhysMetaInfoKind = TFDPhysMetaInfoKind;
|
|
|
|
{$else}
|
|
uADPhysIntf, uADStanDef, uADDAptManager, uADStanAsync;
|
|
{$endif}
|
|
|
|
|
|
{ TSQLDBFireDACConnectionProperties }
|
|
|
|
constructor TSQLDBFireDACConnectionProperties.Create(const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8);
|
|
var p: TSQLDBDefinition;
|
|
server,options,namevalue: RawUTF8;
|
|
opt: PUTF8Char;
|
|
begin
|
|
Split(aServerName,'?',server,options);
|
|
if server<>'' then
|
|
for p := Low(FIREDAC_PROVIDER) to high(FIREDAC_PROVIDER) do
|
|
if SameTextU(FIREDAC_PROVIDER[p],server) then begin
|
|
fDBMS := p;
|
|
break;
|
|
end;
|
|
inherited Create(server,aDatabaseName,aUserID,aPassWord);
|
|
fOnBatchInsert := nil; // MultipleValuesInsert is slower than FireDAC ArrayDML
|
|
fFireDACOptions := TStringList.Create;
|
|
if ((fDBMS<low(FIREDAC_PROVIDER)) or (fDBMS>high(FIREDAC_PROVIDER))) and
|
|
(fDBMS<>dNexusDB) then
|
|
if SameTextU(server,'ASA') then
|
|
fDBMS := dMSSQL else begin
|
|
for p := Low(FIREDAC_PROVIDER) to high(FIREDAC_PROVIDER) do
|
|
namevalue := ' '+namevalue+FIREDAC_PROVIDER[p];
|
|
raise ESQLDBFireDAC.CreateUTF8('%.Create: unknown provider - available:%',
|
|
[self,namevalue]);
|
|
end;
|
|
if server='' then
|
|
server := FIREDAC_PROVIDER[fDBMS];
|
|
fFireDACOptions.Text := UTF8ToString(FormatUTF8(
|
|
'DriverID=%'#13#10'User_Name=%'#13#10'Password=%'#13#10'Database=%',
|
|
[server,fUserId,fPassWord,fDatabaseName]));
|
|
opt := pointer(options);
|
|
while opt<>nil do begin
|
|
GetNextItem(opt,';',namevalue);
|
|
if namevalue<>'' then
|
|
fFireDACOptions.Add(UTF8ToString(namevalue));
|
|
end;
|
|
case fDBMS of
|
|
dSQLite: begin
|
|
if fFireDACOptions.Values['CharacterSet']='' then // force UTF-8 for SynDB
|
|
fFireDACOptions.Values['CharacterSet'] := 'UTF8';
|
|
{$ifdef UNICODE} // CreateUTF16 is the default value for Delphi 2009+
|
|
if fFireDACOptions.Values['OpenMode']='' then // force UTF-8 for SynDB
|
|
fFireDACOptions.Values['OpenMode'] := 'CreateUTF8';
|
|
{$else}
|
|
ForceUseWideString := true; // as expected by FireDAC when UTF-8 is enabled
|
|
{$endif}
|
|
fSQLCreateField[ftInt64] := ' BIGINT'; // SQLite3 INTEGER = 32bit for FireDAC
|
|
end;
|
|
dFirebird, dMySQL, dPostgreSQL, dDB2: begin
|
|
if fFireDACOptions.Values['CharacterSet']='' then // force UTF-8 for SynDB
|
|
fFireDACOptions.Values['CharacterSet'] := 'UTF8';
|
|
{$ifndef UNICODE}
|
|
ForceUseWideString := true; // as expected by FireDAC when UTF-8 is enabled
|
|
{$endif}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TSQLDBFireDACConnectionProperties.Destroy;
|
|
begin
|
|
fFireDACOptions.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnectionProperties.GetTableNames(
|
|
out Tables: TRawUTF8DynArray);
|
|
var List: TStringList;
|
|
begin
|
|
List := TStringList.Create;
|
|
try
|
|
(MainConnection as TSQLDBFireDACConnection).fDatabase.GetTableNames(
|
|
'','','',List,[osMy],[tkTable]);
|
|
StringListToRawUTF8DynArray(List,Tables);
|
|
exit;
|
|
finally
|
|
List.Free;
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnectionProperties.GetFields(
|
|
const aTableName: RawUTF8; out Fields: TSQLDBColumnDefineDynArray);
|
|
var meta: TADMetaInfoQuery;
|
|
n: integer;
|
|
F: TSQLDBColumnDefine;
|
|
FA: TDynArray;
|
|
begin
|
|
meta := TADMetaInfoQuery.Create(nil);
|
|
try
|
|
meta.Connection := (MainConnection as TSQLDBFireDACConnection).fDatabase;
|
|
FA.Init(TypeInfo(TSQLDBColumnDefineDynArray),Fields,@n);
|
|
FA.Compare := SortDynArrayAnsiStringI; // FA.Find() case insensitive
|
|
FillChar(F,sizeof(F),0);
|
|
meta.MetaInfoKind := mkTableFields;
|
|
meta.ObjectName := UTF8ToString(UpperCase(aTableName));
|
|
meta.Open;
|
|
while not meta.Eof do begin
|
|
F.ColumnName := StringToUTF8(meta.FieldByName('COLUMN_NAME').AsString);
|
|
F.ColumnTypeNative := StringToUTF8(meta.FieldByName('COLUMN_TYPENAME').AsString);
|
|
F.ColumnLength := meta.FieldByName('COLUMN_LENGTH').AsInteger;
|
|
F.ColumnScale := meta.FieldByName('COLUMN_SCALE').AsInteger;
|
|
F.ColumnPrecision := meta.FieldByName('COLUMN_PRECISION').AsInteger;
|
|
{ TODO : retrieve ColumnType from high-level FireDAC type information }
|
|
F.ColumnType := ColumnTypeNativeToDB(F.ColumnTypeNative,F.ColumnScale);
|
|
FA.Add(F);
|
|
meta.Next;
|
|
end;
|
|
Setlength(Fields,n);
|
|
GetIndexesAndSetFieldsColumnIndexed(aTableName,Fields);
|
|
finally
|
|
meta.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnectionProperties.GetIndexes(
|
|
const aTableName: RawUTF8; out Indexes: TSQLDBIndexDefineDynArray);
|
|
var kind: boolean;
|
|
meta, indexs: TADMetaInfoQuery;
|
|
TableName: string;
|
|
ColName: RawUTF8;
|
|
F: TSQLDBIndexDefine;
|
|
FA: TDynArray;
|
|
n: integer;
|
|
const
|
|
MASTER: array[boolean] of TADPhysMetaInfoKind = (mkPrimaryKey,mkIndexes);
|
|
CHILD: array[boolean] of TADPhysMetaInfoKind = (mkPrimaryKeyFields,mkIndexFields);
|
|
begin
|
|
TableName := UTF8ToString(UpperCase(aTableName));
|
|
FA.Init(TypeInfo(TSQLDBIndexDefineDynArray),Indexes,@n);
|
|
fillchar(F,sizeof(F),0);
|
|
meta := TADMetaInfoQuery.Create(nil);
|
|
indexs := TADMetaInfoQuery.Create(nil);
|
|
try
|
|
meta.Connection := (MainConnection as TSQLDBFireDACConnection).fDatabase;
|
|
indexs.Connection := meta.Connection;
|
|
for kind := true to true do begin // primary keys may not be indexed
|
|
meta.MetaInfoKind := MASTER[kind];
|
|
meta.ObjectName := TableName;
|
|
meta.Open;
|
|
while not meta.Eof do begin
|
|
indexs.MetaInfoKind := CHILD[kind];
|
|
indexs.BaseObjectName := TableName;
|
|
indexs.ObjectName := meta.FieldByName('INDEX_NAME').AsString;
|
|
indexs.Open;
|
|
F.IndexName := StringToUTF8(indexs.ObjectName);
|
|
F.IsPrimaryKey := not kind;
|
|
F.KeyColumns := '';
|
|
while not indexs.Eof do begin
|
|
ColName := StringToUTF8(indexs.FieldByName('COLUMN_NAME').AsString);
|
|
if F.KeyColumns='' then
|
|
F.KeyColumns := ColName else
|
|
F.KeyColumns := F.KeyColumns+','+ColName;
|
|
indexs.Next;
|
|
end;
|
|
FA.Add(F);
|
|
indexs.Close;
|
|
meta.Next;
|
|
end;
|
|
meta.Close;
|
|
end;
|
|
SetLength(Indexes,n);
|
|
finally
|
|
indexs.Free;
|
|
meta.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnectionProperties.GetForeignKeys;
|
|
begin
|
|
{ TODO : get FOREIGN KEYS from FireDAC metadata using mkForeignKeys }
|
|
end;
|
|
|
|
function TSQLDBFireDACConnectionProperties.NewConnection: TSQLDBConnection;
|
|
begin
|
|
result := TSQLDBFireDACConnection.Create(self);
|
|
end;
|
|
|
|
|
|
{ TSQLDBFireDACConnection }
|
|
|
|
procedure TSQLDBFireDACConnection.Commit;
|
|
begin
|
|
inherited Commit;
|
|
try
|
|
fDatabase.Commit;
|
|
except
|
|
inc(fTransactionCount); // the transaction is still active
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
constructor TSQLDBFireDACConnection.Create(aProperties: TSQLDBConnectionProperties);
|
|
begin
|
|
inherited Create(aProperties);
|
|
fDatabase := TADConnection.Create(nil);
|
|
fDatabase.ResourceOptions.SilentMode := True; // no need for wait cursor
|
|
fDatabase.LoginPrompt := false;
|
|
fDatabase.Params.Text :=
|
|
(fProperties as TSQLDBFireDACConnectionProperties).fFireDACOptions.Text;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnection.Connect;
|
|
var Log: ISynLog;
|
|
begin
|
|
if fDatabase=nil then
|
|
raise ESQLDBFireDAC.CreateUTF8('%.Connect(%): Database=nil',
|
|
[self,fProperties.ServerName]);
|
|
Log := SynDBLog.Enter('Connect to DriverID=% Database=%',
|
|
[FIREDAC_PROVIDER[fProperties.DBMS],fProperties.DatabaseName],self);
|
|
try
|
|
fDatabase.Open;
|
|
inherited Connect; // notify any re-connection
|
|
Log.Log(sllDB,'Connected to % (%)',
|
|
[fDatabase.DriverName,fProperties.DatabaseName]);
|
|
except
|
|
on E: Exception do begin
|
|
Log.Log(sllError,E);
|
|
Disconnect; // clean up on fail
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnection.Disconnect;
|
|
begin
|
|
try
|
|
inherited Disconnect; // flush any cached statement
|
|
finally
|
|
if fDatabase<>nil then
|
|
fDatabase.Close;
|
|
end;
|
|
end;
|
|
|
|
destructor TSQLDBFireDACConnection.Destroy;
|
|
begin
|
|
try
|
|
Disconnect;
|
|
except
|
|
on Exception do
|
|
end;
|
|
inherited;
|
|
FreeAndNil(fDatabase);
|
|
end;
|
|
|
|
function TSQLDBFireDACConnection.IsConnected: boolean;
|
|
begin
|
|
result := Assigned(fDatabase) and fDatabase.Connected;
|
|
end;
|
|
|
|
function TSQLDBFireDACConnection.NewStatement: TSQLDBStatement;
|
|
begin
|
|
result := TSQLDBFireDACStatement.Create(self);
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnection.Rollback;
|
|
begin
|
|
inherited Rollback;
|
|
fDatabase.Rollback;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACConnection.StartTransaction;
|
|
begin
|
|
inherited StartTransaction;
|
|
fDatabase.StartTransaction;
|
|
end;
|
|
|
|
|
|
{ TSQLDBFireDACStatement }
|
|
|
|
procedure TSQLDBFireDACStatement.DatasetCreate;
|
|
begin
|
|
fQuery := TADQuery.Create(nil);
|
|
TADQuery(fQuery).Connection := (fConnection as TSQLDBFireDACConnection).Database;
|
|
fDatasetSupportBatchBinding := true;
|
|
end;
|
|
|
|
function TSQLDBFireDACStatement.DatasetPrepare(const aSQL: string): boolean;
|
|
begin
|
|
(fQuery as TADQuery).SQL.Text := aSQL;
|
|
fQueryParams := TADQuery(fQuery).Params;
|
|
result := fQueryParams<>nil;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACStatement.Prepare(const aSQL: RawUTF8;
|
|
ExpectResults: boolean);
|
|
begin
|
|
inherited;
|
|
if fPreparedParamsCount<>fQueryParams.Count then
|
|
raise ESQLDBFireDAC.CreateUTF8(
|
|
'%.Prepare() expected % parameters in request, found % - [%]',
|
|
[self,fPreparedParamsCount,fQueryParams.Count,aSQL]);
|
|
end;
|
|
|
|
procedure TSQLDBFireDACStatement.DatasetExecSQL;
|
|
begin
|
|
if fPreparedUseArrayDML then
|
|
(fQuery as TADQuery).Execute(fParamsArrayCount) else
|
|
(fQuery as TADQuery).Execute;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACStatement.DataSetBindSQLParam(const aArrayIndex,
|
|
aParamIndex: integer; const aParam: TSQLDBParam);
|
|
var P: TADParam;
|
|
i: integer;
|
|
tmp: RawUTF8;
|
|
StoreVoidStringAsNull: boolean;
|
|
begin
|
|
if fDatasetSupportBatchBinding then
|
|
fPreparedUseArrayDML := (aArrayIndex<0) and (fParamsArrayCount>0) else
|
|
fPreparedUseArrayDML := false;
|
|
if fPreparedUseArrayDML and (fQueryParams.ArraySize<>fParamsArrayCount) then
|
|
fQueryParams.ArraySize := fParamsArrayCount;
|
|
with aParam do begin
|
|
P := fQueryParams[aParamIndex];
|
|
P.ParamType := SQLParamTypeToDBParamType(VInOut);
|
|
if VinOut <> paramInOut then
|
|
case VType of
|
|
SynTable.ftNull:
|
|
if fPreparedUseArrayDML then
|
|
for i := 0 to fParamsArrayCount-1 do
|
|
P.Clear(i) else
|
|
P.Clear;
|
|
SynTable.ftInt64: begin
|
|
if fPreparedUseArrayDML then
|
|
for i := 0 to fParamsArrayCount-1 do
|
|
if VArray[i]='null' then
|
|
P.Clear(i) else
|
|
P.AsLargeInts[i] := GetInt64(pointer(VArray[i])) else
|
|
if aArrayIndex>=0 then
|
|
if VArray[aArrayIndex]='null' then
|
|
P.Clear else
|
|
P.AsLargeInt := GetInt64(pointer(VArray[aArrayIndex])) else
|
|
P.AsLargeInt := VInt64;
|
|
end;
|
|
SynTable.ftDouble:
|
|
if fPreparedUseArrayDML then
|
|
for i := 0 to fParamsArrayCount-1 do
|
|
if VArray[i]='null' then
|
|
P.Clear(i) else
|
|
P.AsFloats[i] := GetExtended(pointer(VArray[i])) else
|
|
if aArrayIndex>=0 then
|
|
if VArray[aArrayIndex]='null' then
|
|
P.Clear else
|
|
P.AsFloat := GetExtended(pointer(VArray[aArrayIndex])) else
|
|
P.AsFloat := PDouble(@VInt64)^;
|
|
SynTable.ftCurrency:
|
|
if fPreparedUseArrayDML then
|
|
for i := 0 to fParamsArrayCount-1 do
|
|
if VArray[i]='null' then
|
|
P.Clear(i) else
|
|
P.AsCurrencys[i] := StrToCurrency(pointer(VArray[i])) else
|
|
if aArrayIndex>=0 then
|
|
if VArray[aArrayIndex]='null' then
|
|
P.Clear else
|
|
P.AsCurrency := StrToCurrency(pointer(VArray[aArrayIndex])) else
|
|
P.AsCurrency := PCurrency(@VInt64)^;
|
|
SynTable.ftDate:
|
|
if fPreparedUseArrayDML then
|
|
for i := 0 to fParamsArrayCount-1 do
|
|
if VArray[i]='null' then
|
|
P.Clear(i) else begin
|
|
UnQuoteSQLStringVar(pointer(VArray[i]),tmp);
|
|
P.AsDateTimes[i] := Iso8601ToDateTime(tmp);
|
|
end else
|
|
if aArrayIndex>=0 then
|
|
if VArray[aArrayIndex]='null' then
|
|
P.Clear else begin
|
|
UnQuoteSQLStringVar(pointer(VArray[aArrayIndex]),tmp);
|
|
P.AsDateTime := Iso8601ToDateTime(tmp);
|
|
end else
|
|
P.AsDateTime := PDateTime(@VInt64)^;
|
|
SynTable.ftUTF8:
|
|
if fPreparedUseArrayDML then begin
|
|
StoreVoidStringAsNull := fConnection.Properties.StoreVoidStringAsNull;
|
|
for i := 0 to fParamsArrayCount-1 do
|
|
if (VArray[i]='null') or
|
|
(StoreVoidStringAsNull and (VArray[i]=#39#39)) then
|
|
P.Clear(i) else begin
|
|
UnQuoteSQLStringVar(pointer(VArray[i]),tmp);
|
|
{$ifdef UNICODE} // for FireDAC: TADWideString=UnicodeString
|
|
P.AsWideStrings[i] := UTF8ToString(tmp);
|
|
{$else}
|
|
if fForceUseWideString then
|
|
P.AsWideStrings[i] := UTF8ToWideString(tmp) else
|
|
P.AsStrings[i] := UTF8ToString(tmp);
|
|
{$endif}
|
|
end
|
|
end else
|
|
if aArrayIndex>=0 then
|
|
if (VArray[aArrayIndex]='null') or
|
|
(fConnection.Properties.StoreVoidStringAsNull and
|
|
(VArray[aArrayIndex]=#39#39)) then
|
|
P.Clear else begin
|
|
UnQuoteSQLStringVar(pointer(VArray[aArrayIndex]),tmp);
|
|
{$ifdef UNICODE}
|
|
P.AsWideString := UTF8ToString(tmp); // TADWideString=string
|
|
{$else}
|
|
if fForceUseWideString then
|
|
P.AsWideString := UTF8ToWideString(tmp) else
|
|
P.AsString := UTF8ToString(tmp);
|
|
{$endif}
|
|
end else
|
|
if (VData='') and fConnection.Properties.StoreVoidStringAsNull then
|
|
P.Clear else
|
|
{$ifdef UNICODE}
|
|
P.AsWideString := UTF8ToString(VData); // TADWideString=string
|
|
{$else}
|
|
if (not fForceUseWideString) {or IsAnsiCompatible(VData)} then
|
|
P.AsString := UTF8ToString(VData) else
|
|
P.AsWideString := UTF8ToWideString(VData);
|
|
{$endif}
|
|
SynTable.ftBlob:
|
|
if fPreparedUseArrayDML then
|
|
for i := 0 to fParamsArrayCount-1 do
|
|
if VArray[i]='null' then
|
|
P.Clear(i) else
|
|
P.AsBlobs[i] := VArray[i] else
|
|
if aArrayIndex>=0 then
|
|
if VArray[aArrayIndex]='null' then
|
|
P.Clear else
|
|
P.AsBlob := VArray[aArrayIndex] else
|
|
P.AsBlob := VData;
|
|
else
|
|
raise ESQLDBFireDAC.CreateUTF8(
|
|
'%.DataSetBindSQLParam: invalid type % on bound parameter #%',
|
|
[Self,ord(VType),aParamIndex+1]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBFireDACStatement.DataSetOutSQLParam(const aParamIndex: integer;
|
|
var aParam: TSQLDBParam);
|
|
var Par: TADParam;
|
|
begin
|
|
Par := fQueryParams[aParamIndex];
|
|
case aParam.VType of
|
|
SynTable.ftInt64: aParam.VInt64 := Par.AsLargeInt;
|
|
SynTable.ftDouble: PDouble(@aParam.VInt64)^ := Par.AsFloat;
|
|
SynTable.ftCurrency: PCurrency(@aParam.VInt64)^ := Par.AsCurrency;
|
|
SynTable.ftDate: PDateTime(@aParam.VInt64)^ := Par.AsDateTime;
|
|
SynTable.ftUTF8: aParam.VData := StringToUTF8(Par.AsString);
|
|
SynTable.ftBlob: aParam.VData := Par.AsBlob;
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
TSQLDBFireDACConnectionProperties.RegisterClassNameForDefinition;
|
|
end.
|