Note that the Execute() method returns a RawJSON
kind of variable, which is in fact a sub-type of RawUTF8. Its
purpose is to transmit the UTF-8 encoded content directly, with no translation
to a JSON string, as would be the case with a RawUTF8 variable. In
fact, escaping some JSON array within a JSON string is quite verbose. Using
RawJSON in this case ensure the best client-side and server-side
speed, and also reduce the transmission bandwidth.
The server part is quite easy to follow:
type
TServiceRemoteSQL = class(TInterfacedObject, IRemoteSQL)
protected
fProps: TSQLDBConnectionProperties;
public
destructor Destroy; override;
public // implements IRemoteSQL methods
procedure Connect(aEngine: TRemoteSQLEngine; const aServerName, aDatabaseName,
aUserID, aPassWord: RawUTF8);
function GetTableNames: TRawUTF8DynArray;
function Execute(const aSQL: RawUTF8; aExpectResults, aExpanded: Boolean): RawJSON;
end;
{ TServiceRemoteSQL }
procedure TServiceRemoteSQL.Connect(aEngine: TRemoteSQLEngine; const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8);
const // rseOleDB, rseODBC, rseOracle, rseSQlite3, rseJet, rseMSSQL
TYPES: array[TRemoteSQLEngine] of TSQLDBConnectionPropertiesClass = (
TOleDBConnectionProperties, TODBCConnectionProperties,
TSQLDBOracleConnectionProperties, TSQLDBSQLite3ConnectionProperties,
TOleDBJetConnectionProperties, TOleDBMSSQL2008ConnectionProperties);
begin
if fProps<>nil then
raise Exception.Create('Connect called more than once');
fProps := TYPES[aEngine].Create(aServerName,aDatabaseName,aUserID,aPassWord);
end;
function TServiceRemoteSQL.Execute(const aSQL: RawUTF8; aExpectResults, aExpanded: Boolean): RawJSON;
var res: ISQLDBRows;
begin
if fProps=nil then
raise Exception.Create('Connect call required before Execute');
res := fProps.ExecuteInlined(aSQL,aExpectResults);
if res=nil then
result := '' else
result := res.FetchAllAsJSON(aExpanded);
end;
function TServiceRemoteSQL.GetTableNames: TRawUTF8DynArray;
begin
if fProps=nil then
raise Exception.Create('Connect call required before GetTableNames');
fProps.GetTableNames(result);
end;
destructor TServiceRemoteSQL.Destroy;
begin
FreeAndNil(fProps);
inherited;
end;
Any exception during SynDB process, or raised manually in case
of wrong use case will be transmitted to the client, just as expected. The
fProps instance life-time is handled by the client, so all we need
is to release its pointer in the service implementation destructor.
From the client point of view, it will be consumed as such:
procedure TMainForm.FormShow(Sender: TObject); (...) fModel := TSQLModel.Create([],ROOT_NAME); fClient := TSQLHttpClient.Create('localhost','888',fModel); if not fClient.ServerTimeStampSynchronize then begin ShowLastClientError(fClient,'Please run Project16ServerHttp.exe'); Close; exit; end; if (not fClient.SetUser('User','synopse')) or (not fClient.ServiceRegisterClientDriven(TypeInfo(IRemoteSQL),fService)) then begin ShowLastClientError(fClient,'Remote service not available on server'); Close; exit; end; end;
Note the use of ShowLastClientError() function of
mORMotUILogin unit, which is able to use our
SynTaskDialog unit to report standard and detailed information
about the latest error.
Note that in this sample, no table has been defined within the ORM model. It
is not necessary, since all external process will take place at the SQL level.
As we need authentication (see the call to fClient.SetUser
method), the ORM core will by itself add the TSQLAuthUser and
TSQLAuthGroup tables to the model - no need to add them
explicitly.
From now on, we have a fService: IRemoteSQL instance available
to connect and process any remote SQL request.
procedure TMainForm.btnOpenClick(Sender: TObject);
var TableNames: TRawUTF8DynArray;
(...)
with fSettings do
fService.Connect(Engine,ServerName,DatabaseName,UserID,PassWord);
TableNames := fService.GetTableNames;
cbbTableNames.Items.Text := UTF8ToString(RawUTF8ArrayToCSV(TableNames,#13#10));
(...)
Now we are connected to the database via the remote service, and we
retrieved the table names in a TComboBox.
Then a particular SQL statement can be executed as such:
procedure TMainForm.btnExecuteClick(Sender: TObject);
var SQL: RawUTF8;
begin
SQL := trim(StringToUTF8(mmoQuery.Text));
Screen.Cursor := crHourGlass;
try
try
if isSelect(pointer(SQL)) then begin
fTableJSON := fService.Execute(SQL,True,False);
TSQLTableToGrid.Create(drwgrdData,
TSQLTableJSON.Create([],SQL,pointer(fTableJSON),Length(fTableJSON)),fClient);
end else
fService.Execute(SQL,False,False);
except
on E: Exception do
ShowException(E);
end;
finally
Screen.Cursor := crDefault;
end;
end;
Here, TSQLTableToGrid.Create(), from the mORMotUI
unit, will "inject" the returned data to a standard TDrawGrid,
using a TSQLTableJSON instance to unserialize the returned JSON
content.
Note that in case of any exception (connection failure, or server side
error, e.g. wrong SQL statement), the ShowExecption() method is
used to notify the user with appropriate information.
Feedback is welcome on our forum.

