xtool/contrib/mORMot/SynDBDataset/SynDBNexusDB.pas

590 lines
20 KiB
ObjectPascal

/// NexusDB 3.x direct access classes (embedded engine only)
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynDBNexusDB;
{
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):
- Bas Schouten (initial uSGSynDBNexusDB.pas port)
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
{.$define SYNDB_FULLNEXUSDB}
// by default, only the NexusDB Free Embedded version is interfaced
// - you can define this conditional in project options to use all units
uses
Windows, SysUtils,
{$IFNDEF DELPHI5OROLDER}
Variants,
{$ENDIF}
Classes, Contnrs,
{$ifdef ISDELPHIXE2}Data.DB,{$else}DB,{$endif}
SynCommons,
SynTable,
SynLog,
SynDB,
SynDBDataset,
nxDB,
nxsdServerEngine,
nxsrServerEngine,
nxsqlEngine,
nxlgEventLog,
nxllTransport,
{$ifdef SYNDB_FULLNEXUSDB}
nxchCommandHandler,
nxsiServerInfoPluginServer,
nxtcCOMTransport,
nxtmSharedMemoryTransport,
nxtnNamedPipeTransport,
nxtsBlowfishRC4SecuredTransport,
nxtwWinsockTransport,
{$endif}
nxseAutoComponent;
{ -------------- NexusDB database engine native connection }
type
/// Exception type associated to the direct NexusDB connection
ESQLDBNexusDB = class(ESQLDBDataset);
// available communication protocols used by NexusDB between client and server
// - nxpFOLDER: default protocol, accessing NexusDB database in a Windows Folder
// - nxpTCPIP: TCP/IP transport, indicated by nxtcp://
// - nxpPIPE: Windows Named Pipe transport, indicated by nxpipe://
// - nxpMEM: direct memory transport, indicated by nxmem://
// - nxpBFISH: BlowFish transport, indicated by nxbfisch://
TNXProtocol = (
nxpUnknown,
nxpFOLDER,
nxpTCPIP,
nxpPIPE,
nxpCOM,
nxpMEM,
nxpBFISH);
// implement properties shared by native NexusDB connections
// - note that only the embedded engine is implemented by now - feedback needed!
TSQLDBNexusDBConnectionProperties = class(TSQLDBDatasetConnectionProperties)
private
fProtocol: TNXProtocol;
protected
/// initialize fForeignKeys content with all foreign keys of this DB
// - used by GetForeignKey method
procedure GetForeignKeys; override;
public
/// initialize the properties to connect to the NexusDB engine
// - this overridden method will initialize the protocol to be used as stated
// by aServerName i.e. nxpTCIP://11.23.34.43
// - Default protocol is nxpFolder
// - if protocol is nxpFolder then aDatabaseName will contain the path to the
// folder to be used
// - if protocol is other then nxpFolder than aServerName will contain the server
// to connect to and aDatabaseName will contains the alias of the database
// - Possible aServerName formats:
// $ <protocol>://<servername>/<alias> (aDatabaseName will be overwritten by this alias)
// $ <protocol>://servername (aDatabaseName will contain alias)
// $ '' (aDatabaseName contains path to nxpFOLDER database)
constructor Create(const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8); override;
/// convert a textual column data type, as retrieved e.g. from SQLGetField,
// into our internal primitive types
function ColumnTypeNativeToDB(const aNativeType: RawUTF8; aScale: integer): TSQLDBFieldType; override;
/// Determine if database exists
// - just test if the corresponding folder exists
function DatabaseExists: boolean; virtual;
/// create the database folder (if not existing)
function CreateDatabase: boolean; virtual;
/// delete the database folder
// - including all its files - so to be used carefully!
function DeleteDatabase: boolean; virtual;
/// create a new connection
// - caller is responsible of freeing this instance
// - this overridden method will create an TSQLDBNexusDBConnection instance
function NewConnection: TSQLDBConnection; override;
published
/// the transport protocol used to connect to the NexusDB engine
property Protocol: TNXProtocol read fProtocol;
end;
// implements a direct connection to the native NexusDB database
TSQLDBNexusDBConnection = class(TSQLDBConnectionThreadSafe)
protected
fDatabase: TnxDatabase;
fSession: TnxSession;
fServerEngine: TnxBaseServerEngine;
procedure SetServerEngine(aServerEngine: TnxBaseServerEngine);
public
/// prepare a connection to a specified NexusDB database server
constructor Create(aProperties: TSQLDBConnectionProperties); override;
/// release memory and connection
destructor Destroy; override;
/// connect to the specified NexusDB server
// - should raise an ESQLDBNexusDB on error
procedure Connect; override;
/// stop connection to the specified NexusDB database server
// - should raise an ESQLDBNexusDB 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 NexusDB connection instance
property Database: TnxDatabase read fDatabase;
/// associated NexusDB server engine
property ServerEngine: TnxBaseServerEngine read fServerEngine write SetServerEngine;
end;
// implements a statement via the native NexusDB connection
TSQLDBNexusDBStatement = class(TSQLDBDatasetStatement)
protected
/// initialize and set fQuery internal field as expected
procedure DatasetCreate; override;
/// set fQueryParams internal field as expected
function DatasetPrepare(const aSQL: string): boolean; override;
/// execute underlying TQuery.ExecSQL
procedure DatasetExecSQL; override;
public
end;
const
/// set aServerName to this value to create an in-memory table
// - do not use this constant, since it was not working as expected yet
NEXUSDB_INMEMORY = '#INMEM';
// determine NexusDB transport protocol (TNXProtocol) to be used, based on
// protocol indicator in connection string
// - if no protocol specifier is included in the connectionstring then nxpFOLDER
// is assumed.
// - aServerName will contain the URL to the Server if the protocol
// is not nxpFOLDER
function GetNXProtocol(const aConnectionString: RawUTF8; out aServerName: RawUTF8;
out aAlias: RawUTF8): TNXProtocol;
/// return the internal NexusDB embedded engine
// - initialize it, if was not already the case
function NexusEmbeddedEngine: TnxServerEngine;
/// release any internal NexusDB embedded engine
// - returns nil on success, or PtrInt(-1) if was not initialized
function DropNexusEmbeddedEngine: TnxServerEngine;
implementation
uses
{$ifdef SYNDB_FULLNEXUSDB}
nxreRemoteServerEngine,
uMiscellaneous,
{$endif}
nxsdConst,
nxsdTypes;
{ TSQLDBNexusDBConnectionProperties }
function TSQLDBNexusDBConnectionProperties.ColumnTypeNativeToDB(const aNativeType: RawUTF8;
aScale: integer): TSQLDBFieldType;
const CONV_TABLE: array[TnxFieldType] of TSQLDBFieldType = (
SynTable.ftInt64, SynTable.ftUTF8, SynTable.ftUTF8, SynTable.ftInt64,
SynTable.ftInt64, SynTable.ftInt64, SynTable.ftInt64, SynTable.ftInt64,
SynTable.ftInt64, SynTable.ftInt64, SynTable.ftInt64, SynTable.ftDouble,
SynTable.ftDouble, SynTable.ftDouble, SynTable.ftCurrency, SynTable.ftDate,
SynTable.ftDate, SynTable.ftDate, SynTable.ftInt64, SynTable.ftBlob,
SynTable.ftUTF8, SynTable.ftBlob, SynTable.ftBlob, SynTable.ftUTF8,
SynTable.ftUTF8, SynTable.ftUTF8, SynTable.ftInt64, SynTable.ftUTF8,
SynTable.ftCurrency, SynTable.ftUTF8, SynTable.ftDouble );
begin
result := CONV_TABLE[FieldDataTypesMapSQL(UTF8ToString(aNativeType))];
end;
constructor TSQLDBNexusDBConnectionProperties.Create(const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8);
var lServerURL, lAlias: RawUTF8;
begin
fDBMS := dNexusDB;
inherited Create(aServerName,aDatabaseName,aUserID,aPassWord);
fProtocol := GetNXProtocol(aServerName,lServerURL,lAlias);
if fProtocol=nxpFOLDER then
fServerName := '' else
if fProtocol>nxpFOLDER then
fServerName := lServerURL;
if fProtocol>nxpUnknown then
fDatabaseName := lAlias else
fDatabaseName := '';
end;
procedure TSQLDBNexusDBConnectionProperties.GetForeignKeys;
begin
with Execute('select F.FK_CONSTRAINT_TABLE_NAME||''.''||C.FK_CONSTRAINT_REFERENCING_COLUMNS_NAME col, ' +
' F.FK_CONSTRAINT_REFERENCES_TABLE_NAME||''.''||R.FK_CONSTRAINT_REFERENCED_COLUMNS_NAME ref ' +
' from #FOREIGNKEY_CONSTRAINTS F, ' + ' #FOREIGNKEY_CONSTRAINTS_REFERENCING_COLUMNS C, ' +
' #FOREIGNKEY_CONSTRAINTS_REFERENCED_COLUMNS R ' + ' where ' +
' F.FK_CONSTRAINT_TABLE_NAME = C.FK_CONSTRAINT_TABLE_NAME' +
' and F.FK_CONSTRAINT_NAME = C.FK_CONSTRAINT_NAME' +
' F.FK_CONSTRAINT_TABLE_NAME = R.FK_CONSTRAINT_TABLE_NAME' +
' and F.FK_CONSTRAINT_NAME = R.FK_CONSTRAINT_NAME', []) do
while Step do
fForeignKeys.Add(ColumnUTF8(0),ColumnUTF8(1));
end;
function TSQLDBNexusDBConnectionProperties.NewConnection: TSQLDBConnection;
begin
result := TSQLDBNexusDBConnection.Create(self);
end;
function TSQLDBNexusDBConnectionProperties.DatabaseExists: boolean;
begin
if (fProtocol=nxpFOLDER) and (fDatabaseName<>NEXUSDB_INMEMORY) then
result := DirectoryExists(UTF8ToString(fDatabaseName)) else
result := True; // if we cannot determine directly, assume it exists
end;
function TSQLDBNexusDBConnectionProperties.CreateDatabase: boolean;
begin
if fProtocol=nxpFOLDER then
if fDatabaseName=NEXUSDB_INMEMORY then
result := true else
result := ForceDirectories(UTF8ToString(fDatabaseName)) else
result := false;
end;
function TSQLDBNexusDBConnectionProperties.DeleteDatabase: boolean;
begin
if fProtocol=nxpFOLDER then
if fDatabaseName=NEXUSDB_INMEMORY then
result := true else
result := DirectoryDelete(UTF8ToString(fDatabaseName)) else
result := false;
end;
{ TSQLDBNexusDBConnection }
procedure TSQLDBNexusDBConnection.Commit;
begin
inherited Commit;
try
fDatabase.Commit;
except
inc(fTransactionCount); // the transaction is still active
raise;
end;
end;
procedure TSQLDBNexusDBConnection.Connect;
var Log: ISynLog;
begin
Log := SynDBLog.Enter;
try
ServerEngine.Active := True;
ServerEngine.Connected := True;
fDatabase.Name := ClassName;
fDatabase.Connect;
inherited Connect; // notify any re-connection
except
on E: Exception do begin
Log.Log(sllError,E);
Disconnect; // clean up on fail
raise;
end;
end;
end;
constructor TSQLDBNexusDBConnection.Create(aProperties: TSQLDBConnectionProperties);
var
lProp: TSQLDBNexusDBConnectionProperties;
var Log: ISynLog;
begin
Log := SynDBLog.Enter;
inherited Create(aProperties);
lProp := aProperties as TSQLDBNexusDBConnectionProperties; // type check to make sure
if lProp.Protocol=nxpUnknown then
raise ESQLDBNexusDB.CreateUTF8(
'%.Create: Unknown NexusDB protocol in Servername=[%]',
[self,lProp.ServerName]);
fDatabase := TnxDatabase.Create(nil);
fSession := TnxSession.Create(nil);
fSession.UserName := UTF8ToString(lProp.UserID);
fSession.Password := UTF8ToString(lProp.PassWord);
fDatabase.Session := fSession;
if lProp.Protocol=nxpFOLDER then begin
SetServerEngine(NexusEmbeddedEngine);
if not lProp.DatabaseExists then begin
Log.Log(sllDB,'Database % does not exists -> create folder',[lProp.DatabaseName]);
lProp.CreateDatabase;
end;
Database.AliasPath := lProp.DatabaseName;
Log.Log(sllDB,'NexusDB % using database folder %',
[fDatabase.Version,lProp.DatabaseName]);
end else begin
raise ESQLDBNexusDB.Create('Remote NexusDB engine not supported (yet)');
{ SetServerEngine(TnxRemoteServerEngine.Create(nil));
Database.AliasName := lProp.DatabaseName;
case lProp.Protocol of
nxpTCPIP: FTransport := TnxWinsockTransport.Create(nil);
nxpPIPE: FTransport := TnxNamedPipeTransport.Create(nil);
nxpCOM: FTransport := TnxRegisteredCOMTransport.Create(nil);
nxpMEM: FTransport := TnxSharedMemoryTransport.Create(nil);
nxpBFISH: FTransport := TnxBlowfishRC4SecuredTransport.Create(nil);
end;
TnxRemoteServerEngine(FServerEngine).Transport := FTransport;
FTransport.ServerName := lProp.Servername;
FTransport.CommandHandler := FCommandHandler;
FTransport.EventLog := FEventLog; }
end;
end;
destructor TSQLDBNexusDBConnection.Destroy;
begin
Disconnect;
inherited;
FreeAndNil(fDatabase);
FreeAndNil(fSession);
end;
procedure TSQLDBNexusDBConnection.Disconnect;
begin
try
inherited Disconnect; // flush any cached statements
finally
if Assigned(fDatabase) then
fDatabase.Close;
if Assigned(fSession) and fSession.Active then begin
fSession.CloseInactiveTables;
fSession.CloseInactiveFolders;
fSession.Close;
end;
end;
end;
function TSQLDBNexusDBConnection.IsConnected: boolean;
begin
result := Assigned(fDatabase) and fDatabase.Connected;
end;
function TSQLDBNexusDBConnection.NewStatement: TSQLDBStatement;
begin
result := TSQLDBNexusDBStatement.Create(self);
end;
procedure TSQLDBNexusDBConnection.Rollback;
begin
inherited Rollback;
fDatabase.Rollback;
end;
procedure TSQLDBNexusDBConnection.SetServerEngine(aServerEngine: TnxBaseServerEngine);
begin
if FServerEngine<>aServerEngine then begin
FServerEngine := aServerEngine;
fSession.ServerEngine := aServerEngine;
end;
end;
procedure TSQLDBNexusDBConnection.StartTransaction;
begin
inherited StartTransaction;
if not fDatabase.TryStartTransaction then
raise ESQLDBNexusDB.Create('Error occcured trying to start a transaction');
end;
{ TSQLDBNexusDBStatement }
function TSQLDBNexusDBStatement.DatasetPrepare(const aSQL: string): boolean;
begin
(fQuery as TnxQuery).SQL.Text := aSQL;
fQueryParams := TnxQuery(fQuery).Params;
result := fQueryParams<>nil;
end;
procedure TSQLDBNexusDBStatement.DatasetExecSQL;
begin
(fQuery as TnxQuery).ExecSQL;
end;
procedure TSQLDBNexusDBStatement.DatasetCreate;
begin
fQuery := TnxQuery.Create(nil);
with TnxQuery(fQuery) do begin
Database := (fConnection as TSQLDBNexusDBConnection).Database;
Session := TSQLDBNexusDBConnection(fConnection).Database.Session;
end;
end;
{ low-level NexusDB engine functions }
var
vNexusEmbeddedEngine: TnxServerEngine;
function GetNXProtocol(const aConnectionString: RawUTF8; out aServerName: RawUTF8;
out aAlias: RawUTF8): TNXProtocol;
const
NXPROTNAMES:array[nxpFOLDER..high(TNXProtocol)] of RawUTF8 = (
'nxemb','nxtcp','nxpipe','nxcom','nxmem','nxbfish');
var Prot, Alias,l,r: RawUTF8;
IsPath: boolean;
pr:TNXProtocol;
begin
Split(aConnectionString,':',Prot,Alias);
IsPath := (Prot='') or // no protocol indicated: assume it's a path (relative)
(Prot=NEXUSDB_INMEMORY) or
((Length(Prot)=1) and // check for drive letter
(Prot[1] in ['a'..'z','A' .. 'Z']) and
DirectoryExists(TFileName(Prot[1])+':\')) or // check if root folder can be found
IdemPChar(Pointer(aConnectionString),'.\') or
IdemPChar(Pointer(aConnectionString),'..\');
if IsPath then begin
result := nxpFOLDER;
aAlias := aConnectionString;
end else begin
result := nxpUnknown;
for pr := Low(NXPROTNAMES) to High(NXPROTNAMES) do
if prot=NXPROTNAMES[pr] then begin
result := pr;
break;
end;
if result=nxpFOLDER then begin
if prot<>'' then
aAlias := Alias else
aAlias := aConnectionString;
end else
if result>nxpFOLDER then begin
Split(aConnectionString,'://',l,r);
Split(r,'/',aServerName,aAlias);
end;
end;
end;
function DropNexusEmbeddedEngine: TnxServerEngine;
var cmp: TComponent;
i: integer;
begin
if PtrInt(vNexusEmbeddedEngine)<>-1 then begin
if Assigned(vNexusEmbeddedEngine) then begin
vNexusEmbeddedEngine.Close;
vNexusEmbeddedEngine.SqlEngine := nil;
vNexusEmbeddedEngine.EventLog := nil;
// ensure our components get destroyed before destroying the engine
for i := vNexusEmbeddedEngine.ComponentCount-1 downto 0 do begin
cmp := vNexusEmbeddedEngine.Components[i];
{$ifdef SYNDB_FULLNEXUSDB}
if cmp is TnxServerCommandHandler then
with TnxServerCommandHandler(cmp) do begin
ServerEngine := nil;
Close;
Free;
end else
{$endif}
if cmp is TnxSqlEngine then
with TnxSqlEngine(cmp) do begin
Close;
Free;
end else
if cmp is TnxEventLog then
with TnxEventLog(cmp) do begin
Flush;
Free;
end;
end;
end;
FreeAndNil(vNexusEmbeddedEngine);
end;
result := vNexusEmbeddedEngine;
end;
function NexusEmbeddedEngine: TnxServerEngine;
{$ifdef SYNDB_FULLNEXUSDB}
var CommandHandler: TnxServerCommandHandler;
{$endif}
begin
if PtrInt(vNexusEmbeddedEngine)=-1 then
raise ESQLDBNexusDB.Create('Nexus Embedded engine was already finalized!') else
if vNexusEmbeddedEngine=nil then begin
vNexusEmbeddedEngine := TnxServerEngine.Create(nil);
//vNexusEmbeddedEngine.Options := vNexusEmbeddedEngine.Options+[seoInMemOnly];
{$ifdef SYNDB_FULLNEXUSDB}
CommandHandler := TnxServerCommandHandler.Create(vNexusEmbeddedEngine);
{$endif}
vNexusEmbeddedEngine.SqlEngine := TnxSqlEngine.Create(vNexusEmbeddedEngine);
vNexusEmbeddedEngine.EventLog := TnxEventLog.Create(vNexusEmbeddedEngine);
{$ifdef SYNDB_FULLNEXUSDB}
CommandHandler.ServerEngine := vNexusEmbeddedEngine;
{$endif}
end;
result := vNexusEmbeddedEngine;
end;
procedure FinalizeNXEmbeddedEngine;
begin
if PtrInt(vNexusEmbeddedEngine)<>-1 then
try
DropNexusEmbeddedEngine;
finally
PtrInt(vNexusEmbeddedEngine) := -1; // mark always as finalized
end;
end;
initialization
TSQLDBNexusDBConnectionProperties.RegisterClassNameForDefinition;
finalization
FinalizeNXEmbeddedEngine;
end.