mORMot and Open Source friends
Check-in [ace12b0996]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:
  • BREAKING CHANGE in TSQLRestServerCallBackParams use: all method-based services should now be a procedure, and use aParams.Results()/Error() methods to return the content - new definition of aParams features now wfull access to incoming/outgoing context and parameters
  • TSQLRestServer.URI() method uses now one TSQLRestServerURIParams parameter
  • added TAuthSession.SentHeaders and RemoteIP properties (for HTTP)
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: ace12b0996bf53914ff62c0757d4160fb550d5ab
User & Date: abouchez 2012-12-04 17:27:47
Context
2012-12-05
10:35
renamed aParams: TSQLRestServerCallBackParams into more explicit/neutral Ctxt: TSQLRestServerCallBackParams signature check-in: c1b6696521 user: abouchez tags: trunk
2012-12-04
17:27
  • BREAKING CHANGE in TSQLRestServerCallBackParams use: all method-based services should now be a procedure, and use aParams.Results()/Error() methods to return the content - new definition of aParams features now wfull access to incoming/outgoing context and parameters
  • TSQLRestServer.URI() method uses now one TSQLRestServerURIParams parameter
  • added TAuthSession.SentHeaders and RemoteIP properties (for HTTP)
check-in: ace12b0996 user: abouchez tags: trunk
2012-12-03
13:53
introducing TSQLHttpClientGeneric.Compression property to set the handled compression schemes at runtime check-in: 47acace079 user: abouchez tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/Documentation/Synopse SQLite3 Framework.pro.

4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216

4217
4218


4219
4220
4221
4222

4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235





4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246

4247
4248
4249
4250
4251
4252
4253
....
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275


4276
4277
4278
4279
4280
4281
4282
4283
4284

4285
4286
4287
4288


4289
4290
4291
























4292
4293
4294


4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
- By some RESTful @**service@s, implemented in the Server as {\i published methods}, and consumed in the Client via native Delphi methods;
- Defining some {\i service @*contract@s} as standard Delphi {\f1\fs20 interface}, and then run it seamlesly on both client and client sides.
The first of the two last items can be compared to the {\i DataSnap} Client-Server features, also @*JSON@-based, introduced in Delphi 2010. See for instance the following example available on the Internet at @http://docwiki.embarcadero.com/RADStudio/en/Developing_DataSnap_Applications
The second is purely interface-based, so matches the "designed by contract" principle - see @47@ - as implemented by Microsoft's @*WCF@ technology. {\i Windows Communication Foundation} is the unified programming model provided by Microsoft for building service-oriented applications - see @http://msdn.microsoft.com/en-us/library/dd456779. We included most of the nice features made available in WCF in {\i mORMot}, in a KISS manner.
:49  Client-Server services via methods
To implement a service in the {\i Synopse mORMot framework}, the first method is to define @**published method@ Server-side, then use easy functions about JSON or URL-parameters to get the request encoded and decoded as expected, on Client-side.
We'll implement the same example as in the official Embarcadero docwiki page above. Add two numbers. Very useful service, isn't it?
:   My Server is rich
On the server side, we need to customize the standard {\f1\fs20 TSQLRestServer} class definition (more precisely a {\f1\fs20 @*TSQLRestServerDB@} class which includes a {\i SQlite3} engine, or a lighter {\f1\fs20 @*TSQLRestServerFullMemory@} kind of server, which is enough for our purpose), by adding a new {\f1\fs20 published} method:
!type
!  TSQLRestServerTest = class(TSQLRestServerFullMemory)
!   (...)
!!  published
!!    function Sum(var aParams: TSQLRestServerCallBackParams): Integer;
!  end;
The method name ("Sum") will be used for the URI encoding, and will be called remotely from {\i ModelRoot/Sum} URL. The {\i ModelRoot} is the one defined in the {\f1\fs20 Root} parameter of the {\i model} used by the application.
This method, like all Server-side methods, MUST have the same exact parameter definition as in the {\f1\fs20 TSQLRestServerCallBack} prototype:
!type
!  TSQLRestServerCallBack = function(var aParams: TSQLRestServerCallBackParams): Integer of object;
Then we implement this method:
!function TSQLRestServerTest.Sum(var aParams: TSQLRestServerCallBackParams): Integer;
!var a,b: Extended;
!begin
!  if not UrlDecodeNeedParameters(aParams.Parameters,'A,B') then
!  begin
!    result := 404; // invalid Request
!    aParams.ErrorMsg^ := 'Missing Parameter';
!    exit;
!  end;
!  while aParameters<>nil do
!  begin

!    UrlDecodeExtended(aParams.Parameters,'A=',a);
!    UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);


!  end;
!  aParams.Resp := JSONEncodeResult([a+b]);
!  // same as : aResp := JSONEncode(['result',a+b],TempMemoryStream);
!  result := 200; // success

!end;
The only not obvious part of this code is the parameters marshaling, i.e. how the values are retrieved from the incoming {\f1\fs20 aParams.Parameters} text buffer, then converted into native local variables.
On the Server side, typical implementation steps are therefore:
- Use the {\f1\fs20 UrlDecodeNeedParameters} function to check that all expected parameters were supplied by the caller in {\f1\fs20 aParams.Parameters};
- Call {\f1\fs20 UrlDecodeInteger / UrlDecodeInt64 / UrlDecodeExtended / UrlDecodeValue / UrlDecodeObject} functions (all defined in {\f1\fs20 SynCommons.pas}) to retrieve each individual parameter from standard JSON content;
- Implement the service (here it is just the {\f1\fs20 a+b} expression);
- Then return the result into {\f1\fs20 aParams.Resp} variable.
The powerful {\f1\fs20 UrlDecodeObject} function (defined in {\f1\fs20 mORMot.pas}) can be used to un-serialize most class instance from its textual JSON representation ({\f1\fs20 @*TPersistent@, @*TSQLRecord@, TStringList}...).
Note that due to this implementation pattern, the {\i mORMot} service implementation is very fast, and not sensitive to the "Hash collision attack" security issue, as reported with {\i Apache} - see @http://blog.synopse.info/post/2011/12/30/Hash-collision-attack for details.
The implementation must return the HTTP error code (e.g. 200 on success) as an integer value, and any response in {\f1\fs20 aParams.Resp} as a serialized JSON object by default (using e.g. {\f1\fs20 TSQLRestServer.JSONEncodeResult}), since default mime-type is {\f1\fs20 JSON_CONTENT_TYPE}:
$ {"Result":"OneValue"}
or a JSON object containing an array:
$ {"Result":["One","two"]}





So you can consume these services, implemented Server-Side in fast Delphi code, with any @*AJAX@ application on the client side (if you use HTTP as communication protocol).
The {\f1\fs20 aParams.Head^} parameter may be overridden on the server side to set a custom header which will be provided to the client - it may be useful for instance to specify another mime-type than the default constant {\f1\fs20 JSON_CONTENT_TYPE}, i.e. {\f1\fs20 'application/json; charset=UTF-8'}, and returns plain text, HTML or binary.
In case of an error on the server side (only two valid status codes are {\f1\fs20 200} and {\f1\fs20 201}), the client will receive a corresponding serialized JSON error object, as such:
${
$ "ErrorCode":404,
$ "ErrorText":"Missing Parameter"
$}
The {\f1\fs20 aParams.ErrorMsg^} parameter can be overridden on the server side to specify a custom error message in plain English, which will be returned to the client in case of an invalid status code. If no custom {\f1\fs20 ErrorMsg} is specified, the framework will return the corresponding generic HTTP status text.
The {\f1\fs20 aParams.Context} parameter may contain at calling time the expected {\f1\fs20 TSQLRecord} ID (as decoded from {\i RESTful} URI), and the current session, user and group IDs. If @*authentication@ - see @18@ - is not used, this parameter is meaningless: in fact, {\f1\fs20 aParams.Context.Session} will contain either 0 if any @*session@ is not yet started, or 1 if authentication mode is not active. Server-side implementation can use the {\f1\fs20 TSQLRestServer.SessionGetUser} method to retrieve the corresponding user details (note that when using this method, the returned {\f1\fs20 TSQLAuthUser} instance is a local thread-safe copy which shall be freed when done).
An {\i important point} is to remember that the implementation of the callback method {\b must be thread-safe} - as stated by @25@. In fact, the {\f1\fs20 TSQLRestServer.URI} method expects such callbacks to handle the thread-safety on their side. It's perhaps some more work to handle a critical section in the implementation, but, in practice, it's the best way to achieve performance and scalability: the resource locking can be made at the tiniest code level.
:   The Client is always right

The client-side is implemented by calling some dedicated methods, and providing the service name ({\f1\fs20 'sum'}) and its associated parameters:
!function Sum(aClient: TSQLRestClientURI; a, b: double): double;
!var err: integer;
!begin
!  val(aClient.CallBackGetResult('sum',['a',a,'b',b]),Result,err);
!end;
You could even implement this method in a dedicated client method - which make sense:
................................................................................
You have to create the server instance, and the corresponding {\f1\fs20 TSQLRestClientURI} (or {\f1\fs20 TMyClient}), with the same database model, just as usual...
On the Client side, you can use the {\f1\fs20 CallBackGetResult} method to call the service from its name and its expected parameters, or create your own caller using the {\f1\fs20 UrlEncode()} function. Note that you can specify most class instance into its JSON representation by using some {\f1\fs20 TObject} into the method arguments:
!function TMyClient.SumMyObject(a, b: TMyObject): double;
!var err: integer;
!begin
!  val(CallBackGetResult('summyobject',['a',a,'b',b]),Result,err);
!end;
This Client-Server protocol uses JSON here, but you can serve any kind of data, binary, HTML, whatever... just by overriding the content type on the server.
The usual protocols of our framework can be used: @*HTTP@/1.1, Named Pipe, Windows GDI messages, direct in-memory/in-process access.
Of course, these services can be related to any table/class of our @*ORM@ framework, so you would be able to create easily any RESTful compatible requests on URI like {\f1\fs20 ModelRoot/TableName/ID/MethodName}. The ID of the corresponding record is decoded from its {\i RESTful} scheme into {\f1\fs20 aParams.Context.ID}. For example, here we return a @*BLOB@ field content as hexadecimal:


!function TSQLRestServerTest.DataAsHex(var aParams: TSQLRestServerCallBackParams): Integer;
!var aData: TSQLRawBlob;
!begin
!  result := 404; // invalid Request
!  if (self=nil) or (aParams.Table=nil) or
!      not aParams.Table.InheritsFrom(TSQLRecord) or
!    (aParams.Context.ID<0) then
!    exit; // we need a valid record and its ID
!  if not RetrieveBlob(TSQLRecordPeople,aParams.Context.ID,'Data',aData) then

!    exit; // impossible to retrieve the Data BLOB field
!  aParams.Resp := JSONEncodeResult([SynCommons.BinToHex(aData)]);
!  // idem: aResp := JSONEncode(['result',BinToHex(aRecord.fData)],TempMemoryStream);
!  result := 200; // success


!end;
:63  Interface based services
The @49@ implementation gives full access to the lowest-level of the {\i mORMot}'s core, so it has some advantages:
























- It can be tuned to fit any purpose (such as retrieving or returning some HTML or binary data, or modifying the HTTP headers on the fly);
- It is integrated into the @*REST@ful URI model, so it can be related to any table/class of our @*ORM@ framework (like {\f1\fs20 DataAsHex} service above), or it can handle any remote query (e.g. any @*AJAX@ or @*SOAP@ requests);
- It has a very low performance overhead, so can be used to reduce server workload for some common tasks.


But this implementation pattern has some drawbacks:
- Most content marshaling is to be done by hand, so may introduce implementation issues;
- Client and server side code does not have the same implementation pattern, so you will have to code explicitly data marshaling twice, for both client and server;
- The services do not have any hierarchy, and are listed as a plain list, which is not very convenient;
- It is difficult to synchronize several service calls within a single context, e.g. when a workflow is to be handled during the application process (you have to code some kind of state machine on both sides);
- @*Security@ is handled globally for the user, or should be checked by hand in the implementation method (using the {\f1\fs20 aParams.Context} values).
You can get rid of those limitations with the interface-based service implementation of {\i mORMot}. For a detailed introduction and best practice guide to @*SOA@, you can consult this classic article: @http://www.ibm.com/developerworks/webservices/library/ws-soa-design1
According to this document, all expected SOA features are now available in the current implementation of the {\i mORMot} framework (including service catalog aka "broker").
:   Implemented features
Here are the key features of the current implementation of services using interfaces in the {\i Synopse mORMot framework}:
|%25%75
|\b Feature|Remarks\b0
|Service Orientation|Allow loosely-coupled relationship
|Design by contract|Data Contracts are defined in Delphi code as standard {\f1\fs20 interface} custom types
|Factory driven|Get an implementation instance from a given interface
|Server factory|You can get an implementation on the server side
|Client factory|You can get a "fake" implementation on the client side, remotely calling the server to execute the process
|Auto marshaling|The contract is transparently implemented: no additional code is needed e.g. on the client side, and will handle simple types (strings, numbers, dates, sets and enumerations) and high-level types (objects, collections, records, dynamic arrays) from Delphi 6 up to XE3
|Flexible|Methods accept per-value or per-reference parameters
|Instance lifetime|An implementation class can be:\line - Created on every call,\line - Shared among all calls,\line - Shared for a particular user or group,\line - Stay alive as long as the client-side interface is not released,\line - or as long as an @*authentication@ session exists
|Stateless|Following a standard request/reply pattern
|Signed|The contract is checked to be consistent before any remote execution
|Secure|Every service and/or methods can be enabled or disabled on need
|Safe|Using extended RESTful authentication - see @18@
|Multi-hosted\line (with DMZ)|Services are hosted by default within the main @*ORM@ server, but can have their own process, with a dedicated connection to the ORM core






|





|




|

|

<
|
<
<
<
<
<
<
<
>
|
|
>
>
|
<
<
<
>






|

|
<



>
>
>
>
>

<
<
<
<
<
<
<
<

<
>







 







|
|
|
>
>
|


<
|
<
<
|
|
>
|
|
|
|
>
>

<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>
>
|

|

|
|











|







4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207

4208







4209
4210
4211
4212
4213
4214



4215
4216
4217
4218
4219
4220
4221
4222
4223
4224

4225
4226
4227
4228
4229
4230
4231
4232
4233








4234

4235
4236
4237
4238
4239
4240
4241
4242
....
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269

4270


4271
4272
4273
4274
4275
4276
4277
4278
4279
4280


4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
- By some RESTful @**service@s, implemented in the Server as {\i published methods}, and consumed in the Client via native Delphi methods;
- Defining some {\i service @*contract@s} as standard Delphi {\f1\fs20 interface}, and then run it seamlesly on both client and client sides.
The first of the two last items can be compared to the {\i DataSnap} Client-Server features, also @*JSON@-based, introduced in Delphi 2010. See for instance the following example available on the Internet at @http://docwiki.embarcadero.com/RADStudio/en/Developing_DataSnap_Applications
The second is purely interface-based, so matches the "designed by contract" principle - see @47@ - as implemented by Microsoft's @*WCF@ technology. {\i Windows Communication Foundation} is the unified programming model provided by Microsoft for building service-oriented applications - see @http://msdn.microsoft.com/en-us/library/dd456779. We included most of the nice features made available in WCF in {\i mORMot}, in a KISS manner.
:49  Client-Server services via methods
To implement a service in the {\i Synopse mORMot framework}, the first method is to define @**published method@ Server-side, then use easy functions about JSON or URL-parameters to get the request encoded and decoded as expected, on Client-side.
We'll implement the same example as in the official Embarcadero docwiki page above. Add two numbers. Very useful service, isn't it?
:   Publishing a service on the server
On the server side, we need to customize the standard {\f1\fs20 TSQLRestServer} class definition (more precisely a {\f1\fs20 @*TSQLRestServerDB@} class which includes a {\i SQlite3} engine, or a lighter {\f1\fs20 @*TSQLRestServerFullMemory@} kind of server, which is enough for our purpose), by adding a new {\f1\fs20 published} method:
!type
!  TSQLRestServerTest = class(TSQLRestServerFullMemory)
!   (...)
!!  published
!!    procedure Sum(var aParams: TSQLRestServerCallBackParams);
!  end;
The method name ("Sum") will be used for the URI encoding, and will be called remotely from {\i ModelRoot/Sum} URL. The {\i ModelRoot} is the one defined in the {\f1\fs20 Root} parameter of the {\i model} used by the application.
This method, like all Server-side methods, MUST have the same exact parameter definition as in the {\f1\fs20 TSQLRestServerCallBack} prototype:
!type
!  TSQLRestServerCallBack = procedure(var aParams: TSQLRestServerCallBackParams) of object;
Then we implement this method:
!procedure TSQLRestServerTest.Sum(var aParams: TSQLRestServerCallBackParams);
!var a,b: Extended;

!  if UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin







!    while aParams.Parameters<>nil do begin
!      UrlDecodeExtended(aParams.Parameters,'A=',a);
!      UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
!    end;
!    aParams.Results([a+b]);
!  end else



!    aParams.Error('Missing Parameter');
!end;
The only not obvious part of this code is the parameters marshaling, i.e. how the values are retrieved from the incoming {\f1\fs20 aParams.Parameters} text buffer, then converted into native local variables.
On the Server side, typical implementation steps are therefore:
- Use the {\f1\fs20 UrlDecodeNeedParameters} function to check that all expected parameters were supplied by the caller in {\f1\fs20 aParams.Parameters};
- Call {\f1\fs20 UrlDecodeInteger / UrlDecodeInt64 / UrlDecodeExtended / UrlDecodeValue / UrlDecodeObject} functions (all defined in {\f1\fs20 SynCommons.pas}) to retrieve each individual parameter from standard JSON content;
- Implement the service (here it is just the {\f1\fs20 a+b} expression);
- Then return the result calling {\f1\fs20 aParams.Results()} method or {\f1\fs20 aParams.Error()} in case of any error.
The powerful {\f1\fs20 UrlDecodeObject} function (defined in {\f1\fs20 mORMot.pas}) can be used to un-serialize most class instance from its textual JSON representation ({\f1\fs20 @*TPersistent@, @*TSQLRecord@, TStringList}...).
Using {\f1\fs20 aParams.Results()} will encode the specified values as a JSON object with one {\f1\fs20 "Result"} member, with default mime-type {\f1\fs20 JSON_CONTENT_TYPE}:

$ {"Result":"OneValue"}
or a JSON object containing an array:
$ {"Result":["One","two"]}
Using {\f1\fs20 aParams.Returns()} will let the method return the content in any format, e.g. as a JSON object (via the overloaded {\f1\fs20 aParams.Returns([])} method expecting field name/value pairs), or any content, since the returned mime-type can be defined as a parameter to {\f1\fs20 aParams.Returns()} - it may be useful to specify another mime-type than the default constant {\f1\fs20 JSON_CONTENT_TYPE}, i.e. {\f1\fs20 'application/json; charset=UTF-8'}, and returns plain text, HTML or binary. For instance, you can return directly a value as plain text as such:
!procedure TSQLRestServer.TimeStamp(var aParams: TSQLRestServerCallBackParams);
!begin
!  aParams.Returns(Int64ToUtf8(ServerTimeStamp),HTML_SUCCESS,TEXT_CONTENT_TYPE_HEADER);
!end;
So you can consume these services, implemented Server-Side in fast Delphi code, with any @*AJAX@ application on the client side (if you use HTTP as communication protocol).








An {\i important point} is to remember that the implementation of the callback method {\b must be thread-safe} - as stated by @25@. In fact, the {\f1\fs20 TSQLRestServer.URI} method expects such callbacks to handle the thread-safety on their side. It's perhaps some more work to handle a critical section in the implementation, but, in practice, it's the best way to achieve performance and scalability: the resource locking can be made at the tiniest code level.

:   Defining the client
The client-side is implemented by calling some dedicated methods, and providing the service name ({\f1\fs20 'sum'}) and its associated parameters:
!function Sum(aClient: TSQLRestClientURI; a, b: double): double;
!var err: integer;
!begin
!  val(aClient.CallBackGetResult('sum',['a',a,'b',b]),Result,err);
!end;
You could even implement this method in a dedicated client method - which make sense:
................................................................................
You have to create the server instance, and the corresponding {\f1\fs20 TSQLRestClientURI} (or {\f1\fs20 TMyClient}), with the same database model, just as usual...
On the Client side, you can use the {\f1\fs20 CallBackGetResult} method to call the service from its name and its expected parameters, or create your own caller using the {\f1\fs20 UrlEncode()} function. Note that you can specify most class instance into its JSON representation by using some {\f1\fs20 TObject} into the method arguments:
!function TMyClient.SumMyObject(a, b: TMyObject): double;
!var err: integer;
!begin
!  val(CallBackGetResult('summyobject',['a',a,'b',b]),Result,err);
!end;
This Client-Server protocol uses JSON here, as encoded server-side via {\f1\fs20 aParams.Results()} method, but you can serve any kind of data, binary, HTML, whatever... just by overriding the content type on the server with {\f1\fs20 aParams.Returns()}.
:   Advanced process on server side
On server side, method definition has only one {\f1\fs20 aParams} parameter, which has several members at calling time, and publish all service calling features, including {\i RESTful} URI routing, session handling or low-level HTTP headers (if any).
At first, {\f1\fs20 aParams} may indicate the expected {\f1\fs20 TSQLRecord} ID and {\f1\fs20 TSQLRecord} class, as decoded from {\i RESTful} URI. It means that a service can be related to any table/class of our @*ORM@ framework, so you would be able to create easily any RESTful compatible requests on URI like {\f1\fs20 ModelRoot/TableName/ID/MethodName}. The ID of the corresponding record is decoded from its {\i RESTful} scheme into {\f1\fs20 aParams.ID}, and the table is available in {\f1\fs20 aParams.Table} or {\f1\fs20 aParams.TableIndex} (if you need its index in the associated server Model).
For example, here we return a @*BLOB@ field content as hexadecimal, according to its {\f1\fs20 TableName/Id}:
!procedure TSQLRestServerTest.DataAsHex(var aParams: TSQLRestServerCallBackParams);
!var aData: TSQLRawBlob;
!begin

!  if (self=nil) or (aParams.Table<>TSQLRecordPeople) or (aParams.ID<0) then


!    aParams.Error('Need a valid record and its ID') else
!  if RetrieveBlob(TSQLRecordPeople,aParams.ID,'Data',aData) then
!    aParams.Results([SynCommons.BinToHex(aData)]) else
!    aParams.Error('Impossible to retrieve the Data BLOB field');
!end;
A corresponding client method may be:
!function TSQLRecordPeople.DataAsHex(aClient: TSQLRestClientURI): RawUTF8;
!begin
!  Result := aClient.CallBackGetResult('DataAsHex',[],RecordClass,fID);
!end;


If @*authentication@ - see @18@ - is used, the current session, user and group IDs are available in {\f1\fs20 Session / SessionUser / SessionGroup} fields. If authentication is not available, those fields are meaningless: in fact, {\f1\fs20 aParams.Context.Session} will contain either 0 ({\f1\fs20 CONST_AUTHENTICATION_SESSION_NOT_STARTED}) if any @*session@ is not yet started, or 1 ({\f1\fs20 CONST_AUTHENTICATION_NOT_USED}) if authentication mode is not active. Server-side implementation can use the {\f1\fs20 TSQLRestServer.SessionGetUser} method to retrieve the corresponding user details (note that when using this method, the returned {\f1\fs20 TSQLAuthUser} instance is a local thread-safe copy which shall be freed when done).
In {\f1\fs20 aParams.Call^} member, you can access low-level communication content, i.e. all incoming and outgoing values, including headers and message body. Depending on the transmission protocol used, you can retrieve e.g. HTTP header information. For instance, here is how you can access the caller remote IP address and client application user agent:
! aRemoteIP := FindIniNameValue(pointer(aParams.Call.InHead),'REMOTEIP: ');
! aUserAgent := FindIniNameValue(pointer(aParams.Call.InHead),'USER-AGENT: ');
:   Handling errors
Calling either {\f1\fs20 aParams.Results()}, {\f1\fs20 aParams.Returns()}, {\f1\fs20 aParams.Success()} or {\f1\fs20 aParams.Error()} will specify the HTTP error code (e.g. 200 / "OK" for {\f1\fs20 Results()} and {\f1\fs20 Success()} methods by default, or 400 / "Bad Request" for {\f1\fs20 Error()}) as an {\f1\fs20 integer} value. For instance, here is how a service not returning any content can handle those error codes:
!procedure TSQLRestServer.Batch(var aParams: TSQLRestServerCallBackParams);
!begin
!  if (aParams.Method=mPUT) and RunBatch(nil,nil,aParams) then
!    aParams.Success else
!    aParams.Error;
!end;
In case of an error on the server side, you may call {\f1\fs20 aParams.Error()} method (only the two valid status codes are {\f1\fs20 200} and {\f1\fs20 201}).
The {\f1\fs20 aParams.Error()} method has an optional parameter to specify a custom error message in plain English, which will be returned to the client in case of an invalid status code. If no custom text is specified, the framework will return the corresponding generic HTTP status text (e.g. {\f1\fs20 "Bad Request"} for default status code {\f1\fs20 HTML_BADREQUEST} = 400).
In this case, the client will receive a corresponding serialized JSON error object, e.g. for {\f1\fs20 aParams.Error('Missing Parameter',HTML_NOTFOUND)}:
${
$ "ErrorCode":404,
$ "ErrorText":"Missing Parameter"
$}
If called from an AJAX client, or a browser, this content should be easy to interpret.
Note that the framework core will catch any exception during the method execution, and will return a {\f1\fs20 "Internal Server Error" / HTML_SERVERERROR} = 500 error code with the associated textual exception details.
:   Benefits and limitations of this implementation
Method-based services allow fast and direct access to all {\f1\fs20 mORMot} Client-Server {\f1\fs20 RESTful} features, over all usual protocols of our framework: @*HTTP@/1.1, Named Pipe, Windows GDI messages, direct in-memory/in-process access.
The {\i mORMot} implementation of method-based services gives full access to the lowest-level of the framework core, so it has some advantages:
- It can be tuned to fit any purpose (such as retrieving or returning some HTML or binary data, or modifying the HTTP headers on the fly);
- It is integrated into the @*REST@ful URI model, so it can be related to any table/class of our @*ORM@ framework (like {\f1\fs20 DataAsHex} service above), or it can handle any remote query (e.g. any @*AJAX@ or @*SOAP@ requests);
- It has a very low performance overhead, so can be used to reduce server workload for some common tasks.
Note that due to this implementation pattern, the {\i mORMot} service implementation is very fast, and not sensitive to the "Hash collision attack" security issue, as reported with {\i Apache} - see @http://blog.synopse.info/post/2011/12/30/Hash-collision-attack for details.
:63  Interface based services
In real world, especially when your application relies heavily on services, the @49@ implementation pattern has some drawbacks:
- Most content marshaling is to be done by hand, so may introduce implementation issues;
- Client and server side code does not have the same implementation pattern, so you will have to code explicitly data marshaling twice, for both client and server ({\i DataSnap} and WCF both suffer from a similar issue, by which client classes shall be coded separately, most time generated by a Wizard);
- The services do not have any hierarchy, and are listed as a plain list, which is not very convenient;
- It is difficult to synchronize several service calls within a single context, e.g. when a workflow is to be handled during the application process (you have to code some kind of state machine on both sides, and use all session handling by hand);
- @*Security@ is handled globally for the user, or should be checked by hand in the implementation method (using the {\f1\fs20 aParams} members).
You can get rid of those limitations with the interface-based service implementation of {\i mORMot}. For a detailed introduction and best practice guide to @*SOA@, you can consult this classic article: @http://www.ibm.com/developerworks/webservices/library/ws-soa-design1
According to this document, all expected SOA features are now available in the current implementation of the {\i mORMot} framework (including service catalog aka "broker").
:   Implemented features
Here are the key features of the current implementation of services using interfaces in the {\i Synopse mORMot framework}:
|%25%75
|\b Feature|Remarks\b0
|Service Orientation|Allow loosely-coupled relationship
|Design by contract|Data Contracts are defined in Delphi code as standard {\f1\fs20 interface} custom types
|Factory driven|Get an implementation instance from a given interface
|Server factory|You can get an implementation on the server side
|Client factory|You can get a "fake" implementation on the client side, remotely calling the server to execute the process
|Auto marshaling|The contract is transparently implemented: no additional code is needed e.g. on the client side, and will handle simple types (strings, numbers, dates, sets and enumerations) and high-level types (objects, collections, records, dynamic arrays, variants) from Delphi 6 up to XE3
|Flexible|Methods accept per-value or per-reference parameters
|Instance lifetime|An implementation class can be:\line - Created on every call,\line - Shared among all calls,\line - Shared for a particular user or group,\line - Stay alive as long as the client-side interface is not released,\line - or as long as an @*authentication@ session exists
|Stateless|Following a standard request/reply pattern
|Signed|The contract is checked to be consistent before any remote execution
|Secure|Every service and/or methods can be enabled or disabled on need
|Safe|Using extended RESTful authentication - see @18@
|Multi-hosted\line (with DMZ)|Services are hosted by default within the main @*ORM@ server, but can have their own process, with a dedicated connection to the ORM core

Changes to SQLite3/Samples/06 - Remote JSON REST Service/Project06Server.dpr.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34



35
36
37
38
39
40
41
  mORMot,
  SysUtils;

type
  // TSQLRestServerFullMemory kind of server is light and enough for our purpose
  TServiceServer = class(TSQLRestServerFullMemory)
  published
    function Sum(var aParams: TSQLRestServerCallBackParams): Integer;
  end;


{ TServiceServer }

function TServiceServer.Sum(var aParams: TSQLRestServerCallBackParams): Integer;
var a,b: Extended;
begin
  if not UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin
    result := 404; // invalid Request
    aParams.ErrorMsg^ := 'Missing Parameter'; // custom error message
    exit;
  end;
  while aParams.Parameters<>nil do begin
    UrlDecodeExtended(aParams.Parameters,'A=',a);
    UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
  end;
  aParams.Resp := JSONEncodeResult([a+b]);
  // same as : aResp := JSONEncode(['result',a+b],TempMemoryStream);
  result := 200; // success



end;

var
  aModel: TSQLModel;
begin
  aModel := TSQLModel.Create([],'service');
  try






|





|


|
<
<
<
<
|
|
|
|
<
<
<
>
>
>







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23




24
25
26
27



28
29
30
31
32
33
34
35
36
37
  mORMot,
  SysUtils;

type
  // TSQLRestServerFullMemory kind of server is light and enough for our purpose
  TServiceServer = class(TSQLRestServerFullMemory)
  published
    procedure Sum(var aParams: TSQLRestServerCallBackParams);
  end;


{ TServiceServer }

procedure TServiceServer.Sum(var aParams: TSQLRestServerCallBackParams);
var a,b: Extended;
begin
  if UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin




    while aParams.Parameters<>nil do begin
      UrlDecodeExtended(aParams.Parameters,'A=',a);
      UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
    end;



    aParams.Results([a+b]);
  end else
    aParams.Error('Missing Parameter');
end;

var
  aModel: TSQLModel;
begin
  aModel := TSQLModel.Create([],'service');
  try

Changes to SQLite3/Samples/MainDemo/FileServer.pas.

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
..
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
    function OnDatabaseUpdateEvent(Sender: TSQLRestServer;
      Event: TSQLEvent; aTable: TSQLRecordClass; aID: integer): boolean;
  published
    /// a RESTful service used from the client side to add an event
  	// to the TSQLAuditTrail table
	  // - an optional database record can be specified in order to be
  	// associated with the event
    function Event(var aParams: TSQLRestServerCallBackParams): Integer;
  end;


implementation


{ TFileServer }
................................................................................
    FreeAndNil(fTempAuditTrail);
    FreeAndNil(Server);
  finally
    inherited;
  end;
end;

function TFileServer.Event(var aParams: TSQLRestServerCallBackParams): Integer;
var E: integer;
begin
  if UrlDecodeInteger(aParams.Parameters,'EVENT=',E) and
     (E>0) and (E<=ord(High(TFileEvent))) then begin
    AddAuditTrail(TFileEvent(E),'',RecordReference(Model,aParams.Table,aParams.Context.ID));
    result := HTML_SUCCESS;
  end else
    result := HTML_BADREQUEST;
end;

function TFileServer.OnDatabaseUpdateEvent(Sender: TSQLRestServer;
  Event: TSQLEvent; aTable: TSQLRecordClass; aID: integer): boolean;
const EVENT_FROM_SQLEVENT: array[TSQLEvent] of TFileEvent = (
  feRecordCreated, feRecordModified, feRecordDeleted);
begin
  result := true;
  if aTable.InheritsFrom(TSQLFile) then
    AddAuditTrail(EVENT_FROM_SQLEVENT[Event], '', Model.RecordReference(aTable,aID));
end;

end.






|







 







|



|
|
|

|













35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
..
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
    function OnDatabaseUpdateEvent(Sender: TSQLRestServer;
      Event: TSQLEvent; aTable: TSQLRecordClass; aID: integer): boolean;
  published
    /// a RESTful service used from the client side to add an event
  	// to the TSQLAuditTrail table
	  // - an optional database record can be specified in order to be
  	// associated with the event
    procedure Event(var aParams: TSQLRestServerCallBackParams);
  end;


implementation


{ TFileServer }
................................................................................
    FreeAndNil(fTempAuditTrail);
    FreeAndNil(Server);
  finally
    inherited;
  end;
end;

procedure TFileServer.Event(var aParams: TSQLRestServerCallBackParams);
var E: integer;
begin
  if UrlDecodeInteger(aParams.Parameters,'EVENT=',E) and
     (E>ord(feUnknownState)) and (E<=ord(High(TFileEvent))) then begin
    AddAuditTrail(TFileEvent(E),'',RecordReference(Model,aParams.Table,aParams.ID));
    aParams.Success;
  end else
    aParams.Error;
end;

function TFileServer.OnDatabaseUpdateEvent(Sender: TSQLRestServer;
  Event: TSQLEvent; aTable: TSQLRecordClass; aID: integer): boolean;
const EVENT_FROM_SQLEVENT: array[TSQLEvent] of TFileEvent = (
  feRecordCreated, feRecordModified, feRecordDeleted);
begin
  result := true;
  if aTable.InheritsFrom(TSQLFile) then
    AddAuditTrail(EVENT_FROM_SQLEVENT[Event], '', Model.RecordReference(aTable,aID));
end;

end.

Changes to SQLite3/mORMot.pas.

660
661
662
663
664
665
666




667
668
669
670
671
672
673
674
675
676
677
678


679
680
681
682
683
684
685
....
3026
3027
3028
3029
3030
3031
3032






















3033
3034
3035
3036
3037
3038
3039
....
3307
3308
3309
3310
3311
3312
3313

3314
3315
3316
3317
3318
3319
3320

3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341







3342
3343



3344
3345
3346
3347

3348
3349
3350





3351
3352
3353
3354
3355
3356
3357




3358
3359
3360
3361




3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380































































3381
3382

3383
3384
3385
3386
3387
3388
3389
....
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443

3444
3445



3446
3447
3448
3449
3450
3451
3452
....
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
....
6146
6147
6148
6149
6150
6151
6152
6153
6154
6155
6156
6157
6158
6159
6160
6161
6162
....
6654
6655
6656
6657
6658
6659
6660
6661
6662
6663
6664
6665
6666
6667
6668
....
7403
7404
7405
7406
7407
7408
7409
7410
7411
7412
7413
7414
7415
7416
7417
7418
....
7517
7518
7519
7520
7521
7522
7523


7524
7525
7526
7527
7528
7529
7530
....
7557
7558
7559
7560
7561
7562
7563






7564
7565
7566
7567
7568
7569
7570
....
7645
7646
7647
7648
7649
7650
7651
7652
7653
7654
7655
7656
7657
7658
7659
7660
7661
7662
7663
7664
7665
7666
7667
7668
7669
7670
7671
7672
7673
7674
7675
7676
7677
7678
7679
7680
7681
7682
7683
....
7785
7786
7787
7788
7789
7790
7791
7792
7793
7794
7795
7796
7797
7798
7799
7800
....
7885
7886
7887
7888
7889
7890
7891
7892
7893
7894
7895
7896
7897
7898
7899
7900
7901
7902
7903
7904
7905
7906
7907
7908
7909
7910
7911
7912
7913
7914
7915
7916
7917
7918
7919
7920
7921
7922
7923
7924
7925
7926
7927
7928
7929
7930
7931
7932
7933
7934
7935
7936
7937
7938
7939
7940
7941
7942
7943
....
8032
8033
8034
8035
8036
8037
8038
8039
8040
8041
8042
8043
8044
8045
8046
....
8069
8070
8071
8072
8073
8074
8075
8076
8077
8078
8079
8080
8081
8082
8083
8084
8085
8086
8087
8088
8089
8090
8091
8092
8093
8094
8095
8096
8097
8098
8099
8100
8101
8102
8103
8104
8105
8106
8107
....
9141
9142
9143
9144
9145
9146
9147
9148
9149
9150
9151
9152
9153
9154
9155
....
9171
9172
9173
9174
9175
9176
9177
9178
9179
9180
9181
9182
9183
9184
9185
9186
....
9774
9775
9776
9777
9778
9779
9780
9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
9791
9792
9793
9794
9795
9796
9797
9798
9799
9800
9801
9802
9803
9804
....
9895
9896
9897
9898
9899
9900
9901
9902
9903
9904
9905
9906
9907
9908
9909
.....
19273
19274
19275
19276
19277
19278
19279
19280
19281
19282
19283
19284
19285
19286
19287
.....
20409
20410
20411
20412
20413
20414
20415
20416
20417
20418
20419
20420
20421
20422
20423
20424
20425
20426
20427
20428
20429
20430
20431
20432
20433
20434
20435
20436
20437
20438
20439
20440
20441
20442
20443
20444
20445
20446
20447
20448
20449
20450


20451



20452
20453

20454
20455
20456
20457
20458
20459
20460
20461
20462
.....
20523
20524
20525
20526
20527
20528
20529
20530
20531
20532
20533
20534

20535
20536


20537
20538

20539
20540
20541
20542
20543
20544
20545
20546
20547
20548
20549
20550
20551
20552
20553
20554
20555
20556

20557
20558




20559
20560

20561
20562
20563
20564
20565
20566
20567
20568
20569
20570
.....
21121
21122
21123
21124
21125
21126
21127
21128
21129





















































21130
21131
21132
21133
21134
21135

21136
21137
21138
21139
21140
21141
21142
21143
21144
21145
21146
21147
21148
21149
21150
21151
21152
21153
21154
21155
21156
21157

21158
21159
21160
21161


21162
21163

















21164
21165
21166
21167
21168



















21169

21170
21171
21172
21173
21174
21175
21176
21177
21178
21179
21180
21181
21182

21183
21184
21185
21186
21187
21188
21189

21190


21191
21192
21193
21194
21195
21196
21197
.....
21220
21221
21222
21223
21224
21225
21226
21227
21228
21229
21230
21231
21232
21233
21234
21235
21236
21237
21238
21239
21240
21241
21242
21243
21244
21245
21246
21247
21248
21249
21250
21251


21252
21253
21254
21255
21256
21257
21258



21259
21260
21261
21262
21263
21264
21265
21266
21267

21268
21269
21270
21271
21272
21273
21274
21275
21276
21277
21278
21279
21280
21281
21282
21283
21284
21285
21286
21287
21288
21289
21290
21291
21292
21293
21294
21295
21296
21297
21298
21299
21300
21301
21302
21303
21304
21305
21306
21307
21308
21309
21310
21311
21312
21313
21314
21315
21316
21317
21318
21319
21320
21321
21322
21323
21324
21325
21326
21327
21328
21329
21330
21331
21332
21333
21334
21335
21336
21337
21338
21339
21340
21341
21342
21343
21344

21345
21346
21347
21348
21349
21350
21351
.....
21402
21403
21404
21405
21406
21407
21408
21409

21410

21411
21412
21413
21414
21415
21416
21417
21418
21419
21420
21421
21422
21423
21424

21425

21426
21427
21428
21429
21430
21431
21432
.....
21456
21457
21458
21459
21460
21461
21462
21463
21464
21465
21466
21467
21468
21469
21470
21471
21472
21473
21474
21475
21476
21477
21478
21479
21480
21481
21482
21483
21484
21485
21486
21487
21488
21489
21490
21491
21492
21493
21494
21495
21496
21497
21498
21499
21500
.....
21503
21504
21505
21506
21507
21508
21509
21510
21511
21512
21513
21514
21515
21516
21517
21518
21519
21520
21521
21522
21523
21524
21525
21526
21527
21528
21529
21530
21531
21532
21533
21534
21535
21536
21537
21538
21539
21540
21541
21542
21543
21544
21545
21546
21547
21548
21549
21550
21551
21552
21553
21554
21555
21556
21557
21558
21559
21560
21561


21562
21563
21564
21565
21566
21567
21568
21569
21570
21571
21572
21573

21574
21575
21576
21577
21578
21579
21580
21581
21582
21583
21584
21585
21586
21587
21588
21589
21590
21591
21592
21593
21594
21595
21596
21597
21598
21599
21600
21601
21602
21603
21604
21605
.....
21624
21625
21626
21627
21628
21629
21630
21631
21632
21633
21634
21635
21636
21637
21638
21639
21640
21641
21642
21643
21644
21645
21646
21647
21648
21649
21650
21651
21652
21653
21654
21655
21656
21657
21658
21659
21660
21661
21662
21663
21664
21665
21666
21667
21668
21669
21670
21671
21672
21673
21674
21675
21676
21677
21678
21679
21680
21681
21682
21683
21684
21685
21686
21687
21688
21689
21690
21691
21692
21693
21694
21695
21696
21697
21698
21699
21700
21701
21702
21703
21704
21705
21706
21707
21708
21709
21710
21711
21712
21713
21714
21715
21716
21717
21718
21719
21720
21721
21722
21723
21724
21725
21726
21727
21728
21729
21730


21731
21732
21733
21734
21735
21736
21737
21738
21739
21740
21741
21742
21743

21744

21745
21746
21747
21748
21749
21750
21751
21752
21753
21754
21755
21756
21757
21758
21759
.....
21774
21775
21776
21777
21778
21779
21780
21781
21782
21783
21784
21785
21786
21787
21788
21789
21790
21791
21792
21793
21794
21795
21796
21797
21798
21799
21800
21801
21802
21803
21804
21805
21806
21807
21808
21809
21810
21811
21812
21813
21814
21815
21816
21817
21818
21819
21820
21821
21822
21823
21824
21825
21826
21827
21828
21829
21830
21831
21832
21833
21834
21835
21836
21837
21838
21839
21840
21841
21842
21843
21844
21845
21846
21847
21848
21849
21850
21851
21852
21853
21854
21855
21856
21857


21858
21859
21860
21861
21862
21863
21864
21865
21866
21867
21868
21869
21870
21871
21872
21873
21874
21875
21876
21877
21878
21879
21880
21881
21882
21883
21884
21885
21886
21887
21888
21889
21890
21891
21892
21893
21894
21895
21896
21897
21898
21899
21900
21901
21902
21903
21904
21905


21906
21907
21908
21909
21910
21911
21912
21913
21914
21915
21916
21917
21918
21919
21920
21921
21922
21923
21924
21925
21926
21927
21928
21929
21930
21931
21932
21933
21934
21935
21936
21937
21938
21939
21940
21941
21942
21943
21944
21945
21946
21947
21948
21949
21950
21951
21952
21953
21954
21955
21956
21957
21958
21959
21960
21961
21962
21963
21964
21965
21966
21967



21968
21969
21970

21971

21972
21973
21974
21975
21976
21977
21978
21979
21980
21981
21982
21983
21984
21985
21986
21987
21988
21989
21990
21991
21992
21993
21994
21995
21996
21997
21998
21999
22000
22001
22002
22003
22004
22005
22006
22007
22008
22009
22010
22011
22012
22013
22014
22015
22016
22017
22018
22019
22020
22021
22022
22023
22024
22025
22026
22027
22028
22029
22030
22031
22032
22033




22034
22035
22036
22037
22038
22039
22040
22041
.....
22051
22052
22053
22054
22055
22056
22057
22058
22059

22060

22061
22062
22063
22064
22065
22066
22067
22068
22069
22070
22071
22072
22073
22074
22075
22076
22077
22078
22079
22080
22081
22082
22083
22084
22085
.....
22144
22145
22146
22147
22148
22149
22150
22151
22152
22153
22154
22155
22156
22157
22158
22159
22160
.....
22173
22174
22175
22176
22177
22178
22179
22180
22181
22182
22183
22184
22185
22186
22187
22188
22189
22190
22191
22192
22193
22194
22195
22196
22197
22198
22199
22200
22201
22202
22203
22204
22205
22206
22207
22208
22209
22210
.....
22211
22212
22213
22214
22215
22216
22217
22218
22219
22220
22221
22222
22223
22224
22225
22226
.....
22601
22602
22603
22604
22605
22606
22607
22608
22609
22610
22611
22612
22613
22614
22615
22616
22617
22618
22619
22620
22621
.....
22623
22624
22625
22626
22627
22628
22629
22630
22631
22632
22633
22634
22635

22636
22637
22638
22639
22640
22641
22642
22643
22644
22645
22646
22647
.....
22656
22657
22658
22659
22660
22661
22662

22663
22664
22665
22666
22667
22668
22669
.....
22723
22724
22725
22726
22727
22728
22729
22730
22731
22732
22733
22734
22735
22736
22737
.....
29257
29258
29259
29260
29261
29262
29263
29264

29265
29266





29267
29268
29269
29270
29271
29272
29273
29274
29275
29276
29277
29278
29279
.....
29281
29282
29283
29284
29285
29286
29287
29288
29289
29290
29291
29292
29293
29294
29295

29296
29297

29298
29299
29300
29301
29302
29303
29304
29305
29306
29307
29308
29309
29310
29311
29312
29313
29314
29315
29316
29317
29318
29319
29320
29321
29322
29323
29324
29325
29326
29327
29328
29329
29330
29331
29332
29333
29334
29335
29336
29337
29338
29339
29340
29341

29342
29343
29344
29345
29346
29347
29348
      indeed no implementation requirement to force a specific class type
    - added aUseBatchMode optional parameter to TSQLRecordMany.ManyDelete() method
    - now JSON parser will handle #1..' ' chars as whitespace (not only ' ')
    - now huge service JSON response is truncated (to default 20 KB) in logs

  Version 1.18
    - renamed SQLite3Commons.pas to mORMot.pas




    - deep code refactoring, introducing TSQLPropInfo* classes in order to
      decouple the ORM definitions from the RTTI - will allow definition of
      any class members, even if there is no RTTI generated or via custom
      properties attributes or a fluent interface
    - introduced more readable "stored AS_UNIQUE" published property definition
      in TSQLRecord (via the explicit AS_UNIQUE=false constant)
    - introducing TInterfaceStub and TInterfaceMock classes to define
      high-performance interface stubbing and mocking via a fluent interface
    - integrated Windows Authentication to the mORMot Client-Server layer: in
      order to enable it, define a SSPIAUTH conditional and call
      TSQLRestClientURI.SetUser() with an empty user name, and ensure that
      TSQLAuthUser.LoginName contains a matching 'DomainName\UserName' value


    - added process of Variant and WideString types in TSQLRecord properties
    - added JSON serialization of Variant and WideString types in JSONToObject()
      / ObjectToJSON() functions and WriteObject method
    - TSQLRestClientURINamedPipe and TSQLRestClientURIMessage are now thread-safe
      (i.e. protected by a system mutex) therefore can be called from a
      multi-threaded process, e.g. TSQLRestServer instances as proxies
    - modified named pipe client side to avoid unexpected file not found errors
................................................................................

  /// maximum handled dimension for TSQLRecordRTree
  // - this value is the one used by SQLite3 R-Tree virtual table
  RTREE_MAX_DIMENSION = 5;

  /// used as "stored AS_UNIQUE" published property definition in TSQLRecord 
  AS_UNIQUE = false;























type
  /// generic parent class of all custom Exception types of this unit
  EORMException = class(ESynException);

  /// exception raised in case of wrong Model definition
  EModelException = class(EORMException);
................................................................................
    // - will return the specified associated TSynFilterOrValidate instance
    function AddFilterOrValidate(const aFieldName: RawUTF8;
      aFilter: TSynFilterOrValidate): TSynFilterOrValidate; overload;
      {$ifdef HASINLINE}inline;{$endif}
  end;

  TAuthSession = class;


  /// the available THTTP methods transmitted between client and server
  TSQLURIMethod = (mNone, mGET, mPOST, mPUT, mDELETE, mBEGIN, mEND,
    mABORT, mLOCK, mUNLOCK, mSTATE);

  /// used to store the current execution context of a remote request
  // - if RESTful authentication is enabled, it will be filled as expected

  TSQLRestServerSessionContext = record
    /// the corresponding session TAuthSession.IDCardinal value
    // - equals 0 (CONST_AUTHENTICATION_SESSION_NOT_STARTED) if the session
    // is not started yet - i.e. if still in handshaking phase
    // - equals 1 (CONST_AUTHENTICATION_NOT_USED) if authentication mode
    // is not enabled - i.e. if TSQLRest.HandleAuthentication = FALSE
    Session: cardinal;
    /// the corresponding TAuthSession.User.ID value
    // - is undefined if Session is 0 or 1 (no authentication running)
    User: integer;
    /// the corresponding TAuthSession.User.GroupRights.ID value
    // - is undefined if Session is 0 or 1 (no authentication running)
    Group: integer;
    /// the associated TSQLRecord.ID, as decoded from URI scheme
    // - this property will be set from incoming URI, even if RESTful
    // authentication is not enabled
    ID: integer;
    /// the used Client-Server method (matching the corresponding HTTP Verb)
    // - this property will be set from incoming URI, even if RESTful
    // authentication is not enabled
    Method: TSQLURIMethod;







  end;




  /// store all parameters for a TSQLRestServerCallBack event handler
  // - having a dedicated record avoid changing the implementation methods
  // signature if the framework add some parameters to this structure
  // - see TSQLRestServerCallBack for general code use

  TSQLRestServerCallBackParams = record
    /// the corresponding authentication session ID
    // - Context.Session=1 (CONST_AUTHENTICATION_NOT_USED) if authentication





    // mode is not enabled
    // - Context.Session=0 (CONST_AUTHENTICATION_SESSION_NOT_STARTED) if the
    // session not started yet
    // - if authentication is enabled, Context.User and Context.Group can be
    // checked to perform security during service execution
    // - Context.ID is the record ID as specified at the URI level (if any)
    Context: TSQLRestServerSessionContext;




    /// the Table as specified at the URI level (if any)
    Table: TSQLRecordClass;
    /// the index in the Model of the Table specified at the URI level (if any)
    TableIndex: integer;




    /// the index of the callback published method within the internal class list
    MethodIndex: integer;
    /// URI inlined parameters
    // - use UrlDecodeValue*() functions to retrieve the values
    Parameters: PUTF8Char;
    /// the URI address, just before parameters
    // - can be either the table name (in RESTful protocol), or a service name
    URI: RawUTF8;
    /// the message body (e.g. HTTP body) as send by the client
    SentData: RawUTF8;
    /// the response to be sent back to the client
    Resp: RawUTF8;
    /// a pointer to the Header to be sent back to the client
    // - you can use this value e.g. to change the result mime-type
    Head: PRawUTF8;
    /// a pointer to an optional error message to be sent back to the client
    // - to be used to specify the numerical error message returned as
    // integer result of the TSQLRestServerCallBack function
    ErrorMsg: PRawUTF8;































































  end;
  

  (*/ method prototype which must be used to implement the Server-Side
     ModelRoot/[TableName/ID/]MethodName RESTful GET/PUT request of the Framework
   - this mechanism is able to handle some custom Client/Server request, similar
     to the DataSnap technology, but in a KISS way; it's fully integrated in the
     Client/Server architecture of our framework
   - just add a published method of this type to any TSQLRestServer descendant
   - when TSQLRestServer.URI receive a request for ModelRoot/MethodName
................................................................................
     retrieved with a loop like this:
     !  if not UrlDecodeNeedParameters(aParams.Parameters,'SORT,COUNT') then
     !    exit;
     !  while aParams.Parameters<>nil do begin
     !    UrlDecodeValue(aParams.Parameters,'SORT=',aSortString);
     !    UrlDecodeValueInteger(aParams.Parameters,'COUNT=',aCountInteger,@aParams.Parameters);
     !  end;
   - aParams.SentData is set with incoming data from the GET/PUT method
   - aParams.Context will identify to the authentication session of the remote client
     (CONST_AUTHENTICATION_NOT_USED=1 if authentication mode is not enabled or
     CONST_AUTHENTICATION_SESSION_NOT_STARTED=0 if the session not started yet) -
     code may use SessionGetUser() protected method to retrieve the user details
   - aParams.Context.Method will indicate the used HTTP verb (e.g. GET/POST/PUT..)
   - implementation must return the HTTP error code (e.g. 200/HTML_SUCCESS) as
     an integer value, and any response in aParams.Resp as a JSON object by
     default (using e.g. TSQLRestServer.JSONEncodeResult), since default mime-type
     is JSON_CONTENT_TYPE:
     $ {"result":"OneValue"}
     or a JSON object containing an array:
     $ {"result":["One","two"]}
   - implementation can return an optional HTTP header (useful to set the response
     mime-type - see e.g. the TEXT_CONTENT_TYPE_HEADER constant) in aParams.Head^
   - implementation can return an optional error text in aParams.ErrorMsg^ in
     order to specify the HTTP error code with plain text (which will be sent
     as JSON error object into the client)
   - a typical implementation may be:
     ! function TSQLRestServerTest.Sum(var aParams: TSQLRestServerCallBackParams): Integer;
     ! var a,b: Extended;
     ! begin
     !   if not UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin
     !     result := HTML_NOTFOUND; // invalid Request
     !     aParams.ErrorMsg^ := 'Missing Parameter';
     !     exit;
     !   end;
     !   while Params.Parameters<>nil do begin
     !     UrlDecodeExtended(aParams.Parameters,'A=',a);
     !     UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
     !   end;
     !   aParams.Resp := JSONEncodeResult([a+b]);

     !   // same as : aParams.Resp := JSONEncode(['result',a+b]);
     !   result := HTML_SUCCESS; 



     ! end;
   - Client-Side can be implemented as you wish. By convention, it could be
     appropriate to define in either TSQLRestServer (if to be called as
     ModelRoot/MethodName), either TSQLRecord (if to be called as
     ModelRoot/TableName/MethodName[/ID]) a custom public or protected method,
     calling TSQLRestClientURI.URL with the appropriate parameters, and named
     (by convention) as MethodName; TSQLRestClientURI has dedicated methods
................................................................................
     like CallBackGetResult, CallBackGet, and CallBackPut; see also
     TSQLModel.getURICallBack and JSONDecode function
     ! function TSQLRecordPeople.Sum(aClient: TSQLRestClientURI; a, b: double): double;
     ! var err: integer;
     ! begin
     !   val(aClient.CallBackGetResult('sum',['a',a,'b',b]),result,err);
     ! end; *)
  TSQLRestServerCallBack = function(var aParams: TSQLRestServerCallBackParams): Integer of object;

  /// the possible options for handling table names
  TSQLCheckTableName = (ctnNoCheck,ctnMustExist,ctnTrimExisting);

  /// internal data used by TSQLRecord.FillPrepare()/FillPrepareMany() methods
  // - using a dedicated class will reduce memory usage for each TSQLRecord
  // instance (which won't need these properties most of the time)
................................................................................
    // - on success, aResp shall contain a serialized JSON object with one
    // nested result property, which may be a JSON array, containing the
    // all "var" or "out" parameters values, and then the method main result -
    // for instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    function ExecuteMethod(const aContext: TSQLRestServerSessionContext;
      aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char;
      var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
    /// this method will create an implementation instance
    function CreateInstance: TInterfacedObject;
  public
    /// initialize the service provider on the server side
    // - expect an direct server-side implementation class (inheriting from
    // TInterfacedClass or from TInterfacedObjectWithCustomCreate if you need
    // an overriden constructor)
................................................................................
    function InternalDelete(Table: TSQLRecordClass; const SQLWhere: RawUTF8;
      var IDs: TIntegerDynArray): boolean; 
    /// wait for the transaction critical section to be acquired
    // - used to implement a thread-safe and transaction-safe write to the DB
    // - returns FALSE in case of time out (see AcquireWriteTimeOut property)
    // - returns TRUE if it's safe to write to the DB - in this case, you must
    // call ReleaseWrite when done to release the fTransactionCriticalSession
    function AcquireWrite(const aContext: TSQLRestServerSessionContext): Boolean;
    /// release the fTransactionCriticalSession
    procedure ReleaseWrite;
    /// retrieve the server time stamp
    // - default implementation will use fServerTimeStampOffset to compute
    // the value from PC time (i.e. Now+fServerTimeStampOffset as TTimeLog)
    // - inherited classes may override this method, or set the appropriate
    // value in fServerTimeStampOffset protected field
................................................................................
    // - use the TSQLAuthGroup.AccessRights CSV format
    function ToString: RawUTF8;
    /// unserialize the content from TEXT
    // - use the TSQLAuthGroup.AccessRights CSV format
    procedure FromString(P: PUTF8Char);
  end;

  PSQLAccessRights = ^TSQLAccessRights;

  TSQLRestServerStatic = class;
  TSQLRestServerStaticClass = class of TSQLRestServerStatic;
  TSQLRestServerStaticInMemory = class;
  TSQLVirtualTableModule = class;


  {/ table containing the available user access rights for authentication
................................................................................
    fLastAccess: cardinal;
    fID: RawUTF8;
    fIDCardinal: cardinal;
    fTimeOut: cardinal;
    fAccessRights: TSQLAccessRights;
    fPrivateKey: RawUTF8;
    fPrivateSalt: RawUTF8;


    fPrivateSaltHash: Cardinal;
    fLastTimeStamp: Cardinal;
  public
    /// initialize a session instance with the supplied TSQLAuthUser instance
    // - this aUser instance will be handled by the class until Destroy
    // - raise an exception on any error
    // - on success, will also retrieve the aUser.Data BLOB field content
................................................................................
    /// the number of millisedons a session is kept alive
    // - extracted from User.TSQLAuthGroup.SessionTimeout
    // - allow direct comparison with GetTickCount API call
    property Timeout: cardinal read fTimeOut;
    /// the hexadecimal private key as returned to the connected client
    // as 'SessionID+PrivateKey'
    property PrivateKey: RawUTF8 read fPrivateKey;






  end;

  { we need the RTTI information to be compiled for the published methods
    of this TSQLRestServer class and its children (like TPersistent), to
    enable Server-Side ModelRoot/[TableName/[ID/]]MethodName requests
      -> see TSQLRestServerCallBack }

................................................................................
    /// this method is overriden for setting the NoAJAXJSON field
    // of all associated TSQLRestServerStatic servers
    procedure SetNoAJAXJSON(const Value: boolean); virtual;
    /// search for the corresponding TSQLRestServerCallBack in its published methods,
    // then launch it
    // - the aParams parameters will be used to set a default header for the callback
    // - return TRUE if the method was found and run, FALSE if method was not found
    function LaunchCallBack(var aParams: TSQLRestServerCallBackParams;
      var aResp: RawUTF8; var aResult: Cardinal): boolean;
    /// try to call a Service from a given URI
    // - this method will call any interface-based service previously registered
    // via ServerRegister()
    // - returns TRUE if the supplied method was a service name, and an error
    // code is returned in aResult (with an optional message in aErrorMsg)
    // - is in fact used internaly by the URI method: you are not likely to call
    // this method, but should rather call e.g. Services['Calculator'].Get(I)
    // to retrieve a working service interface to work with
    function LaunchService(const aParams: TSQLRestServerCallBackParams;
      var aResp: RawUTF8; var aResult: cardinal): boolean;
    /// execute a BATCH sequence
    // - expect input as generated by TSQLRestClientURI.Batch*() methods:
    // & '{"Table":["cmd":values,...]}'
    // or, in a table-independent way:
    // & '["cmd@table":values,...]'
    // - returns an array of integers: '[200,200,...]'
    function RunBatch(aStatic: TSQLRestServerStatic; aTable: TSQLRecordClass;
      Sent: PUTF8Char; var Resp, ErrorMsg: RawUTF8): boolean;
    /// fill the supplied context from the supplied aContext.Session ID
    // - returns nil if not found, or fill aContext.User/Group values if matchs
    // - this method will also check for outdated sessions, and delete them
    // - this method is not thread-safe: caller should use fSessionCriticalSection
    function SessionAccess(var aContext: TSQLRestServerSessionContext): TAuthSession;
    /// delete a session from its index in fSessions[]
    // - will perform any needed clean-up, and log the event
    // - this method is not thread-safe: caller should use fSessionCriticalSection
    procedure SessionDelete(aSessionIndex: integer);
    /// returns a copy of the user associated to a session ID
    // - returns nil if the session does not exist (e.g. if authentication is
    // disabled)
................................................................................

{$ifdef MSWINDOWS}
    /// declare the server on the local machine as a Named Pipe: allows
    // TSQLRestClientURINamedPipe local or remote client connection
    // - ServerApplicationName ('DBSERVER' e.g.) will be used to create a named
    // pipe server identifier, it is of UnicodeString type since Delphi 2009
    // (use of Unicode FileOpen() version)
    // - this server identifier is appended to '\\.\pipe\Sqlite3_' to obtain
    // the full pipe name to initiate ('\\.\pipe\Sqlite3_DBSERVER' e.g.)
    // - this server identifier may also contain a fully qualified path
    // ('\\.\pipe\ApplicationName' e.g.)
    // - allows only one ExportServer*() by running process
    // - returns true on success, false otherwize (ServerApplicationName already used?)
    function ExportServerNamedPipe(const ServerApplicationName: TFileName): boolean;
    /// end any currently initialized named pipe server
    function CloseServerNamedPipe: boolean;
................................................................................
    // if you call it from the main thread, it may fail to release resources
    // - it is set e.g. by TSQLite3HttpServer to be called from HTTP threads,
    // or by TSQLRestServerNamedPipeResponse for named-pipe server cleaning
    procedure EndCurrentThread(Sender: TObject); virtual;

    /// implement a generic local, piped or HTTP/1.1 provider
    // - this is the main entry point of the server, from the client side
    // - some GET/POST/PUT JSON data can be specified in SentData
    // - return in result.Lo the HTTP STATUS integer error or success code:
    // 404/HTML_NOTFOUND e.g. if the url doesn't start with Model.Root (caller
    // can try another TSQLRestServer)
    // - return in result.Hi the database internal status
    // - store the data to be sent into Resp, some headers in Head
    // - default implementation calls protected methods EngineList() Retrieve()
    // Add() Update() Delete() UnLock() EngineExecute() above, which must be overriden by
    // the TSQLRestServer child
    // - see TSQLRestClient to check how data is expected in our RESTful format
    // - AccessRights must be handled by the TSQLRestServer child, according
    // to the Application Security Policy (user logging, authentification and
    // rights management) - making access rights a parameter allows this method
    // to be handled as pure stateless, thread-safe and session-free
    // - handle enhanced REST codes: LOCK/UNLOCK/BEGIN/END/ABORT
    // - for 'GET ModelRoot/TableName', url parameters can be either "select" and
    // "where" (to specify a SQL Query, from the SQLFromSelectWhere function),
    // either "sort", "dir", "startIndex", "results", as expected by the YUI
    // DataSource Request Syntax for data pagination - see
    // http://developer.yahoo.com/yui/datatable/#data
    function URI(const url, method, SentData: RawUTF8;
      out Resp, Head: RawUTF8; RestAccessRights: PSQLAccessRights): Int64Rec; virtual;

    /// create an index for the specific FieldName
    // - will call CreateSQLMultiIndex() internaly
    function CreateSQLIndex(Table: TSQLRecordClass; const FieldName: RawUTF8;
      Unique: boolean; const IndexName: RawUTF8=''): boolean; overload;
    /// create one or multiple index(es) for the specific FieldName(s)
    function CreateSQLIndex(Table: TSQLRecordClass; const FieldNames: array of RawUTF8;
      Unique: boolean): boolean; overload;
    /// create one index for all specific FieldNames at once
    function CreateSQLMultiIndex(Table: TSQLRecordClass; const FieldNames: array of RawUTF8;
      Unique: boolean; IndexName: RawUTF8=''): boolean; virtual;

    /// encode some value into a JSON "result":"value" UTF-8 encoded content
    // - wrapper around standard JSONEncode() function
    // - OneValue usually have only ONE parameter, e.g.:
    // ! result := JSONEncodeResult([Value]);
    // returned as '"result":100' if Value=100
    // - if OneValue has more than one parameter, returns a JSON array of values
    // like '"result":["value1",value2]'
    // - this method will work outside of a true TSQLRestServer instance: you
    // can use e.g.
    // ! TSQLRestServer.JSONEncodeResult(['value1',10])
    class function JSONEncodeResult(const OneValue: array of const): RawUTF8;
    /// call this method to disable Authentication method check for a given
    // published method name
    // - by default, only Auth and TimeStamp methods do not require the RESTful
    // authentication of the URI; you may call this method to add another method
    // to the list (e.g. for returning some HTML content from a public URI)
    procedure ServiceMethodByPassAuthentication(const aMethodName: RawUTF8);

................................................................................
    // (should not to be used normaly, because it will add unnecessary overhead)
    property StaticVirtualTableDirect: boolean read fVirtualTableDirect
      write fVirtualTableDirect;
  published
    /// this method will be accessible from ModelRoot/Stat URI, and
    // will retrieve some statistics as a JSON object
    // - method parameters signature matches TSQLRestServerCallBack type
    function Stat(var aParams: TSQLRestServerCallBackParams): Integer;
    /// this method will be accessible from ModelRoot/Auth URI, and
    // will be called by the client for authentication and session management
    // - method parameters signature matches TSQLRestServerCallBack type
    // - to be called in a two pass "challenging" algorithm:
    // $ GET ModelRoot/auth?UserName=...
    // $  -> returns an hexadecimal nonce contents (valid for 5 minutes)
    // $ GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=...
................................................................................
    // user which opened the session via a successful call to the Auth service
    // - when you don't need the session any more (e.g. if the TSQLRestClientURI
    // instance is destroyed), you can call the service as such:
    // $ GET ModelRoot/auth?UserName=...&Session=...
    // - for a way of computing SHA-256 in JavaScript, see for instance
    // @http://www.webtoolkit.info/javascript-sha256.html
    // - this global callback method is thread-safe
    function Auth(var aParams: TSQLRestServerCallBackParams): Integer;
    /// this method will be accessible from the ModelRoot/TimeStamp URI, and
    // will return the server time stamp TTimeLog/Int64 value as RawUTF8
    // - method parameters signature matches TSQLRestServerCallBack type
    function TimeStamp(var aParams: TSQLRestServerCallBackParams): Integer;
    /// this method will be accessible from the ModelRoot/CacheFlush URI, and
    // will flush the server cache
    // - this method shall be called by the clients when the Server cache could
    // be not refreshed
    // - ModelRoot/CacheFlush URI will flush the whole Server cache, for all tables
    // - ModelRoot/CacheFlush/TableName URI will flush the specified table cache
    // - ModelRoot/CacheFlush/TableName/ID URI will flush the content of the
    // specified record
    // - method parameters signature matches TSQLRestServerCallBack type
    function CacheFlush(var aParams: TSQLRestServerCallBackParams): Integer;
    /// this method will be accessible from the ModelRoot/Batch URI, and
    // will execute a set of RESTful commands
    // - expect input as JSON commands - see TSQLRestServer.RunBatch, i.e.
    // & '{"Table":["cmd":values,...]}'
    // or for multiple tables:
    // & '["cmd@Table":values,...]'
    // with cmd in POST/PUT with {object} as value or DELETE with ID
    // - only accepted context HTTP verb is PUT (for thread-safe and security
    // reasons)
    function Batch(var aParams: TSQLRestServerCallBackParams): Integer;
  end;

  /// REST server with direct access to an external database engine
  // - you can set an alternate per-table database engine by using this class
  // - this abstract class is to be overriden with a proper implementation (like
  // our TSQLRestServerStaticInMemory class)
  TSQLRestServerStatic = class(TSQLRestServer)
................................................................................
  end;

  /// Rest client with remote access to a server through a Named Pipe
  // - named pipe is fast and optimized under Windows
  // - can be accessed localy or remotely
  TSQLRestClientURINamedPipe = class(TSQLRestClientURI)
  private
    /// handle for '\\.\pipe\Test_Local_Sqlite3' e.g.
    fServerPipe: cardinal;
    /// the pipe name
    fPipeName: TFileName;
{$ifndef ANONYMOUSNAMEDPIPE}
  {$ifndef NOSECURITYFORNAMEDPIPECLIENTS}
    fPipeSecurityAttributes: TSecurityAttributes;
    fPipeSecurityDescriptor: array[0..SECURITY_DESCRIPTOR_MIN_LENGTH] of byte;
................................................................................
  public
    /// connect to a server contained in a running application
    // - the server must have been declared by a previous
    // TSQLRestServer.ExportServer(ApplicationName) call
    // with ApplicationName as user-defined server identifier ('DBSERVER' e.g.)
    // - ApplicationName is of UnicodeString type since Delphi 2009
    // (direct use of Wide Win32 API version)
    // - this server identifier is appended to '\\.\pipe\Sqlite3_' to obtain
    // the full pipe name to connect to ('\\.\pipe\Sqlite3_DBSERVER' e.g.)
    // - this server identifier may also contain a remote computer name, and
    // must be fully qualified ('\\ServerName\pipe\ApplicationName' e.g.)
    // - raise an exception if the server is not running or invalid
    constructor Create(aModel: TSQLModel; const ApplicationName: TFileName);
    /// release memory and handles
    destructor Destroy; override;
  end;
................................................................................
  // and is handled in TSQLRibbon.RefreshClickHandled
  WM_TIMER_REFRESH_SCREEN = 1;
  /// timer identifier which indicates we must refresh the Report content
  // - used for User Interface generation
  // - is handled in TSQLRibbon.RefreshClickHandled
  WM_TIMER_REFRESH_REPORT = 2;

  /// HTML Status Code for "Success"
  HTML_SUCCESS = 200;
  /// HTML Status Code for "Created"
  HTML_CREATED = 201;
  /// HTML Status Code for "Bad Request"
  HTML_BADREQUEST = 400;
  /// HTML Status Code for "Forbidden"
  HTML_FORBIDDEN = 403;
  /// HTML Status Code for "Not Found"
  HTML_NOTFOUND = 404;
  /// HTML Status Code for "Request Time-out"
  HTML_TIMEOUT = 408;
  /// HTML Status Code for "Not Implemented"
  HTML_NOTIMPLEMENTED = 501;
  /// HTML Status Code for "Service Unavailable"
  HTML_UNAVAILABLE = 503;


/// create a TRecordReference with the corresponding parameters
function RecordReference(Model: TSQLModel; aTable: TSQLRecordClass; aID: integer): TRecordReference;

/// convert a dynamic array of TRecordRef into its corresponding IDs
procedure RecordRefToID(var aArray: TIntegerDynArray);

................................................................................
    /// the currently running service factory
    // - it can be used within server-side implementation to retrieve the
    // associated TSQLRestServer instance
    Factory: TServiceFactoryServer;
    /// the currently runnning session identifier which launched the method
    // - make available the current session or authentication parameters
    // (including e.g. user details via Factory.RestServer.SessionGetUser)
    Session: ^TSQLRestServerSessionContext;
    /// the thread which launched the request
    // - is set by TSQLRestServer.BeginCurrentThread from multi-thread server
    // handlers - e.g. TSQLite3HttpServer or TSQLRestServerNamedPipeResponse
    RunningThread: TThread;
  end;

threadvar
................................................................................
    WhereClause := FormatUTF8('% MATCH :(''%''): ORDER BY rank(matchinfo(%)',
      [SQLTableName,MatchClause,SQLTableName]);
  for i := 0 to high(PerFieldWeight) do
    WhereClause := FormatUTF8('%,:(%):',[WhereClause,PerFieldWeight[i]]);
  result := FTSMatch(Table,WhereClause+') DESC',DocID);
end;

function TSQLRest.AcquireWrite(const aContext: TSQLRestServerSessionContext): Boolean;
var Start, Now: Cardinal;
begin
  if self<>nil then begin
    Start := GetTickCount;
    repeat
      if TryEnterCriticalSection(fTransactionCriticalSession) then begin
        if (fTransactionActive=0) or (fTransactionActive=aContext.Session) then begin
................................................................................
function TSQLRestClientURI.EngineUpdateField(Table: TSQLRecordClass;
  const SetFieldName, SetValue, WhereFieldName, WhereValue: RawUTF8): boolean;
begin
  if (self=nil) or (Table=nil) then
    result := false else
    // PUT ModelRoot/TableName?setname=..&set=..&wherename=..&where=..
    result := URI(FormatUTF8('%?setname=%&set=%&wherename=%&where=%',
      [Model.URI[Table],SetFieldName,UrlEncode(SetValue),WhereFieldName,UrlEncode(WhereValue)]),
        'PUT').Lo=HTML_SUCCESS;
end;


{ TSQLRestServer }

{$ifdef MSWINDOWS}
const
  ServerPipeNamePrefix: TFileName = '\\.\pipe\mORMot_';

var
  GlobalURIRequestServer: TSQLRestServer = nil;

function URIRequest(url, method, SendData: PUTF8Char; Resp, Head: PPUTF8Char): Int64Rec; cdecl;
function StringToPChar(const s: RawUTF8): PUTF8Char;
var L: integer;
begin
  L := length(s);
  if L=0 then
    result := nil else begin
    inc(L); // copy also last #0 from s
    if USEFASTMM4ALLOC then
      GetMem(result,L) else
      result := pointer(GlobalAlloc(GMEM_FIXED,L));
    move(pointer(s)^,result^,L);
  end;
end;
var sResp, sHead: RawUTF8;
begin
  if GlobalURIRequestServer=nil then begin
    Int64(result) := HTML_NOTIMPLEMENTED; // 501 
    exit;
  end;
  result := GlobalURIRequestServer.URI(url,method,SendData,sResp,sHead,


    @SUPERVISOR_ACCESS_RIGHTS);



  if Resp<>nil then
    Resp^ := StringToPChar(sResp); // make result copy as PAnsiChar

  if Head<>nil then
    Head^ := StringToPChar(sHead);
end;

function ReadString(Handle: cardinal): RawUTF8;
var L, Read: cardinal;
    P: PUTF8Char;
begin
  result := '';
................................................................................
  result := true;
end;

const
  MAGIC_SYN: cardinal = $A5ABA5AB;

procedure TSQLRestServer.AnswerToMessage(var Msg: TWMCopyData);
var url, method: RawUTF8;
    SentData: RawUTF8;
    Resp, Head, ResStr: RawUTF8;
    P: PUTF8Char;
    Res: record

      Magic: cardinal;
      LoHi: Int64Rec;


    end;
    Data: TCopyDataStruct;

begin
  Msg.Result := HTML_NOTFOUND;
  if (self=nil) or (Msg.From=0) then
    exit;
  P := Msg.CopyDataStruct^.lpData;
  if (P=nil) or (Msg.CopyDataStruct^.cbData<=7) then
    exit;
  if PCardinal(P)^<>MAGIC_SYN then
    exit; // invalid layout: a broadcasted WM_COPYDATA message? :(
  inc(P,4);
  // #1 is a field delimiter below, since Get*Item() functions return nil for #0
  Msg.Result := HTML_SUCCESS; // Send something back
  url := GetNextItem(P,#1);
  method := GetNextItem(P,#1);
  Head := GetNextItem(P,#1);
  SetString(SentData,P,
    PtrInt(Msg.CopyDataStruct^.cbData)-(P-Msg.CopyDataStruct^.lpData));
  Res.Magic := MAGIC_SYN;

  Res.LoHi := URI(url,method,SentData,Resp,Head,@SUPERVISOR_ACCESS_RIGHTS);
  // note: it's up to URI overriden method to implement access rights




  SetString(ResStr,PAnsiChar(@Res),sizeof(Res));
  Resp := ResStr+Head+#1+Resp;

  Data.dwData := fServerWindow;
  Data.cbData := length(Resp);
  Data.lpData := pointer(Resp);
  { TODO : 64-bits windows: can we store a PtrInt value in WM_COPYDATA? }
  SendMessage(Msg.From,WM_COPYDATA,fServerWindow,PtrInt(@Data));
end;

function TSQLRestServer.CloseServerNamedPipe: boolean;
begin
  if fExportServerNamedPipeThread<>nil then begin
................................................................................
begin
  result := true;
  for i := 0 to high(FieldNames) do
    if not CreateSQLMultiIndex(Table,[FieldNames[i]],Unique) then
     result := false;
end;

class function TSQLRestServer.JSONEncodeResult(const OneValue: array of const): RawUTF8;
var W: TTextWriter;





















































    i,h: integer;
begin
  result := '';
  h := high(OneValue);
  if h<0 then
    exit;

  W := TTextWriter.CreateOwnedStream;
  try
    W.AddShort('{"result":');
    if h=0 then
      // result is one value
      W.AddJSONEscape(OneValue[0]) else begin
      // result is one array of values
      W.Add('[');
      i := 0;
      repeat
        W.AddJSONEscape(OneValue[i]);
        if i=h then break;
        W.Add(',');
        inc(i);
      until false;
      W.Add(']');
    end;
    W.Add('}');
    W.SetText(result);
  finally
    W.Free;
  end;

end;

procedure TSQLRestServer.ServiceMethodByPassAuthentication(const aMethodName: RawUTF8);
var i: Integer;


begin
  if self=nil then

















    exit;
  i :=  fPublishedMethods.Find(aMethodName);
  if i>=0 then
    AddInteger(fPublishedMethodsUnauthenticated,fPublishedMethodsUnauthenticatedCount,i,True);
end;





















function TSQLRestServer.LaunchCallBack(var aParams: TSQLRestServerCallBackParams;
  var aResp: RawUTF8; var aResult: Cardinal): boolean;
var Method: TMethod;
    Invoke: TSQLRestServerCallBack absolute Method;
begin
  result := False;
  if (aParams.MethodIndex<0) or (self=nil) then
    exit;
  Method.Code := pointer(fPublishedMethods.List[aParams.MethodIndex].Tag);
  // launch the method found
{$ifdef WITHLOG}
  SQLite3Log.Enter(self,pointer(fPublishedMethods.List[aParams.MethodIndex].Name));
{$endif}

  try
    Method.Data := Self;
    aResult := Invoke(aParams);
    aResp := aParams.Resp;
    inc(fStats.fServices);
    result := true; // mark method found and execution OK
  except

    on Exception do; // ignore any exception (return false)


  end;
end;

type
  TServiceInternalMethod = (imFree, imContract, imSignature);

const
................................................................................
    exit;
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  result := (fServices as TServiceContainerServer).AddInterface(
    aInterfaces,aInstanceCreation,aContractExpected);
end;

function TSQLRestServer.LaunchService(const aParams: TSQLRestServerCallBackParams;
  var aResp: RawUTF8; var aResult: cardinal): boolean;
var Service: TServiceFactory;
    method, JSON: RawUTF8;
    Values: TPUtf8CharDynArray;
    ServiceParams: PUTF8Char;
    i, m, ServiceID: integer;
    internal: TServiceInternalMethod;
begin
  result := false;
  if Services=nil then
    exit;
  // 1. retrieve request parameters according to routing scheme
  ServiceID := aParams.Context.ID;
  case ServicesRouting of
  rmRest: begin
    i := Services.fListInterfaceMethod.IndexOf(aParams.URI);
    if i<0 then
      exit; // no specific message: it may be a valid request
  {$ifdef WITHLOG}
    SQLite3Log.Enter(self,pointer(aParams.URI));
  {$endif}
    i := PtrInt(Services.fListInterfaceMethod.Objects[i]);
    m := i shr 16;
    Service := Services.Index(i and $ffff);


    if aParams.SentData<>'' then  // parameters sent as JSON array (the Delphi/AJAX way)
      ServiceParams := pointer(aParams.SentData) else begin
      JSON := UrlDecode(aParams.Parameters); // optional URI decoding (the HTML way)
      ServiceParams := pointer(JSON);
    end;
    if ServiceID<0 then
      ServiceID := 0;



  end;
  rmJSON_RPC: begin
    Service := Services[aParams.URI];
    SetString(JSON,PAnsiChar(pointer(aParams.SentData)),length(aParams.SentData));
    JSONDecode(JSON,['method','params','id'],Values,True);
    if Values[0]=nil then begin
      aParams.ErrorMsg^ := 'Method name required';
      exit;
    end;

    method := Values[0];
    ServiceParams := Values[1];
    ServiceID := GetCardinal(Values[2]);
    m := Service.fInterface.FindMethodIndex(method);
    if m>=0 then
      inc(m,length(SERVICE_PSEUDO_METHOD)) else begin
      for internal := low(TServiceInternalMethod) to high(TServiceInternalMethod) do
        if IdemPropNameU(method,SERVICE_PSEUDO_METHOD[internal]) then begin
          m := ord(internal);
          break;
        end;
      if m<0 then begin
        aParams.ErrorMsg^ := 'Unknown method';
        exit;
      end;
    end;
  {$ifdef WITHLOG}
    SQLite3Log.Enter(self,pointer(aParams.URI+'.'+method));
  {$endif}
  end;
  else Exit;
  end;
  // 2. handle request
  if ServiceParams=nil then begin
    aParams.ErrorMsg^ := 'Parameters required';
    exit;
  end;
  if Service=nil then
    exit;
  inc(fStats.fServices);
  case m of
  ord(imFree): // "method":"_free_" to release sicClientDriven..sicPerGroup
    if ServiceID<=0 then // expects an instance ID to be released
      exit else
      m := -1; // notify ExecuteMethod() to release the internal instance
  ord(imContract): begin // "method":"_contract_" to retrieve the implementation contract
    aResult := HTML_SUCCESS; // OK
    aResp := '{"result":['+Service.ContractExpected+'],"id":0}';
    result := true;
    exit; // "id":0 for this method -> no instance was created
  end;
  ord(imSignature): begin // "method":"_signature_" to retrieve the implementation signature
    if TServiceContainerServer(Services).PublishSignature then begin
      aResult := HTML_SUCCESS; // OK
      aResp := '{"result":['+Service.Contract+'],"id":0}';
      result := true; // "id":0 for this method -> no instance was created
    end;
    exit; // not allowed to publish signature
  end;
  else
    dec(m,length(SERVICE_PSEUDO_METHOD)); // index of operation in fMethods[]
  end;
  if (aParams.Context.Session>CONST_AUTHENTICATION_NOT_USED) and (m>=0) and
     (aParams.Context.Group-1 in Service.fExecution[m].Denied) then begin
    aParams.ErrorMsg^ := 'Unauthorized method';
    exit;
  end;
  aResult := TServiceFactoryServer(Service).ExecuteMethod(
    aParams.Context,m,ServiceID,ServiceParams,aResp,aParams.Head^,aParams.ErrorMsg^);
  result := true; // notify method found (any error status is in aResult)
end;

function TSQLRestServer.RunBatch(aStatic: TSQLRestServerStatic;
  aTable: TSQLRecordClass; Sent: PUTF8Char; var Resp, ErrorMsg: RawUTF8): boolean;
var EndOfObject: AnsiChar;
    wasString, OK: boolean;
    TableName, Value: RawUTF8;
    URIMethod, RunningBatchURIMethod: TSQLURIMethod;
    RunningBatchStatic: TSQLRestServerStatic; { TODO: allow nested batch between tables? }
    Method, MethodTable: PUTF8Char;
    Props: TSQLRecordProperties;
    i, ID, Count: integer;
    Results: TIntegerDynArray;
    RunTable: TSQLRecordClass;
    RunStatic: TSQLRestServerStatic;
begin
  result := false;

  if (self=nil) or (Sent=nil) then
    exit;
  if aTable<>nil then begin
    // unserialize expected sequence array as '{"Table":["cmd":values,...]}'
    while Sent^<>'{' do inc(Sent);
    if Sent^<>'{' then
      exit;
................................................................................
        RunningBatchURIMethod := URIMethod;
      end;
      // process CRUD method operation
      case URIMethod of
      mDELETE: begin // '{"Table":[...,"DELETE":ID,...]}' or '[...,"DELETE@Table":ID,...]'
        ID := GetInteger(GetJSONField(Sent,Sent,@wasString,@EndOfObject));
        if (ID<=0) or wasString or
           not RecordCanBeUpdated(RunTable,ID,seDelete,@ErrorMsg) then

          exit;

        if RunStatic<>nil then
          OK := RunStatic.EngineDelete(RunTable,ID) else
          OK := EngineDelete(RunTable,ID);
        if OK then begin
          fCache.NotifyDeletion(RunTable,ID);
          if (RunningBatchStatic<>nil) or
             AfterDeleteForceCoherency(RunTable,ID) then
            Results[Count] := HTML_SUCCESS; // 200 OK
        end;
      end;
      mPOST: begin // '{"Table":[...,"POST":{object},...]}' or '[...,"POST@Table":{object},...]'
        Value := JSONGetObject(Sent,nil,EndOfObject);
        if (Sent=nil) or
           not RecordCanBeUpdated(RunTable,0,seAdd,@ErrorMsg) then

          exit;

        if RunStatic<>nil then
          ID := RunStatic.EngineAdd(RunTable,Value) else
          ID := EngineAdd(RunTable,Value);
        Results[Count] := ID;
        fCache.Notify(RunTable,ID,Value,soInsert);
      end;
      mPUT: begin // '{"Table":[...,"PUT":{object},...]}' or '[...,"PUT@Table":{object},...]'
................................................................................
    while Sent^<>'}' do inc(Sent);
    result := Sent^='}';
  end else
    result := true;
  // send back operation status array
  for i := 0 to Count-1 do
    if Results[i]<>HTML_SUCCESS then begin
      Resp := IntegerDynArrayToCSV(Results,Count,'[',']');
      exit;
    end;
  Resp := '["OK"]';  // to save bandwith if no adding
end;

procedure StatusCodeToErrorMsg(Code: integer; var result: RawUTF8);
begin
  case Code of
    100:             result := 'Continue';
    HTML_SUCCESS:    result := 'OK';
    HTML_CREATED:    result := 'Created';
    202:             result := 'Accepted';
    203:             result := 'Non-Authoritative Information';
    204:             result := 'No Content';
    300:             result := 'Multiple Choices';
    301:             result := 'Moved Permanently';
    302:             result := 'Found';
    303:             result := 'See Other';
    304:             result := 'Not Modified';
    307:             result := 'Temporary Redirect';
    HTML_BADREQUEST: result := 'Bad Request';
    401:             result := 'Unauthorized';
    HTML_FORBIDDEN:  result := 'Forbidden';
    HTML_NOTFOUND:   result := 'Not Found';
    405:             result := 'Method Not Allowed';
    406:             result := 'Not Acceptable';
    500:             result := 'Internal Server Error';
    HTML_UNAVAILABLE:result := 'Service Unavailable';
    else             result := 'Invalid Request';
  end;
end;

function StringToMethod(const method: RawUTF8): TSQLURIMethod;
const NAME: array[mGET..high(TSQLURIMethod)] of string[7] = (
  'GET','POST','PUT','DELETE','BEGIN','END','ABORT','LOCK','UNLOCK','STATE');
var URIMethodUp: string[7];
begin
................................................................................
    for result := low(NAME) to high(NAME) do
      if URIMethodUp=NAME[result] then
        exit;
  end;
  result := mNone;
end;

function TSQLRestServer.URI(const url, method, SentData: RawUTF8;
      out Resp, Head: RawUTF8; RestAccessRights: PSQLAccessRights): Int64Rec;
var BlobFieldName: RawUTF8;
    Static: TSQLRestServerStatic;
    Engine: TSQLRestServer;
    i,j,L: PtrInt;
    SQLSelect, SQLWhere, SQLSort, SQLDir, SQL, ErrorMsg: RawUTF8;
    SQLStartIndex, SQLResults: integer;
    StaticKind: (sNone, sInMemory, sVirtual);
    SQLisSelect, OK: boolean;
    URI: TSQLRestServerCallBackParams;
    Session: TAuthSession;
    SessionAccessRights: TSQLAccessRights; // session may be deleted meanwhile
    P: PUTF8Char;
    Blob: PPropInfo;
{$ifdef WITHSTATPROCESS}
    timeStart,timeEnd: Int64;
{$endif}
{$ifdef WITHLOG}
    Log: ISynLog;
{$endif}

  procedure SetRespFromError;
  begin
     inc(fStats.fInvalid);
    if ErrorMsg='' then
      StatusCodeToErrorMsg(result.Lo,ErrorMsg);
    {$ifdef WITHLOG}
    Log.Log(sllServer,'% % ERROR=% (%)',[method,url,result.Lo,ErrorMsg],self);
    {$endif}
    if Resp='' then // return error content as JSON object
      with TTextWriter.CreateOwnedStream do
      try
        AddShort('{'#13#10'"ErrorCode":');
        Add(result.Lo);
        AddShort(#13#10'"ErrorText":"');
        AddJSONEscape(pointer(ErrorMsg));
        AddShort('"'#13#10'}');
        SetText(resp);
      finally
        Free;
      end;
  end;

begin
{$ifdef WITHLOG}
  Log := SQLite3Log.Enter;
{$endif}
  // 0. always return internal database state count (even if URI is '')
{$ifdef WITHSTATPROCESS}
  QueryPerformanceCounter(timeStart);
{$endif}


  result.Hi := InternalState; // get first, since other threads may change it
  L := length(url);
  inc(fStats.fIncomingBytes,L+length(method)+length(SentData)+12);
  // 1. retrieve URI expecting 'ModelRoot[/TableName[/ID[/BlobFieldName]]]' format
  i := 0;
  if (url<>'') and (url[1]='/') then inc(i); // URL may be '/path'
  j := length(Model.Root);
  if (i+j>L) or (not(url[i+j+1] in [#0,'/','?'])) or
     (StrCompIL(pointer(PtrInt(url)+i),pointer(Model.Root),j,0)<>0) then begin
    result.Lo := HTML_NOTFOUND; // server does not match the Request-URI
    ErrorMsg := 'Invalid Root';
    SetRespFromError;

    exit; // bad ModelRoot -> caller can try another TSQLRestServer
  end;
  result.Lo := HTML_BADREQUEST; // default error code is 400 BAD REQUEST
  URI.URI := copy(url,j+i+2,maxInt);
  i := PosEx(RawUTF8('/'),URI.URI,1);
  if i>0 then begin
    URI.Parameters := @URI.URI[i+1];
    URI.Context.ID := GetNextItemCardinal(URI.Parameters,'/');
    if (URI.Context.ID>0) and (URI.Parameters<>nil) then begin
      // for URL like "ModelRoot/TableName/ID/BlobFieldName"
      P := PosChar(URI.Parameters,'?');
      if P=nil then
        BlobFieldName := URI.Parameters else
        SetString(BlobFieldName,PAnsiChar(URI.Parameters),P-URI.Parameters);
    end;
    SetLength(URI.URI,i-1);
    j := PosEx(RawUTF8('?'),url,1);
    if j>0 then // '?select=...&where=...' or '?where=...'
      URI.Parameters := @url[j+1] else
      URI.Parameters := nil;
  end else begin
    URI.Context.ID := -1;
    i := PosEx(RawUTF8('?'),url,1);
    if i>0 then begin // '?select=...&where=...' or '?where=...'
      URI.Parameters := @url[i+1];
      i := PosEx(RawUTF8('?'),URI.URI);
      if i>0 then
        dec(i);
      SetLength(URI.URI,i);
    end else
      URI.Parameters := nil; // no parameter
  end;
................................................................................
        end;
    if Static<>nil then
      Engine := Static;
  end;
  // 2. handle security
  Session := nil;
  if HandleAuthentication then begin
    URI.Context.Session := CONST_AUTHENTICATION_SESSION_NOT_STARTED;
    // check session_signature=... parameter
    if URI.Parameters<>nil then begin
      // expected format is 'session_signature='Hexa8(SessionID)+Hexa8(TimeStamp)+
      // Hexa8(crc32('SessionID+HexaSessionPrivateKey'+Sha256('salt'+PassWord)+
      //   Hexa8(TimeStamp)+url))
      i := L-(17+24);
      if (i>0) and // should be LAST parameter in URL
         IdemPChar(@url[i],'SESSION_SIGNATURE=') and
         HexDisplayToCardinal(@url[i+18],URI.Context.Session) then begin
        EnterCriticalSection(fSessionCriticalSection);
        try
          Session := SessionAccess(URI.Context);
          if Session.IsValidURL(url,i-2) then begin
            // supplied RestAccessRights is ignored and replaced by the user rights
            {$ifdef WITHLOG}
            Log.Log(sllUserAuth,'%/%',[Session.User.LogonName,URI.Context.Session],self);
            {$endif}
            move(Session.fAccessRights,SessionAccessRights,sizeof(TSQLAccessRights));
            RestAccessRights := @SessionAccessRights;
          end else
            // mark invalid query authentication
            Session := nil;
        finally
          LeaveCriticalSection(fSessionCriticalSection);
        end;
      end;
    end;
    if (Session=nil) and ((URI.MethodIndex<0) or
      not IntegerScanExists(pointer(fPublishedMethodsUnauthenticated),
        fPublishedMethodsUnauthenticatedCount,URI.MethodIndex)) then begin
      // /auth + /timestamp are e.g. allowed services without signature
      result.Lo := HTML_FORBIDDEN; // 403 in case of authentication failure
      // 401 Unauthorized response MUST include a WWW-Authenticate header,
      // which is not what we used, so we won't send 401 error code but 403
      SetRespFromError;
      exit; // authentication error -> caller can try to open another session
    end;
  end else begin // default unique session if authentication is not enabled
    URI.Context.Session := CONST_AUTHENTICATION_NOT_USED;
    URI.Context.User := 0;
    URI.Context.Group := 0;
  end;
  // 3. call appropriate database commands
  URI.Context.Method := StringToMethod(method);
  URI.SentData := SentData;
  URI.Head := @Head;
  URI.ErrorMsg := @ErrorMsg;
  with URI.Context do
  case Method of
  mLOCK,mGET: begin
    if URI.Table=nil then begin
      if (Method<>mLOCK) and
          // GET ModelRoot/MethodName + parameters sent in URI
         ((URI.MethodIndex<0) or not LaunchCallBack(URI,Resp,result.Lo)) then
        if (URI.URI='') or not (reService in RestAccessRights^.AllowRemoteExecute) or
           // GET ModelRoot/Service.Method[/ID] + parameters sent as JSON or in URI
           not LaunchService(URI,Resp,result.Lo) then begin
          if (SentData='') and (URI.Parameters<>nil) and
             (reUrlEncodedSQL in RestAccessRights^.AllowRemoteExecute) then begin
            // GET with a SQL statement sent in URI, as sql=....
            while not UrlDecodeValue(URI.Parameters,'SQL=',SQL,@URI.Parameters) do
              if URI.Parameters=nil then break;
          end else
            // GET with a SQL statement sent as UTF-8 body
            SQL := SentData;
          SQLisSelect := isSelect(pointer(SQL));
          if (SQL<>'') and
            (SQLisSelect or (reSQL in RestAccessRights^.AllowRemoteExecute)) then begin
            // no user check for SELECT: see TSQLAccessRights.GET comment
            Static := InternalAdaptSQL(Model.GetTableIndexFromSQLSelect(SQL,false),SQL);
            if Static<>nil then
              Engine := Static;
            Resp := Engine.EngineList(SQL);
            // security note: only first statement is run by EngineList()
            if Resp<>'' then begin // got JSON list '[{...}]' ?
              result.Lo := HTML_SUCCESS;  // 200 OK
              if not SQLisSelect then
                inc(fStats.fModified);
            end;
          end;
        end;
    end else
    // here, Table<>nil and TableIndex in [0..MAX_SQLTABLES-1]
    if not (URI.TableIndex in RestAccessRights^.GET) then // check User Access
      result.Lo := HTML_FORBIDDEN else begin
      if ID>0 then begin
        // GET ModelRoot/TableName/ID[/BlobFieldName] to retrieve one member,
        // with or w/out locking, or a specified BLOB field content
        if Method=mLOCK then // LOCK is to be followed by PUT -> check user
          if not (URI.TableIndex in RestAccessRights^.PUT) then
            result.Lo := HTML_FORBIDDEN else
            if Model.Lock(URI.TableIndex,ID) then
              Method := mGET; // mark successfully locked
        if Method<>mLOCK then
          if BlobFieldName<>'' then begin
            // GET ModelRoot/TableName/ID/BlobFieldName: retrieve BLOB field content
            Blob := URI.Table.RecordProps.BlobFieldPropFromRawUTF8(BlobFieldName);
            if Blob<>nil then begin
              if Engine.EngineRetrieveBlob(URI.Table,ID,Blob,TSQLRawBlob(Resp)) then begin


                result.Lo := HTML_SUCCESS; // 200 OK
                Head := HEADER_CONTENT_TYPE+GetMimeContentType(pointer(Resp),Length(Resp));
              end;
            end else begin
              // GET ModelRoot/TableName/ID/MethodName: try MethodName
              URI.MethodIndex := fPublishedMethods.Find(BlobFieldName);
              if URI.MethodIndex>=0 then
                LaunchCallBack(URI,Resp,result.Lo);
            end;
          end else begin
            // GET ModelRoot/TableName/ID: retrieve a member content, JSON encoded
            Resp := fCache.Retrieve(URI.TableIndex,ID);
            if Resp='' then begin

              Resp := Engine.EngineRetrieve(URI.TableIndex,ID); // get JSON object '{...}'

              fCache.Notify(URI.TableIndex,ID,Resp,soSelect); // cache if expected
            end;
            if Resp<>'' then // if something was found
              result.Lo := HTML_SUCCESS; // 200 OK
          end;
      end else
      // ModelRoot/TableName with 'select=..&where=' or YUI paging
      if Method<>mLOCK then begin // LOCK not available here
        SQLSelect := 'RowID'; // if no select is specified (i.e. ModelRoot/TableName)
        // all IDs of this table are returned to the client
        SQLWhere := '';
        if URI.Parameters<>nil then begin // '?select=...&where=...' or '?where=...'
          SQLStartIndex := 0;
          SQLResults := 0;
          if URI.Parameters^<>#0 then
................................................................................
          end;
          if (SQLResults<>0) and not ContainsUTF8(pointer(SQLWhere),'LIMIT ') then
            SQLWhere := FormatUTF8('% LIMIT % OFFSET %',[SQLWhere,SQLResults,SQLStartIndex]);
        end;
        SQL := Model.Props[URI.Table].SQLFromSelectWhere(SQLSelect,trim(SQLWhere));
        if (Static<>nil) and (StaticKind=sInMemory) then
          // manual retrieval (no SQLite3 module available for fStaticData[])
          Resp := Static.EngineList(SQL) else
          // TSQLVirtualTableJSON/External will rely on their SQLite3 module
          Resp := EngineList(SQL);
        if Resp<>'' then  // got JSON list '[{...}]' ?
          result.Lo := HTML_SUCCESS;  // 200 OK
      end;
    end;
  end;
  mUNLOCK: begin
    // ModelRoot/TableName/ID to unlock a member
    if not (URI.TableIndex in RestAccessRights^.PUT) then
      result.Lo := HTML_FORBIDDEN else
    if (URI.Table<>nil) and (ID>0) and
       Model.UnLock(URI.Table,ID) then
        result.Lo := HTML_SUCCESS; // 200 OK
  end;
  mSTATE: begin
    // STATE method for TSQLRestClientURI.ServerInternalState
    // this method is called with Root (-> Table=nil -> Static=nil)
    // we need a specialized method in order to avoid fStats.Invalid increase
    result.Lo := HTML_SUCCESS;
    for i := 0 to high(fStaticData) do
      if fStaticData[i]<>nil then
        if fStaticData[i].RefreshedAndModified then begin
          inc(InternalState); // force refresh
          break;
        end;
  end else
  // write methods (mPOST, mPUT, mDELETE...) are handled separately
  if (URI.Table<>nil) or (Method<>mPOST) or // check thread-safe call of service
     not (reService in RestAccessRights^.AllowRemoteExecute) or
     // POST ModelRoot/Service.Method[/ID] + parameters sent as JSON or in URI
     not LaunchService(URI,Resp,result.Lo) then
  // now we have to handle a write to the DB (POST/PUT/DELETE...)
  if AcquireWrite(URI.Context) then // make it thread-safe and transaction-safe
  try
    case Method of
    mPOST: begin       // POST=ADD=INSERT
      if URI.Table=nil then begin
        // ModelRoot with free SQL statement sent as UTF-8 (only for Admin group)
        // security note: multiple SQL statements can be run in EngineExecuteAll()
        if (reSQL in RestAccessRights^.AllowRemoteExecute) and (ErrorMsg='') and
           EngineExecuteAll(SentData) then begin
          result.Lo := HTML_SUCCESS; // 200 OK
          inc(fStats.fModified);
        end;
      end else
      // here, Table<>nil and TableIndex in [0..MAX_SQLTABLES-1]
      if not (URI.TableIndex in RestAccessRights^.POST) then // check User
        result.Lo := HTML_FORBIDDEN else
      if ID<0 then begin
        // ModelRoot/TableName with possible JSON SentData: create a new member
        ID := Engine.EngineAdd(URI.Table,SentData);
        if ID<>0 then begin
          result.Lo := HTML_CREATED; // 201 Created
          Head := 'Location: '+URI.URI+'/'+
            {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(ID);
          fCache.Notify(URI.TableIndex,ID,SentData,soInsert);
          inc(fStats.fModified);
        end;
      end else
        // ModelRoot/TableName/0 = BATCH sequence '{"Table":["cmd":values,...]}'
        if not (URI.TableIndex in RestAccessRights^.PUT) or
           not (URI.TableIndex in RestAccessRights^.DELETE) then // POST already checked
          result.Lo := HTML_FORBIDDEN else
          if RunBatch(Static,URI.Table,pointer(SentData),Resp,ErrorMsg) then
            result.Lo := HTML_SUCCESS;
    end;
    mPUT: begin        // PUT=UPDATE
      if URI.MethodIndex>=0 then
        // PUT ModelRoot/MethodName (e.g. ModelRoot/Batch)
        LaunchCallBack(URI,Resp,result.Lo) else
      if ID>0 then begin
        // PUT ModelRoot/TableName/ID[/BlobFieldName] to update member/BLOB content
        if not RecordCanBeUpdated(URI.Table,ID,seUpdate,@ErrorMsg) or
           not (URI.TableIndex in RestAccessRights^.PUT) then // check User
          result.Lo := HTML_FORBIDDEN else begin 


          OK := false;
          if BlobFieldName<>'' then begin
            // PUT ModelRoot/TableName/ID/BlobFieldName: update BLOB field content
            Blob := URI.Table.RecordProps.BlobFieldPropFromRawUTF8(BlobFieldName);
            if Blob<>nil then begin
              OK := Engine.EngineUpdateBlob(URI.Table,ID,Blob,SentData);
            end else begin
              // PUT ModelRoot/TableName/ID/MethodName: try MethodName
              URI.MethodIndex := fPublishedMethods.Find(BlobFieldName);
              if URI.MethodIndex>=0 then
                LaunchCallBack(URI,Resp,result.Lo);
            end;
          end else begin
            // ModelRoot/TableName/ID with JSON SentData: update a member
            OK := Engine.EngineUpdate(URI.Table,ID,SentData);
            if OK then
              fCache.NotifyDeletion(URI.TableIndex,ID); // flush (no CreateTime in JSON)
          end;
          if OK then begin
            result.Lo := HTML_SUCCESS; // 200 OK
            inc(fStats.fModified);
          end;
        end;
      end else
      if URI.Parameters<>nil then // e.g. from TSQLRestClient.EngineUpdateField
        // PUT ModelRoot/TableName?setname=..&set=..&wherename=..&where=..
        if not (URI.TableIndex in RestAccessRights^.PUT) then // check User
          result.Lo := HTML_FORBIDDEN else begin 
          repeat
            UrlDecodeValue(URI.Parameters,'SETNAME=',SQLSelect);
            UrlDecodeValue(URI.Parameters,'SET=',SQLDir);
            UrlDecodeValue(URI.Parameters,'WHERENAME=',SQLSort);
            UrlDecodeValue(URI.Parameters,'WHERE=',SQLWhere,@URI.Parameters);
          until URI.Parameters=nil;
          if (SQLSelect<>'') and (SQLDir<>'') and (SQLSort<>'') and (SQLWhere<>'') then
            if Engine.EngineUpdateField(URI.Table,SQLSelect,SQLDir,SQLSort,SQLWhere) then begin
              result.Lo := HTML_SUCCESS; // 200 OK
              inc(fStats.fModified);
            end;
        end;
    end;
    mDELETE:
      if URI.Table<>nil then
        if ID>0 then
          // ModelRoot/TableName/ID to delete a member
          if not RecordCanBeUpdated(URI.Table,ID,seDelete,@ErrorMsg) or
             not (URI.TableIndex in RestAccessRights^.DELETE) then // check User
            result.Lo := HTML_FORBIDDEN else begin


            if Engine.EngineDelete(URI.Table,ID) and
               AfterDeleteForceCoherency(URI.Table,ID) then begin
              result.Lo := HTML_SUCCESS; // 200 OK
              fCache.NotifyDeletion(URI.TableIndex,ID);
              inc(fStats.fModified);
            end;
          end else
        if URI.Parameters<>nil then
          if (not (URI.TableIndex in RestAccessRights^.DELETE)) or
             (not (reUrlEncodedDelete in RestAccessRights^.AllowRemoteExecute)) then
            result.Lo := HTML_FORBIDDEN else begin
            // ModelRoot/TableName?WhereClause to delete members
            SQLWhere := Trim(UrlDecode(URI.Parameters));
            if SQLWhere<>'' then begin
              if Delete(URI.Table,SQLWhere) then begin
                result.Lo := HTML_SUCCESS; // 200 OK
                inc(fStats.fModified);
              end;
            end;
          end;
    mBEGIN: begin      // BEGIN TRANSACTION
      // TSQLVirtualTableJSON/External will rely on SQLite3 module
      // and also TSQLRestServerStaticInMemory, since COMMIT/ROLLBACK have Static=nil
      if TransactionBegin(URI.Table,Session) then begin
        if (Static<>nil) and (StaticKind=sVirtual) then
          Static.TransactionBegin(URI.Table,Session);
        result.Lo := HTML_SUCCESS; // 200 OK
      end;
    end;
    mEND: begin        // END=COMMIT
      // this method is called with Root (-> Table=nil -> Static=nil)
      if fTransactionTable<>nil then
        Static := StaticVirtualTable[fTransactionTable];
      Commit(Session);
      if Static<>nil then
        Static.Commit(Session);
      result.Lo := HTML_SUCCESS; // 200 OK
    end;
    mABORT: begin      // ABORT=ROLLBACK
      // this method is called with Root (-> Table=nil -> Static=nil)
      if fTransactionTable<>nil then
        Static := StaticVirtualTable[fTransactionTable];
      RollBack(Session);
      if Static<>nil then
        Static.RollBack(Session);
      result.Lo := HTML_SUCCESS; // 200 OK
    end;
    end;
  finally
    ReleaseWrite;
  end else
    // AcquireWrite(SessionID) returned false (e.g. endless transaction)
    result.Lo := HTML_TIMEOUT; // 408 Request Time-out
  end;
  // 4. returns expected result to the client
  if result.Lo in [HTML_SUCCESS,HTML_CREATED] then begin
    inc(fStats.fResponses);
    {$ifdef WITHLOG}
    Log.Log(sllServer,'% % -> %',[method,url,result.Lo],self);
    {$endif}
  end else
    SetRespFromError;



  inc(fStats.fOutcomingBytes,length(Resp)+length(Head)+16);
  if (Static<>nil) and (StaticKind=sInMemory) then
    result.Hi := cardinal(-1) else // force always refresh for Static table

    result.Hi := InternalState;    // database state may have changed above

{$ifdef WITHSTATPROCESS}
  QueryPerformanceCounter(timeEnd);
  inc(fStats.ProcessTimeCounter,timeEnd-timeStart);
{$endif}
end;

function TSQLRestServer.Stat(var aParams: TSQLRestServerCallBackParams): Integer;
begin
  aParams.Resp := Stats.DebugMessage; // transmitted as JSON object
  result := HTML_SUCCESS;
end;

function TSQLRestServer.TimeStamp(var aParams: TSQLRestServerCallBackParams): Integer;
begin
  aParams.Head^ := TEXT_CONTENT_TYPE_HEADER;
  aParams.Resp := Int64ToUtf8(ServerTimeStamp);
  result := HTML_SUCCESS;
end;

function TSQLRestServer.CacheFlush(var aParams: TSQLRestServerCallBackParams): Integer;
begin
  if aParams.Table=nil then
    Cache.Flush else
    if aParams.Context.ID=0 then
      Cache.Flush(aParams.Table) else
      Cache.SetCache(aParams.Table,aParams.Context.ID);
  result := HTML_SUCCESS;
end;

function TSQLRestServer.Batch(var aParams: TSQLRestServerCallBackParams): Integer;
begin
  if (aParams.Context.Method=mPUT) and
     RunBatch(nil,nil,pointer(aParams.SentData),aParams.Resp,aParams.ErrorMsg^) then
    result := HTML_SUCCESS else
    result := HTML_BADREQUEST; 
end;

function Nonce(Previous: boolean): RawUTF8;
var Tix: cardinal;
    tmp: RawByteString;
begin
  Tix := GetTickCount div (1000*60*5); // valid for 5*60*1000 ms = 5 minutes
  if Previous then
    dec(Tix);
  SetString(tmp,PAnsiChar(@Tix),sizeof(Tix));
  result := SHA256(tmp);
end;

function TSQLRestServer.Auth(var aParams: TSQLRestServerCallBackParams): Integer;

procedure CreateNewSession(var User: TSQLAuthUser; var aParams: TSQLRestServerCallBackParams);
var Session: TAuthSession;
begin
  if User.fID=0 then begin
    {$ifdef WITHLOG}
    SQLite3Log.Family.SynLog.Log(sllUserAuth,
      'User.LogonName=% not found in AuthUser table',[User.LogonName],self);
    {$endif}
    exit; // unknown user -> error 404
  end;
  Session := TAuthSession.Create(self,User);
  try




    aParams.Resp := JSONEncode(['result',Session.fPrivateSalt,'logonname',User.LogonName]);
    User := nil; // will be freed by TAuthSession.Destroy
    if fSessions=nil then
      fSessions := TObjectList.Create;
    fSessions.Add(Session);
    Session := nil; // will be freed by fSessions
  finally
    Session.Free;
................................................................................
    InDataEnc: RawUTF8;
    CtxArr: TDynArray;
    Now: QWord;
    SecCtxIdx: Integer;
    OutData: RawByteString;
{$endif}
begin
  result := HTML_NOTFOUND;
  if not UrlDecodeNeedParameters(aParams.Parameters,'UserName') then

    exit;

  EnterCriticalSection(fSessionCriticalSection);
  try
    if UrlDecodeNeedParameters(aParams.Parameters,'Session') then begin
      // GET ModelRoot/auth?UserName=...&Session=... -> release session
      while aParams.Parameters<>nil do begin
        UrlDecodeValue(aParams.Parameters,'USERNAME=',aUserName);
        UrlDecodeCardinal(aParams.Parameters,'SESSION=',aSessionID,@aParams.Parameters);
      end;
      if (fSessions<>nil) and 
         // allow only to delete its own session - ticket [7723fa7ebd]
         (aSessionID=aParams.Context.Session) then
      for i := 0 to fSessions.Count-1 do
        with TAuthSession(fSessions.List[i]) do
        if fIDCardinal=aSessionID then begin
          SessionDelete(i);
          result := HTML_SUCCESS; // mark success
          break;
        end;
      exit; // unknown session -> error 404
    end else
    if UrlDecodeNeedParameters(aParams.Parameters,'PassWord,ClientNonce') then begin
      // GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=... -> handshaking
      while aParams.Parameters<>nil do begin
        UrlDecodeValue(aParams.Parameters,'USERNAME=',aUserName);
        UrlDecodeValue(aParams.Parameters,'PASSWORD=',aPassWord);
................................................................................
          SecCtxIdx := CtxArr.New; // add a new entry to fSSPIAuthContexts[]
          InvalidateSecContext(fSSPIAuthContexts[SecCtxIdx]);
          fSSPIAuthContexts[SecCtxIdx].ID := fSSPIAuthCounter;
          Inc(fSSPIAuthCounter);
        end;
        // call SSPI provider
        if ServerSSPIAuth(fSSPIAuthContexts[SecCtxIdx], Base64ToBin(InDataEnc), OutData) then begin
          aParams.Resp := JSONEncode(['result','','id',Int64(fSSPIAuthContexts[SecCtxIdx].ID),
            'data',BinToBase64(OutData)]);
          Result := HTML_SUCCESS;
          exit; // 1st call: send back OutData to the client
        end;
        // 2nd call: user was authenticated -> release used context
        ServerSSPIAuthUser(fSSPIAuthContexts[SecCtxIdx], aUserName);
        {$ifdef WITHLOG}
        SQLite3Log.Family.SynLog.Log(sllUserAuth,
          'Windows Authentication success for %',[aUserName],self);
................................................................................
        CreateNewSession(User,aParams);
      finally
        User.Free;
      end;
{$endif}
    end else
      // only UserName=... -> return hexadecimal nonce content valid for 5 minutes
      aParams.Resp := JSONEncodeResult([Nonce(false)]);
  finally
    LeaveCriticalSection(fSessionCriticalSection);
  end;
  result := HTML_SUCCESS;
end;

procedure TSQLRestServer.SessionDelete(aSessionIndex: integer);
begin
  if (self<>nil) and (Cardinal(aSessionIndex)<Cardinal(fSessions.Count)) then
  with TAuthSession(fSessions.List[aSessionIndex]) do begin
    if Services is TServiceContainerServer then
      TServiceContainerServer(Services).OnCloseSession(IDCardinal);
    {$ifdef WITHLOG}
    SQLite3Log.Family.SynLog.Log(sllUserAuth,'Deleted session %/%',[User.LogonName,IDCardinal],self);
    {$endif}
    fSessions.Delete(aSessionIndex);
  end;
end;

function TSQLRestServer.SessionAccess(var aContext: TSQLRestServerSessionContext): TAuthSession;
var i: integer;
    Now: cardinal;
begin
  if (self<>nil) and (fSessions<>nil) then begin
    // first check for outdated sessions to be deleted
    Now := GetTickCount;
    for i := fSessions.Count-1 downto 0 do
      with TAuthSession(fSessions.List[i]) do
        if Now<LastAccess then // 32 bit overflow occured
          fLastAccess := Now else
................................................................................
        if QWord(Now)>QWord(LastAccess)+QWord(TimeOut) then
          SessionDelete(i);
    // retrieve session
    for i := 0 to fSessions.Count-1 do begin
      result := TAuthSession(fSessions.List[i]);
      if result.IDCardinal=aContext.Session then begin
        result.fLastAccess := Now; // refresh session access timestamp
        aContext.User := result.User.fID;
        aContext.Group := result.User.GroupRights.fID;
        exit;
      end;
    end;
  end;
  result := nil;
end;

................................................................................
      fChild[fMasterThreadChildIndex] := nil;
      InterlockedDecrement(fChildCount);
    end;
  inherited;
end;

procedure TSQLRestServerNamedPipeResponse.Execute;
var url, method: RawUTF8;
    SentData: RawUTF8;
    Resp, Head: RawUTF8;
    Code: integer;
    Ticks, Sleeper, ClientTimeOut: cardinal;
    Available: cardinal;
    Res: Int64Rec;
begin
  if (fPipe=0) or (fPipe=INVALID_HANDLE_VALUE) or (fServer=nil) then
    exit;
  fServer.BeginCurrentThread(self);
  Ticks := 0;
  Sleeper := 0;
  ClientTimeOut := GetTickCount+30*60*1000; // disconnect client after 30 min of inactivity
................................................................................
    while not Terminated do
    if // (WaitForSingleObject(fPipe,200)=WAIT_OBJECT_0) and  = don't wait
       PeekNamedPipe(fPipe,nil,0,nil,@Available,nil) and (Available>=4) then begin
      FileRead(fPipe,Code,4);
      if (Code=integer(MAGIC_SYN)) // magic word for URI like request
        and not Terminated then
        try
          url := ReadString(fPipe);
          method := ReadString(fPipe);
          SentData := ReadString(fPipe);
          //writeln('TSQLRestServerNamedPipeResponse.EngineExecute ',method,' ',url);
          Res := fServer.URI(url,method,SentData,Resp,Head,@SUPERVISOR_ACCESS_RIGHTS);
          // it's up to URI overriden method to implement access rights

          FileWrite(fPipe,Res,sizeof(Res));
          WriteString(fPipe,Head);
          WriteString(fPipe,Resp);
          FlushFileBuffers(fPipe); // Flush the pipe to allow the client to read
//        if (Win32Platform=VER_PLATFORM_WIN32_NT) and (Win32MajorVersion<6) then
          Ticks := GetTickCount+20; // start sleeping after 20 ms
          ClientTimeOut := Ticks+30*60*1000;
          if ClientTimeOut<Ticks then // avoid 32 bits overflow
            ClientTimeOut := 30*60*1000;
          Sleeper := 0;
          Sleep(0);
        except
................................................................................
      Sleep(Sleeper); // doesn't slow down connection but decreases CSwitch
      Ticks := 0;
      if GetTickCount>ClientTimeOut then
        break; // disconnect client after 30 min of inactivity
    end else
      Sleep(0);
  finally

    DisconnectNamedPipe(fPipe);
    CloseHandle(fPipe);
  end;
end;


{ TSQLRestClientURINamedPipe }
................................................................................
{$endif}
  CreatePipe;
  if (Pipe=INVALID_HANDLE_VALUE) or (Pipe=ERROR_PIPE_BUSY) then
    for Retry := 1 to NAMEDPIPE_RETRYCOUNT_IFBUSY do begin
      if WaitNamedPipe(pointer(fPipeName),1000) then begin
        // 1000 since we have sleep(128) in TSQLRestServerNamedPipe.EngineExecute
        CreatePipe;
        if Pipe=INVALID_HANDLE_VALUE then
          break;
      end;
      sleep(200); // wait for TSQLRestServerNamedPipe.EngineExecute to be reached
    end;
  if (Pipe=INVALID_HANDLE_VALUE) or (Pipe=ERROR_PIPE_BUSY) then begin
    {$ifdef WITHLOG}
    Log.Log(sllError,'"%" when connecting to %',[
................................................................................
function TServiceFactoryServer.CreateInstance: TInterfacedObject;
begin
  if fImplementationClassWithCustomCreate then
    result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create else
    result := fImplementationClass.Create;
end;

function TServiceFactoryServer.ExecuteMethod(const aContext: TSQLRestServerSessionContext;

  aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char;
  var aResp, aHead, aErrorMsg: RawUTF8): cardinal;





var Inst: TServiceFactoryServerInstance;
    WR: TTextWriter;
    entry: PInterfaceEntry;
    ThreadServer: PServiceRunningContext;
begin
  result := HTML_BADREQUEST;
  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
................................................................................
    sicShared:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven, sicPerSession, sicPerUser, sicPerGroup: begin
      if InstanceCreation=sicClientDriven then
        Inst.InstanceID := aInstanceID else
        if aContext.Session>CONST_AUTHENTICATION_NOT_USED then
          case InstanceCreation of // authenticated user -> handle context
          sicPerSession: Inst.InstanceID := aContext.Session;
          sicPerUser:    Inst.InstanceID := aContext.User;
          sicPerGroup:   Inst.InstanceID := aContext.Group;
          end else
         aErrorMsg := '% mode expects an authenticated session';
      if aErrorMsg='' then

        if InternalInstanceRetrieve(Inst,aMethodIndex) then begin
          result := HTML_SUCCESS;

          exit; // {"method":"_free_", "params":[], "id":1234}
        end;
    end;
  end;
  if Inst.Instance=nil then begin
    if aErrorMsg='' then
      aErrorMsg := '% instance id:% not found or deprecated';
    aErrorMsg := FormatUTF8(pointer(aErrorMsg),
      [UnCamelCase(ServiceInstanceImplementationToText(InstanceCreation)),
       Inst.InstanceID]);
    exit;
  end;
  // 2. call method implementation
  try
    entry := Inst.Instance.GetInterfaceEntry(fInterface.fInterfaceIID);
    if entry=nil then
      exit;
    ThreadServer := @ServiceContext;
    WR := TJSONSerializer.CreateOwnedStream;
    try
      with ThreadServer^ do begin
        Factory := self;
        Session := @aContext;
      end; // RunningThread is already set at thread initialization
      // root/calculator {"method":"add","params":[1,2]} -> {"result":[3],"id":0}
      try
        WR.AddShort('{"result":[');
        if not fInterface.fMethods[aMethodIndex].InternalExecute(
            [PAnsiChar(Inst.Instance)+entry^.IOffset],aParamsJSONArray,WR,aHead,
            fExecution[aMethodIndex].Options) then
          exit; // wrong request
        if aHead='' then begin // aHead<>'' for TServiceCustomAnswer
          WR.AddShort('],"id":');
          WR.Add(Inst.InstanceID); // only used in sicClientDriven mode
          WR.AddShort('}');
        end;
        WR.SetText(aResp);
        result := HTML_SUCCESS; // success
      except
        on E: Exception do begin
          result := 500; // Internal Server Error
          aErrorMsg := FormatUTF8('%: %',[E.ClassName,E.Message]);
          exit;
        end;

      end;
    finally
      with ThreadServer^ do begin
        Factory := nil;
        Session := nil;
      end;
      WR.Free;






>
>
>
>












>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>





<
|
>
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>


>
>
>




>

<
<
>
>
>
>
>
|
<
<
<
<
<
<
>
>
>
>




>
>
>
>





<
<
<
<
<
<
<
<
<
<
<
<
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
>







 







|





|
|
|
|
|
|
|
|
|
|
<
<

|


|
<
<
<
<
|
|
|
|
<
>
|
<
>
>
>







 







|







 







|
|
<







 







|







 







<
<







 







>
>







 







>
>
>
>
>
>







 







|
<








|
<







|




|







 







|
|







 







<
<
<
<
<
<



<
<
<
<
<
<





|
<












<
<
<
<
<
<
<
<
<
<
<







 







|







 







|



|









|









|







 







|







 







|
|







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|







 







|







 







|
|













|












|





|
>
>
|
>
>
>
|
<
>
|
|







 







|
<
<

<
>

<
>
>


>












|
|
|
<
|
<
>
|

>
>
>
>

<
>

|
|







 







|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

<
|

<
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>


<
<
>
>

<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

<
<
<
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
|
<



|





|

>


|
<

<

>
|
>
>







 







|
<











|





<
<
<



>
>
|
|





>
>
>



|
|
|
|
|
|
>












|



|
|
|

|

|

|


<
<







<
|





<
|







|
|
|


|
<




|


|


|







>







 







|
>

>













|
>

>







 







|


|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
<




|











<
<
<

<
|
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|

<



>
>
|
|
|


|

|
|
<
|
<
>


|
|



|
|







|

|


|
|

|







 







|







|
|


|
|


|


|












|


|



|
|
|


|
<
<
<
<
|


|

|
|

|
|
|





|


|




|

|
|







|
|
|


|
|
|
|
|
|




|
>
>
|
<





|



|
|
>
|
>
|

|
|



|







 







|

|
|
|





|
|
|
|
|





|








|
|

|

|

|




|
|
|




|
|
|

|
|
|
|
|
|




|
|
|
|
|




|
|

<
|
|
>
>





|




|



|

|


|






|
|








|






|

<
|
|
>
>
|
|
|
|




|
|
|




|







|

|
|






|

|
|





|

|
|






|


|


|

|
|
>
>
>
|

|
>
|
>






|

|
<


|

<
|
<


|



|

|
|


|

|
|
|
<













|






|






>
>
>
>
|







 







<
|
>

>










|
|
|
|
|
|
|
|







 







|

<







 







|



<




|










|


|







 







|
|







 







|
<
<



<







 







|
|
|
<
|

>
|
|
|

<







 







>







 







|







 







|
>
|
<
>
>
>
>
>





<







 







|

|
|
|
|
|
|
>
|
<
>
|
|



<
|
<
<
<












|





|
|

|




|
|

|
|
|
<
<
>







660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
....
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
....
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347

3348
3349
3350

3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387


3388
3389
3390
3391
3392
3393






3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410














3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
....
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521


3522
3523
3524
3525
3526




3527
3528
3529
3530

3531
3532

3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
....
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
....
6236
6237
6238
6239
6240
6241
6242
6243
6244

6245
6246
6247
6248
6249
6250
6251
....
6743
6744
6745
6746
6747
6748
6749
6750
6751
6752
6753
6754
6755
6756
6757
....
7492
7493
7494
7495
7496
7497
7498


7499
7500
7501
7502
7503
7504
7505
....
7604
7605
7606
7607
7608
7609
7610
7611
7612
7613
7614
7615
7616
7617
7618
7619
....
7646
7647
7648
7649
7650
7651
7652
7653
7654
7655
7656
7657
7658
7659
7660
7661
7662
7663
7664
7665
....
7740
7741
7742
7743
7744
7745
7746
7747

7748
7749
7750
7751
7752
7753
7754
7755
7756

7757
7758
7759
7760
7761
7762
7763
7764
7765
7766
7767
7768
7769
7770
7771
7772
7773
7774
7775
7776
....
7878
7879
7880
7881
7882
7883
7884
7885
7886
7887
7888
7889
7890
7891
7892
7893
....
7978
7979
7980
7981
7982
7983
7984






7985
7986
7987






7988
7989
7990
7991
7992
7993

7994
7995
7996
7997
7998
7999
8000
8001
8002
8003
8004
8005











8006
8007
8008
8009
8010
8011
8012
....
8101
8102
8103
8104
8105
8106
8107
8108
8109
8110
8111
8112
8113
8114
8115
....
8138
8139
8140
8141
8142
8143
8144
8145
8146
8147
8148
8149
8150
8151
8152
8153
8154
8155
8156
8157
8158
8159
8160
8161
8162
8163
8164
8165
8166
8167
8168
8169
8170
8171
8172
8173
8174
8175
8176
....
9210
9211
9212
9213
9214
9215
9216
9217
9218
9219
9220
9221
9222
9223
9224
....
9240
9241
9242
9243
9244
9245
9246
9247
9248
9249
9250
9251
9252
9253
9254
9255
....
9843
9844
9845
9846
9847
9848
9849

















9850
9851
9852
9853
9854
9855
9856
....
9947
9948
9949
9950
9951
9952
9953
9954
9955
9956
9957
9958
9959
9960
9961
.....
19325
19326
19327
19328
19329
19330
19331
19332
19333
19334
19335
19336
19337
19338
19339
.....
20461
20462
20463
20464
20465
20466
20467
20468
20469
20470
20471
20472
20473
20474
20475
20476
20477
20478
20479
20480
20481
20482
20483
20484
20485
20486
20487
20488
20489
20490
20491
20492
20493
20494
20495
20496
20497
20498
20499
20500
20501
20502
20503
20504
20505
20506
20507
20508
20509

20510
20511
20512
20513
20514
20515
20516
20517
20518
20519
.....
20580
20581
20582
20583
20584
20585
20586
20587


20588

20589
20590

20591
20592
20593
20594
20595
20596
20597
20598
20599
20600
20601
20602
20603
20604
20605
20606
20607
20608
20609
20610

20611

20612
20613
20614
20615
20616
20617
20618
20619

20620
20621
20622
20623
20624
20625
20626
20627
20628
20629
20630
.....
21181
21182
21183
21184
21185
21186
21187
21188
21189
21190
21191
21192
21193
21194
21195
21196
21197
21198
21199
21200
21201
21202
21203
21204
21205
21206
21207
21208
21209
21210
21211
21212
21213
21214
21215
21216
21217
21218
21219
21220
21221
21222
21223
21224
21225
21226
21227
21228
21229
21230
21231
21232
21233
21234
21235
21236
21237
21238
21239
21240
21241
21242
21243
21244

21245
21246

21247
21248
21249
21250
21251
21252
21253
21254
21255
21256
21257
21258
21259
21260
21261
21262
21263
21264
21265
21266
21267
21268
21269
21270
21271
21272


21273
21274
21275

21276
21277
21278
21279
21280
21281
21282
21283
21284
21285
21286
21287
21288
21289
21290
21291
21292
21293



21294
21295
21296
21297
21298
21299
21300
21301
21302
21303
21304
21305
21306
21307
21308
21309
21310
21311
21312
21313
21314
21315
21316

21317
21318
21319
21320
21321
21322
21323
21324
21325
21326
21327
21328
21329
21330
21331

21332

21333
21334
21335
21336
21337
21338
21339
21340
21341
21342
21343
21344
.....
21367
21368
21369
21370
21371
21372
21373
21374

21375
21376
21377
21378
21379
21380
21381
21382
21383
21384
21385
21386
21387
21388
21389
21390
21391



21392
21393
21394
21395
21396
21397
21398
21399
21400
21401
21402
21403
21404
21405
21406
21407
21408
21409
21410
21411
21412
21413
21414
21415
21416
21417
21418
21419
21420
21421
21422
21423
21424
21425
21426
21427
21428
21429
21430
21431
21432
21433
21434
21435
21436
21437
21438
21439
21440
21441
21442
21443


21444
21445
21446
21447
21448
21449
21450

21451
21452
21453
21454
21455
21456

21457
21458
21459
21460
21461
21462
21463
21464
21465
21466
21467
21468
21469
21470

21471
21472
21473
21474
21475
21476
21477
21478
21479
21480
21481
21482
21483
21484
21485
21486
21487
21488
21489
21490
21491
21492
21493
21494
21495
21496
.....
21547
21548
21549
21550
21551
21552
21553
21554
21555
21556
21557
21558
21559
21560
21561
21562
21563
21564
21565
21566
21567
21568
21569
21570
21571
21572
21573
21574
21575
21576
21577
21578
21579
21580
21581
.....
21605
21606
21607
21608
21609
21610
21611
21612
21613
21614
21615



























21616
21617
21618
21619
21620
21621
21622
.....
21625
21626
21627
21628
21629
21630
21631
21632

21633
21634
21635
21636
21637
21638
21639
21640
21641
21642
21643
21644
21645
21646
21647
21648



21649

21650



21651


















21652
21653

21654
21655
21656
21657
21658
21659
21660
21661
21662
21663
21664
21665
21666
21667

21668

21669
21670
21671
21672
21673
21674
21675
21676
21677
21678
21679
21680
21681
21682
21683
21684
21685
21686
21687
21688
21689
21690
21691
21692
21693
21694
21695
21696
21697
21698
21699
21700
21701
.....
21720
21721
21722
21723
21724
21725
21726
21727
21728
21729
21730
21731
21732
21733
21734
21735
21736
21737
21738
21739
21740
21741
21742
21743
21744
21745
21746
21747
21748
21749
21750
21751
21752
21753
21754
21755
21756
21757
21758
21759
21760
21761
21762
21763
21764
21765
21766
21767
21768
21769
21770
21771




21772
21773
21774
21775
21776
21777
21778
21779
21780
21781
21782
21783
21784
21785
21786
21787
21788
21789
21790
21791
21792
21793
21794
21795
21796
21797
21798
21799
21800
21801
21802
21803
21804
21805
21806
21807
21808
21809
21810
21811
21812
21813
21814
21815
21816
21817
21818
21819
21820
21821
21822
21823
21824
21825

21826
21827
21828
21829
21830
21831
21832
21833
21834
21835
21836
21837
21838
21839
21840
21841
21842
21843
21844
21845
21846
21847
21848
21849
21850
21851
21852
21853
21854
.....
21869
21870
21871
21872
21873
21874
21875
21876
21877
21878
21879
21880
21881
21882
21883
21884
21885
21886
21887
21888
21889
21890
21891
21892
21893
21894
21895
21896
21897
21898
21899
21900
21901
21902
21903
21904
21905
21906
21907
21908
21909
21910
21911
21912
21913
21914
21915
21916
21917
21918
21919
21920
21921
21922
21923
21924
21925
21926
21927
21928
21929
21930
21931
21932
21933
21934
21935
21936
21937
21938
21939
21940
21941
21942
21943
21944
21945
21946
21947
21948
21949

21950
21951
21952
21953
21954
21955
21956
21957
21958
21959
21960
21961
21962
21963
21964
21965
21966
21967
21968
21969
21970
21971
21972
21973
21974
21975
21976
21977
21978
21979
21980
21981
21982
21983
21984
21985
21986
21987
21988
21989
21990
21991
21992
21993
21994
21995
21996
21997
21998

21999
22000
22001
22002
22003
22004
22005
22006
22007
22008
22009
22010
22011
22012
22013
22014
22015
22016
22017
22018
22019
22020
22021
22022
22023
22024
22025
22026
22027
22028
22029
22030
22031
22032
22033
22034
22035
22036
22037
22038
22039
22040
22041
22042
22043
22044
22045
22046
22047
22048
22049
22050
22051
22052
22053
22054
22055
22056
22057
22058
22059
22060
22061
22062
22063
22064
22065
22066
22067
22068
22069
22070
22071
22072
22073
22074
22075
22076
22077
22078
22079
22080
22081
22082

22083
22084
22085
22086

22087

22088
22089
22090
22091
22092
22093
22094
22095
22096
22097
22098
22099
22100
22101
22102
22103
22104

22105
22106
22107
22108
22109
22110
22111
22112
22113
22114
22115
22116
22117
22118
22119
22120
22121
22122
22123
22124
22125
22126
22127
22128
22129
22130
22131
22132
22133
22134
22135
22136
22137
22138
22139
22140
22141
22142
22143
.....
22153
22154
22155
22156
22157
22158
22159

22160
22161
22162
22163
22164
22165
22166
22167
22168
22169
22170
22171
22172
22173
22174
22175
22176
22177
22178
22179
22180
22181
22182
22183
22184
22185
22186
22187
22188
.....
22247
22248
22249
22250
22251
22252
22253
22254
22255

22256
22257
22258
22259
22260
22261
22262
.....
22275
22276
22277
22278
22279
22280
22281
22282
22283
22284
22285

22286
22287
22288
22289
22290
22291
22292
22293
22294
22295
22296
22297
22298
22299
22300
22301
22302
22303
22304
22305
22306
22307
22308
22309
22310
22311
.....
22312
22313
22314
22315
22316
22317
22318
22319
22320
22321
22322
22323
22324
22325
22326
22327
.....
22702
22703
22704
22705
22706
22707
22708
22709


22710
22711
22712

22713
22714
22715
22716
22717
22718
22719
.....
22721
22722
22723
22724
22725
22726
22727
22728
22729
22730

22731
22732
22733
22734
22735
22736
22737

22738
22739
22740
22741
22742
22743
22744
.....
22753
22754
22755
22756
22757
22758
22759
22760
22761
22762
22763
22764
22765
22766
22767
.....
22821
22822
22823
22824
22825
22826
22827
22828
22829
22830
22831
22832
22833
22834
22835
.....
29355
29356
29357
29358
29359
29360
29361
29362
29363
29364

29365
29366
29367
29368
29369
29370
29371
29372
29373
29374

29375
29376
29377
29378
29379
29380
29381
.....
29383
29384
29385
29386
29387
29388
29389
29390
29391
29392
29393
29394
29395
29396
29397
29398
29399

29400
29401
29402
29403
29404
29405

29406



29407
29408
29409
29410
29411
29412
29413
29414
29415
29416
29417
29418
29419
29420
29421
29422
29423
29424
29425
29426
29427
29428
29429
29430
29431
29432
29433
29434
29435
29436
29437
29438


29439
29440
29441
29442
29443
29444
29445
29446
      indeed no implementation requirement to force a specific class type
    - added aUseBatchMode optional parameter to TSQLRecordMany.ManyDelete() method
    - now JSON parser will handle #1..' ' chars as whitespace (not only ' ')
    - now huge service JSON response is truncated (to default 20 KB) in logs

  Version 1.18
    - renamed SQLite3Commons.pas to mORMot.pas
    - BREAKING CHANGE in TSQLRestServerCallBackParams use: all method-based
      services should now be a procedure, and use aParams.Results()/Error()
      methods to return the content - new definition of aParams features now
      full access to incoming/outgoing context and parameters
    - deep code refactoring, introducing TSQLPropInfo* classes in order to
      decouple the ORM definitions from the RTTI - will allow definition of
      any class members, even if there is no RTTI generated or via custom
      properties attributes or a fluent interface
    - introduced more readable "stored AS_UNIQUE" published property definition
      in TSQLRecord (via the explicit AS_UNIQUE=false constant)
    - introducing TInterfaceStub and TInterfaceMock classes to define
      high-performance interface stubbing and mocking via a fluent interface
    - integrated Windows Authentication to the mORMot Client-Server layer: in
      order to enable it, define a SSPIAUTH conditional and call
      TSQLRestClientURI.SetUser() with an empty user name, and ensure that
      TSQLAuthUser.LoginName contains a matching 'DomainName\UserName' value
    - TSQLRestServer.URI() method uses now one TSQLRestServerURIParams parameter
    - added TAuthSession.SentHeaders and RemoteIP properties (for HTTP)
    - added process of Variant and WideString types in TSQLRecord properties
    - added JSON serialization of Variant and WideString types in JSONToObject()
      / ObjectToJSON() functions and WriteObject method
    - TSQLRestClientURINamedPipe and TSQLRestClientURIMessage are now thread-safe
      (i.e. protected by a system mutex) therefore can be called from a
      multi-threaded process, e.g. TSQLRestServer instances as proxies
    - modified named pipe client side to avoid unexpected file not found errors
................................................................................

  /// maximum handled dimension for TSQLRecordRTree
  // - this value is the one used by SQLite3 R-Tree virtual table
  RTREE_MAX_DIMENSION = 5;

  /// used as "stored AS_UNIQUE" published property definition in TSQLRecord 
  AS_UNIQUE = false;

  /// HTML Status Code for "Success"
  HTML_SUCCESS = 200;
  /// HTML Status Code for "Created"
  HTML_CREATED = 201;
  /// HTML Status Code for "Not Modified"
  HTML_NOTMODIFIED = 304;
  /// HTML Status Code for "Bad Request"
  HTML_BADREQUEST = 400;
  /// HTML Status Code for "Forbidden"
  HTML_FORBIDDEN = 403;
  /// HTML Status Code for "Not Found"
  HTML_NOTFOUND = 404;
  /// HTML Status Code for "Request Time-out"
  HTML_TIMEOUT = 408;
  /// HTML Status Code for "Internal Server Error"
  HTML_SERVERERROR = 500;
  /// HTML Status Code for "Not Implemented"
  HTML_NOTIMPLEMENTED = 501;
  /// HTML Status Code for "Service Unavailable"
  HTML_UNAVAILABLE = 503;


type
  /// generic parent class of all custom Exception types of this unit
  EORMException = class(ESynException);

  /// exception raised in case of wrong Model definition
  EModelException = class(EORMException);
................................................................................
    // - will return the specified associated TSynFilterOrValidate instance
    function AddFilterOrValidate(const aFieldName: RawUTF8;
      aFilter: TSynFilterOrValidate): TSynFilterOrValidate; overload;
      {$ifdef HASINLINE}inline;{$endif}
  end;

  TAuthSession = class;
  PSQLAccessRights = ^TSQLAccessRights;

  /// the available THTTP methods transmitted between client and server
  TSQLURIMethod = (mNone, mGET, mPOST, mPUT, mDELETE, mBEGIN, mEND,
    mABORT, mLOCK, mUNLOCK, mSTATE);


  /// store all parameters for a TSQLRestServer.URI() method call
  // - see TSQLRestClient to check how data is expected in our RESTful format
  TSQLRestServerURIParams = packed record

    /// input parameter containing the caller URI
    Url: RawUTF8;
    /// input parameter containing the caller method
    // - handle enhanced REST codes: LOCK/UNLOCK/BEGIN/END/ABORT
    Method: RawUTF8;
    /// input parameter containing the caller message headers
    InHead: RawUTF8;
    /// input parameter containing the caller message body
    // - e.g. some GET/POST/PUT JSON data can be specified here
    InBody: RawUTF8;
    /// output parameter to be set to the response message header
    OutHead: RawUTF8;
    /// output parameter to be set to the response message body
    OutBody: RawUTF8;
    /// output parameter to be set to the HTTP status integer code
    // - HTML_NOTFOUND=404 e.g. if the url doesn't start with Model.Root (caller
    // can try another TSQLRestServer)
    OutStatus: cardinal;
    /// output parameter to be set to the database internal state
    OutInternalState: cardinal;
    /// associated RESTful access rights
    // - AccessRights must be handled by the TSQLRestServer child, according
    // to the Application Security Policy (user logging, authentification and
    // rights management) - making access rights a parameter allows this method
    // to be handled as pure stateless, thread-safe and session-free
    RestAccessRights: PSQLAccessRights;
  end;

  /// used to map set of parameters for a TSQLRestServer.URI() method
  PSQLRestServerURIParams = ^TSQLRestServerURIParams;

  /// store all parameters for a TSQLRestServerCallBack event handler
  // - having a dedicated record avoid changing the implementation methods
  // signature if the framework add some parameters to this structure
  // - see TSQLRestServerCallBack for general code use
  {$ifdef UNICODE}
  TSQLRestServerCallBackParams = record


  {$else}
  TSQLRestServerCallBackParams = object
  {$endif}
    /// the used Client-Server method (matching the corresponding HTTP Verb)
    // - this property will be set from incoming URI, even if RESTful
    // authentication is not enabled






    Method: TSQLURIMethod;
    /// the URI address, just before parameters
    // - can be either the table name (in RESTful protocol), or a service name
    URI: RawUTF8;
    /// the Table as specified at the URI level (if any)
    Table: TSQLRecordClass;
    /// the index in the Model of the Table specified at the URI level (if any)
    TableIndex: integer;
    /// the associated TSQLRecord.ID, as decoded from URI scheme
    // - this property will be set from incoming URI, even if RESTful
    // authentication is not enabled
    ID: integer;
    /// the index of the callback published method within the internal class list
    MethodIndex: integer;
    /// URI inlined parameters
    // - use UrlDecodeValue*() functions to retrieve the values
    Parameters: PUTF8Char;














    /// access to all input/output parameters at TSQLRestServer.URI() level
    // - process should better call Results() or Success() methods to set the
    // appropriate answer or Error() method in case of an error
    // - low-level access to the call parameters can be made via this pointer
    Call: PSQLRestServerURIParams;
    /// the corresponding session TAuthSession.IDCardinal value
    // - equals 0 (CONST_AUTHENTICATION_SESSION_NOT_STARTED) if the session
    // is not started yet - i.e. if still in handshaking phase
    // - equals 1 (CONST_AUTHENTICATION_NOT_USED) if authentication mode
    // is not enabled - i.e. if TSQLRest.HandleAuthentication = FALSE
    Session: cardinal;
    /// the corresponding TAuthSession.User.ID value
    // - is undefined if Session is 0 or 1 (no authentication running)
    SessionUser: integer;
    /// the corresponding TAuthSession.User.GroupRights.ID value
    // - is undefined if Session is 0 or 1 (no authentication running)
    SessionGroup: integer;
{$ifdef WITHLOG}
    /// associated logging instance
    // - you can use it to log some process on the server side
    Log: ISynLog;
{$endif}
    /// use this method to send back directly a result value to the caller
    // - expects Status to be either HTML_SUCCESS or HTML_CREATED, and will
    // return as answer the supplied Result content with no transformation
    // - if Status is an error code, it will call Error() method
    // - CustomHeader optional parameter can be set e.g. to
    // TEXT_CONTENT_TYPE_HEADER if the default JSON_CONTENT_TYPE is not OK
    procedure Returns(const Result: RawUTF8; Status: integer=HTML_SUCCESS;
      const CustomHeader: RawUTF8=''); overload;
    /// use this method to send back a JSON object to the caller
    // - this method will encode the supplied values e.g. as
    // ! JSONEncode(['name','John','year',1972]) = '{"name":"John","year":1972}'
    // - implementation is just a wrapper around Returns(JSONEncode([]))
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    // - expects Status to be either HTML_SUCCESS or HTML_CREATED
    procedure Returns(const NameValuePairs: array of const; Status: integer=HTML_SUCCESS); overload;
    /// use this method to send back a JSON object with a "result" field
    // - this method will encode the supplied values as a {"result":"...}
    // JSON object, as such for one value:
    // $ {"result":"OneValue"}
    // (with one value, you can just call TSQLRestClientURI.CallBackGetResult
    // method to call and decode this value)
    // or as a JSON object containing an array of values:
    // $ {"result":["One","two"]}
    // - expects Status to be either HTML_SUCCESS or HTML_CREATED
    procedure Results(const Values: array of const; Status: integer=HTML_SUCCESS);
    /// use this method if the caller expect no data, just a status
    // - just wrap the overloaded Returns() method with no result value
    // - if Status is an error code, it will call Error() method
    // - by default, calling this method will mark process as successfull
    procedure Success(Status: integer=HTML_SUCCESS); 
    /// use this method to send back an error to the caller
    // - expects Status to not be HTML_SUCCESS neither HTML_CREATED,
    // and will send back a JSON error message to the caller, with the
    // supplied error text
    // - if no ErrorMessage is specified, will return a default text
    // corresponding to the Status code
    procedure Error(const ErrorMessage: RawUTF8=''; Status: integer=HTML_BADREQUEST); overload;
    /// use this method to send back an error to the caller
    // - implementation is just a wrapper over Error(FormatUTF8(Format,Args))
    procedure Error(Format: PUTF8Char; const Args: array of const; Status: integer=HTML_BADREQUEST); overload;
  end;


  (*/ method prototype which must be used to implement the Server-Side
     ModelRoot/[TableName/ID/]MethodName RESTful GET/PUT request of the Framework
   - this mechanism is able to handle some custom Client/Server request, similar
     to the DataSnap technology, but in a KISS way; it's fully integrated in the
     Client/Server architecture of our framework
   - just add a published method of this type to any TSQLRestServer descendant
   - when TSQLRestServer.URI receive a request for ModelRoot/MethodName
................................................................................
     retrieved with a loop like this:
     !  if not UrlDecodeNeedParameters(aParams.Parameters,'SORT,COUNT') then
     !    exit;
     !  while aParams.Parameters<>nil do begin
     !    UrlDecodeValue(aParams.Parameters,'SORT=',aSortString);
     !    UrlDecodeValueInteger(aParams.Parameters,'COUNT=',aCountInteger,@aParams.Parameters);
     !  end;
   - aParams.Call.InBody is set with bulk incoming data from the GET/PUT method
   - aParams.Context will identify to the authentication session of the remote client
     (CONST_AUTHENTICATION_NOT_USED=1 if authentication mode is not enabled or
     CONST_AUTHENTICATION_SESSION_NOT_STARTED=0 if the session not started yet) -
     code may use SessionGetUser() protected method to retrieve the user details
   - aParams.Context.Method will indicate the used HTTP verb (e.g. GET/POST/PUT..)
   - if process succeeded, implementation shall call aParams.Results([]) method to
     set a JSON response object with one "result" field name or aParams.Returns([])
     with a JSON object described in Name/Value pairs; if the returned value is
     not JSON_CONTENT_TYPE, use aParams.Returns() and its optional CustomHeader
     parameter can specify a custom header like TEXT_CONTENT_TYPE_HEADER
   - if process succeeded, and no data is expected to be returned to the caller,
     implementation shall call overloaded aParams.Success() method with the
     expected status (i.e. just aParams.Success will return HTML_SUCCESS)
   - if process failed, implementation shall call aParams.Error() method to
     set the corresponding error message and error code number


   - a typical implementation may be:
     ! procedure TSQLRestServerTest.Sum(var aParams: TSQLRestServerCallBackParams);
     ! var a,b: Extended;
     ! begin
     !   if UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin




     !     while Params.Parameters<>nil do begin
     !       UrlDecodeExtended(aParams.Parameters,'A=',a);
     !       UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
     !     end;

     !     aParams.Results([a+b]);
     !     // same as: aParams.Returns(JSONEncode(['result',a+b]));

     !     // same as: aParams.Returns(['result',a+b]);
     !   end else
     !     aParams.Error('Missing Parameter');
     ! end;
   - Client-Side can be implemented as you wish. By convention, it could be
     appropriate to define in either TSQLRestServer (if to be called as
     ModelRoot/MethodName), either TSQLRecord (if to be called as
     ModelRoot/TableName/MethodName[/ID]) a custom public or protected method,
     calling TSQLRestClientURI.URL with the appropriate parameters, and named
     (by convention) as MethodName; TSQLRestClientURI has dedicated methods
................................................................................
     like CallBackGetResult, CallBackGet, and CallBackPut; see also
     TSQLModel.getURICallBack and JSONDecode function
     ! function TSQLRecordPeople.Sum(aClient: TSQLRestClientURI; a, b: double): double;
     ! var err: integer;
     ! begin
     !   val(aClient.CallBackGetResult('sum',['a',a,'b',b]),result,err);
     ! end; *)
  TSQLRestServerCallBack = procedure(var aParams: TSQLRestServerCallBackParams) of object;

  /// the possible options for handling table names
  TSQLCheckTableName = (ctnNoCheck,ctnMustExist,ctnTrimExisting);

  /// internal data used by TSQLRecord.FillPrepare()/FillPrepareMany() methods
  // - using a dedicated class will reduce memory usage for each TSQLRecord
  // instance (which won't need these properties most of the time)
................................................................................
    // - on success, aResp shall contain a serialized JSON object with one
    // nested result property, which may be a JSON array, containing the
    // all "var" or "out" parameters values, and then the method main result -
    // for instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    procedure ExecuteMethod(var aParams: TSQLRestServerCallBackParams;
      aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char);

    /// this method will create an implementation instance
    function CreateInstance: TInterfacedObject;
  public
    /// initialize the service provider on the server side
    // - expect an direct server-side implementation class (inheriting from
    // TInterfacedClass or from TInterfacedObjectWithCustomCreate if you need
    // an overriden constructor)
................................................................................
    function InternalDelete(Table: TSQLRecordClass; const SQLWhere: RawUTF8;
      var IDs: TIntegerDynArray): boolean; 
    /// wait for the transaction critical section to be acquired
    // - used to implement a thread-safe and transaction-safe write to the DB
    // - returns FALSE in case of time out (see AcquireWriteTimeOut property)
    // - returns TRUE if it's safe to write to the DB - in this case, you must
    // call ReleaseWrite when done to release the fTransactionCriticalSession
    function AcquireWrite(const aContext: TSQLRestServerCallBackParams): Boolean;
    /// release the fTransactionCriticalSession
    procedure ReleaseWrite;
    /// retrieve the server time stamp
    // - default implementation will use fServerTimeStampOffset to compute
    // the value from PC time (i.e. Now+fServerTimeStampOffset as TTimeLog)
    // - inherited classes may override this method, or set the appropriate
    // value in fServerTimeStampOffset protected field
................................................................................
    // - use the TSQLAuthGroup.AccessRights CSV format
    function ToString: RawUTF8;
    /// unserialize the content from TEXT
    // - use the TSQLAuthGroup.AccessRights CSV format
    procedure FromString(P: PUTF8Char);
  end;



  TSQLRestServerStatic = class;
  TSQLRestServerStaticClass = class of TSQLRestServerStatic;
  TSQLRestServerStaticInMemory = class;
  TSQLVirtualTableModule = class;


  {/ table containing the available user access rights for authentication
................................................................................
    fLastAccess: cardinal;
    fID: RawUTF8;
    fIDCardinal: cardinal;
    fTimeOut: cardinal;
    fAccessRights: TSQLAccessRights;
    fPrivateKey: RawUTF8;
    fPrivateSalt: RawUTF8;
    fSentHeaders: RawUTF8;
    fRemoteIP: RawUTF8;
    fPrivateSaltHash: Cardinal;
    fLastTimeStamp: Cardinal;
  public
    /// initialize a session instance with the supplied TSQLAuthUser instance
    // - this aUser instance will be handled by the class until Destroy
    // - raise an exception on any error
    // - on success, will also retrieve the aUser.Data BLOB field content
................................................................................
    /// the number of millisedons a session is kept alive
    // - extracted from User.TSQLAuthGroup.SessionTimeout
    // - allow direct comparison with GetTickCount API call
    property Timeout: cardinal read fTimeOut;
    /// the hexadecimal private key as returned to the connected client
    // as 'SessionID+PrivateKey'
    property PrivateKey: RawUTF8 read fPrivateKey;
    /// the transmitted HTTP headers, if any
    // - can contain e.g. 'RemoteIP: 127.0.0.1' or 'User-Agent: Mozilla/4.0'
    property SentHeaders: RawUTF8 read fSentHeaders;
    /// the remote IP, if any
    // - is extracted from SentHeaders properties
    property RemoteIP: RawUTF8 read fRemoteIP;
  end;

  { we need the RTTI information to be compiled for the published methods
    of this TSQLRestServer class and its children (like TPersistent), to
    enable Server-Side ModelRoot/[TableName/[ID/]]MethodName requests
      -> see TSQLRestServerCallBack }

................................................................................
    /// this method is overriden for setting the NoAJAXJSON field
    // of all associated TSQLRestServerStatic servers
    procedure SetNoAJAXJSON(const Value: boolean); virtual;
    /// search for the corresponding TSQLRestServerCallBack in its published methods,
    // then launch it
    // - the aParams parameters will be used to set a default header for the callback
    // - return TRUE if the method was found and run, FALSE if method was not found
    function LaunchCallBack(var aParams: TSQLRestServerCallBackParams): boolean;

    /// try to call a Service from a given URI
    // - this method will call any interface-based service previously registered
    // via ServerRegister()
    // - returns TRUE if the supplied method was a service name, and an error
    // code is returned in aResult (with an optional message in aErrorMsg)
    // - is in fact used internaly by the URI method: you are not likely to call
    // this method, but should rather call e.g. Services['Calculator'].Get(I)
    // to retrieve a working service interface to work with
    function LaunchService(var aParams: TSQLRestServerCallBackParams): boolean;

    /// execute a BATCH sequence
    // - expect input as generated by TSQLRestClientURI.Batch*() methods:
    // & '{"Table":["cmd":values,...]}'
    // or, in a table-independent way:
    // & '["cmd@table":values,...]'
    // - returns an array of integers: '[200,200,...]'
    function RunBatch(aStatic: TSQLRestServerStatic; aTable: TSQLRecordClass;
      var aCall: TSQLRestServerCallBackParams): boolean;
    /// fill the supplied context from the supplied aContext.Session ID
    // - returns nil if not found, or fill aContext.User/Group values if matchs
    // - this method will also check for outdated sessions, and delete them
    // - this method is not thread-safe: caller should use fSessionCriticalSection
    function SessionAccess(var aContext: TSQLRestServerCallBackParams): TAuthSession;
    /// delete a session from its index in fSessions[]
    // - will perform any needed clean-up, and log the event
    // - this method is not thread-safe: caller should use fSessionCriticalSection
    procedure SessionDelete(aSessionIndex: integer);
    /// returns a copy of the user associated to a session ID
    // - returns nil if the session does not exist (e.g. if authentication is
    // disabled)
................................................................................

{$ifdef MSWINDOWS}
    /// declare the server on the local machine as a Named Pipe: allows
    // TSQLRestClientURINamedPipe local or remote client connection
    // - ServerApplicationName ('DBSERVER' e.g.) will be used to create a named
    // pipe server identifier, it is of UnicodeString type since Delphi 2009
    // (use of Unicode FileOpen() version)
    // - this server identifier is appended to '\\.\pipe\mORMot_' to obtain
    // the full pipe name to initiate ('\\.\pipe\mORMot_DBSERVER' e.g.)
    // - this server identifier may also contain a fully qualified path
    // ('\\.\pipe\ApplicationName' e.g.)
    // - allows only one ExportServer*() by running process
    // - returns true on success, false otherwize (ServerApplicationName already used?)
    function ExportServerNamedPipe(const ServerApplicationName: TFileName): boolean;
    /// end any currently initialized named pipe server
    function CloseServerNamedPipe: boolean;
................................................................................
    // if you call it from the main thread, it may fail to release resources
    // - it is set e.g. by TSQLite3HttpServer to be called from HTTP threads,
    // or by TSQLRestServerNamedPipeResponse for named-pipe server cleaning
    procedure EndCurrentThread(Sender: TObject); virtual;

    /// implement a generic local, piped or HTTP/1.1 provider
    // - this is the main entry point of the server, from the client side






    // - default implementation calls protected methods EngineList() Retrieve()
    // Add() Update() Delete() UnLock() EngineExecute() above, which must be overriden by
    // the TSQLRestServer child






    // - for 'GET ModelRoot/TableName', url parameters can be either "select" and
    // "where" (to specify a SQL Query, from the SQLFromSelectWhere function),
    // either "sort", "dir", "startIndex", "results", as expected by the YUI
    // DataSource Request Syntax for data pagination - see
    // http://developer.yahoo.com/yui/datatable/#data
    procedure URI(var Call: TSQLRestServerURIParams); virtual;


    /// create an index for the specific FieldName
    // - will call CreateSQLMultiIndex() internaly
    function CreateSQLIndex(Table: TSQLRecordClass; const FieldName: RawUTF8;
      Unique: boolean; const IndexName: RawUTF8=''): boolean; overload;
    /// create one or multiple index(es) for the specific FieldName(s)
    function CreateSQLIndex(Table: TSQLRecordClass; const FieldNames: array of RawUTF8;
      Unique: boolean): boolean; overload;
    /// create one index for all specific FieldNames at once
    function CreateSQLMultiIndex(Table: TSQLRecordClass; const FieldNames: array of RawUTF8;
      Unique: boolean; IndexName: RawUTF8=''): boolean; virtual;












    /// call this method to disable Authentication method check for a given
    // published method name
    // - by default, only Auth and TimeStamp methods do not require the RESTful
    // authentication of the URI; you may call this method to add another method
    // to the list (e.g. for returning some HTML content from a public URI)
    procedure ServiceMethodByPassAuthentication(const aMethodName: RawUTF8);

................................................................................
    // (should not to be used normaly, because it will add unnecessary overhead)
    property StaticVirtualTableDirect: boolean read fVirtualTableDirect
      write fVirtualTableDirect;
  published
    /// this method will be accessible from ModelRoot/Stat URI, and
    // will retrieve some statistics as a JSON object
    // - method parameters signature matches TSQLRestServerCallBack type
    procedure Stat(var aParams: TSQLRestServerCallBackParams);
    /// this method will be accessible from ModelRoot/Auth URI, and
    // will be called by the client for authentication and session management
    // - method parameters signature matches TSQLRestServerCallBack type
    // - to be called in a two pass "challenging" algorithm:
    // $ GET ModelRoot/auth?UserName=...
    // $  -> returns an hexadecimal nonce contents (valid for 5 minutes)
    // $ GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=...
................................................................................
    // user which opened the session via a successful call to the Auth service
    // - when you don't need the session any more (e.g. if the TSQLRestClientURI
    // instance is destroyed), you can call the service as such:
    // $ GET ModelRoot/auth?UserName=...&Session=...
    // - for a way of computing SHA-256 in JavaScript, see for instance
    // @http://www.webtoolkit.info/javascript-sha256.html
    // - this global callback method is thread-safe
    procedure Auth(var aParams: TSQLRestServerCallBackParams);
    /// this method will be accessible from the ModelRoot/TimeStamp URI, and
    // will return the server time stamp TTimeLog/Int64 value as RawUTF8
    // - method parameters signature matches TSQLRestServerCallBack type
    procedure TimeStamp(var aParams: TSQLRestServerCallBackParams);
    /// this method will be accessible from the ModelRoot/CacheFlush URI, and
    // will flush the server cache
    // - this method shall be called by the clients when the Server cache could
    // be not refreshed
    // - ModelRoot/CacheFlush URI will flush the whole Server cache, for all tables
    // - ModelRoot/CacheFlush/TableName URI will flush the specified table cache
    // - ModelRoot/CacheFlush/TableName/ID URI will flush the content of the
    // specified record
    // - method parameters signature matches TSQLRestServerCallBack type
    procedure CacheFlush(var aParams: TSQLRestServerCallBackParams);
    /// this method will be accessible from the ModelRoot/Batch URI, and
    // will execute a set of RESTful commands
    // - expect input as JSON commands - see TSQLRestServer.RunBatch, i.e.
    // & '{"Table":["cmd":values,...]}'
    // or for multiple tables:
    // & '["cmd@Table":values,...]'
    // with cmd in POST/PUT with {object} as value or DELETE with ID
    // - only accepted context HTTP verb is PUT (for thread-safe and security
    // reasons)
    procedure Batch(var aParams: TSQLRestServerCallBackParams);
  end;

  /// REST server with direct access to an external database engine
  // - you can set an alternate per-table database engine by using this class
  // - this abstract class is to be overriden with a proper implementation (like
  // our TSQLRestServerStaticInMemory class)
  TSQLRestServerStatic = class(TSQLRestServer)
................................................................................
  end;

  /// Rest client with remote access to a server through a Named Pipe
  // - named pipe is fast and optimized under Windows
  // - can be accessed localy or remotely
  TSQLRestClientURINamedPipe = class(TSQLRestClientURI)
  private
    /// handle for '\\.\pipe\mORMot_TEST' e.g.
    fServerPipe: cardinal;
    /// the pipe name
    fPipeName: TFileName;
{$ifndef ANONYMOUSNAMEDPIPE}
  {$ifndef NOSECURITYFORNAMEDPIPECLIENTS}
    fPipeSecurityAttributes: TSecurityAttributes;
    fPipeSecurityDescriptor: array[0..SECURITY_DESCRIPTOR_MIN_LENGTH] of byte;
................................................................................
  public
    /// connect to a server contained in a running application
    // - the server must have been declared by a previous
    // TSQLRestServer.ExportServer(ApplicationName) call
    // with ApplicationName as user-defined server identifier ('DBSERVER' e.g.)
    // - ApplicationName is of UnicodeString type since Delphi 2009
    // (direct use of Wide Win32 API version)
    // - this server identifier is appended to '\\.\pipe\mORMot_' to obtain
    // the full pipe name to connect to ('\\.\pipe\mORMot__DBSERVER' e.g.)
    // - this server identifier may also contain a remote computer name, and
    // must be fully qualified ('\\ServerName\pipe\ApplicationName' e.g.)
    // - raise an exception if the server is not running or invalid
    constructor Create(aModel: TSQLModel; const ApplicationName: TFileName);
    /// release memory and handles
    destructor Destroy; override;
  end;
................................................................................
  // and is handled in TSQLRibbon.RefreshClickHandled
  WM_TIMER_REFRESH_SCREEN = 1;
  /// timer identifier which indicates we must refresh the Report content
  // - used for User Interface generation
  // - is handled in TSQLRibbon.RefreshClickHandled
  WM_TIMER_REFRESH_REPORT = 2;



















/// create a TRecordReference with the corresponding parameters
function RecordReference(Model: TSQLModel; aTable: TSQLRecordClass; aID: integer): TRecordReference;

/// convert a dynamic array of TRecordRef into its corresponding IDs
procedure RecordRefToID(var aArray: TIntegerDynArray);

................................................................................
    /// the currently running service factory
    // - it can be used within server-side implementation to retrieve the
    // associated TSQLRestServer instance
    Factory: TServiceFactoryServer;
    /// the currently runnning session identifier which launched the method
    // - make available the current session or authentication parameters
    // (including e.g. user details via Factory.RestServer.SessionGetUser)
    Session: ^TSQLRestServerCallBackParams;
    /// the thread which launched the request
    // - is set by TSQLRestServer.BeginCurrentThread from multi-thread server
    // handlers - e.g. TSQLite3HttpServer or TSQLRestServerNamedPipeResponse
    RunningThread: TThread;
  end;

threadvar
................................................................................
    WhereClause := FormatUTF8('% MATCH :(''%''): ORDER BY rank(matchinfo(%)',
      [SQLTableName,MatchClause,SQLTableName]);
  for i := 0 to high(PerFieldWeight) do
    WhereClause := FormatUTF8('%,:(%):',[WhereClause,PerFieldWeight[i]]);
  result := FTSMatch(Table,WhereClause+') DESC',DocID);
end;

function TSQLRest.AcquireWrite(const aContext: TSQLRestServerCallBackParams): Boolean;
var Start, Now: Cardinal;
begin
  if self<>nil then begin
    Start := GetTickCount;
    repeat
      if TryEnterCriticalSection(fTransactionCriticalSession) then begin
        if (fTransactionActive=0) or (fTransactionActive=aContext.Session) then begin
................................................................................
function TSQLRestClientURI.EngineUpdateField(Table: TSQLRecordClass;
  const SetFieldName, SetValue, WhereFieldName, WhereValue: RawUTF8): boolean;
begin
  if (self=nil) or (Table=nil) then
    result := false else
    // PUT ModelRoot/TableName?setname=..&set=..&wherename=..&where=..
    result := URI(FormatUTF8('%?setname=%&set=%&wherename=%&where=%',
      [Model.URI[Table],SetFieldName,UrlEncode(SetValue),WhereFieldName,
       UrlEncode(WhereValue)]),'PUT').Lo=HTML_SUCCESS;
end;


{ TSQLRestServer }

{$ifdef MSWINDOWS}
const
  ServerPipeNamePrefix: TFileName = '\\.\pipe\mORMot_';

var
  GlobalURIRequestServer: TSQLRestServer = nil;

function URIRequest(url, method, SendData: PUTF8Char; Resp, Head: PPUTF8Char): Int64Rec; cdecl;
function StringToPCharCopy(const s: RawUTF8): PUTF8Char;
var L: integer;
begin
  L := length(s);
  if L=0 then
    result := nil else begin
    inc(L); // copy also last #0 from s
    if USEFASTMM4ALLOC then
      GetMem(result,L) else
      result := pointer(GlobalAlloc(GMEM_FIXED,L));
    move(pointer(s)^,result^,L);
  end;
end;
var call: TSQLRestServerURIParams;
begin
  if GlobalURIRequestServer=nil then begin
    Int64(result) := HTML_NOTIMPLEMENTED; // 501 
    exit;
  end;
  call.Url := url;
  call.Method := method;
  call.InBody := SendData;
  call.RestAccessRights := @SUPERVISOR_ACCESS_RIGHTS;
  GlobalURIRequestServer.URI(call);
  result.Lo := call.OutStatus;
  result.Hi := call.OutInternalState;
  if Head<>nil then

    Head^ := StringToPCharCopy(call.OutHead);
  if Resp<>nil then
    Resp^ := StringToPCharCopy(call.OutBody); 
end;

function ReadString(Handle: cardinal): RawUTF8;
var L, Read: cardinal;
    P: PUTF8Char;
begin
  result := '';
................................................................................
  result := true;
end;

const
  MAGIC_SYN: cardinal = $A5ABA5AB;

procedure TSQLRestServer.AnswerToMessage(var Msg: TWMCopyData);
var call: TSQLRestServerURIParams;


    P: PUTF8Char;

    Res: packed record
      Magic: cardinal;

      Status: cardinal;
      InternalState: cardinal;
    end;
    Data: TCopyDataStruct;
    ResStr: RawUTF8;
begin
  Msg.Result := HTML_NOTFOUND;
  if (self=nil) or (Msg.From=0) then
    exit;
  P := Msg.CopyDataStruct^.lpData;
  if (P=nil) or (Msg.CopyDataStruct^.cbData<=7) then
    exit;
  if PCardinal(P)^<>MAGIC_SYN then
    exit; // invalid layout: a broadcasted WM_COPYDATA message? :(
  inc(P,4);
  // #1 is a field delimiter below, since Get*Item() functions return nil for #0
  Msg.Result := HTML_SUCCESS; // Send something back
  call.Url := GetNextItem(P,#1);
  call.Method := GetNextItem(P,#1);
  call.InHead := GetNextItem(P,#1);

  with Msg.CopyDataStruct^ do

    SetString(call.InBody,P,PtrInt(cbData)-(P-lpData));
  call.RestAccessRights := @SUPERVISOR_ACCESS_RIGHTS;
  // note: it's up to URI overriden method to implement access rights
  URI(call);
  Res.Magic := MAGIC_SYN;
  Res.Status := call.OutStatus;
  Res.InternalState := call.OutInternalState;
  SetString(ResStr,PAnsiChar(@Res),sizeof(Res));

  ResStr := ResStr+call.OutHead+#1+call.OutBody;
  Data.dwData := fServerWindow;
  Data.cbData := length(ResStr);
  Data.lpData := pointer(ResStr);
  { TODO : 64-bits windows: can we store a PtrInt value in WM_COPYDATA? }
  SendMessage(Msg.From,WM_COPYDATA,fServerWindow,PtrInt(@Data));
end;

function TSQLRestServer.CloseServerNamedPipe: boolean;
begin
  if fExportServerNamedPipeThread<>nil then begin
................................................................................
begin
  result := true;
  for i := 0 to high(FieldNames) do
    if not CreateSQLMultiIndex(Table,[FieldNames[i]],Unique) then
     result := false;
end;

procedure TSQLRestServer.ServiceMethodByPassAuthentication(const aMethodName: RawUTF8);
var i: Integer;
begin
  if self=nil then
    exit;
  i :=  fPublishedMethods.Find(aMethodName);
  if i>=0 then
    AddInteger(fPublishedMethodsUnauthenticated,fPublishedMethodsUnauthenticatedCount,i,True);
end;

procedure StatusCodeToErrorMsg(Code: integer; var result: RawUTF8);
begin
  case Code of
    100:             result := 'Continue';
    HTML_SUCCESS:    result := 'OK';
    HTML_CREATED:    result := 'Created';
    202:             result := 'Accepted';
    203:             result := 'Non-Authoritative Information';
    204:             result := 'No Content';
    300:             result := 'Multiple Choices';
    301:             result := 'Moved Permanently';
    302:             result := 'Found';
    303:             result := 'See Other';
    HTML_NOTMODIFIED:result := 'Not Modified';
    307:             result := 'Temporary Redirect';
    HTML_BADREQUEST: result := 'Bad Request';
    401:             result := 'Unauthorized';
    HTML_FORBIDDEN:  result := 'Forbidden';
    HTML_NOTFOUND:   result := 'Not Found';
    405:             result := 'Method Not Allowed';
    406:             result := 'Not Acceptable';
    HTML_SERVERERROR:result := 'Internal Server Error';
    HTML_UNAVAILABLE:result := 'Service Unavailable';
    else             result := 'Invalid Request';
  end;
end;

procedure TSQLRestServerCallBackParams.Returns(const Result: RawUTF8;
  Status: integer; const CustomHeader: RawUTF8);
begin
  if Status in [HTML_SUCCESS,HTML_CREATED] then begin
    Call.OutStatus := Status;
    Call.OutBody := Result;
  end else
    Error(Result,Status);
end;

procedure TSQLRestServerCallBackParams.Returns(const NameValuePairs: array of const;
  Status: integer);
begin
  Returns(JSONEncode(NameValuePairs),Status);
end;

procedure TSQLRestServerCallBackParams.Results(const Values: array of const;
  Status: integer);
var i,h: integer;
begin

  h := high(Values);
  if h<0 then

    Call.OutBody := '{"result":null}' else
    with TTextWriter.CreateOwnedStream do
    try
      AddShort('{"result":');
      if h=0 then
        // result is one value
        AddJSONEscape(Values[0]) else begin
        // result is one array of values
        Add('[');
        i := 0;
        repeat
          AddJSONEscape(Values[i]);
          if i=h then break;
          Add(',');
          inc(i);
        until false;
        Add(']');
      end;
      Add('}');
      SetText(Call.OutBody);
    finally
      Free;
    end;
  Call.OutStatus := Status;
end;




procedure TSQLRestServerCallBackParams.Success(Status: integer);
begin

  if Status in [HTML_SUCCESS,HTML_CREATED] then
    Call.OutStatus := Status else
    Error('',Status);
end;

procedure TSQLRestServerCallBackParams.Error(Format: PUTF8Char;
  const Args: array of const; Status: integer);
begin
  Error(FormatUTF8(Format,Args),Status);
end;

procedure TSQLRestServerCallBackParams.Error(const ErrorMessage: RawUTF8; Status: integer);
var ErrorMsg: RawUTF8;
begin
  if Status in [HTML_SUCCESS,HTML_CREATED] then begin // not an error
    Call.OutStatus := Status;
    Call.OutBody := ErrorMessage;
    exit;



  end;
  if ErrorMessage='' then
    StatusCodeToErrorMsg(Status,ErrorMsg) else
    ErrorMsg := ErrorMessage;
  {$ifdef WITHLOG}
  Log.Log(sllServer,'% % ERROR=% (%)',[Call.Method,URI,Call.OutStatus,ErrorMsg]);
  {$endif}
  if Call.OutBody='' then // return error content as JSON object, if not set
    with TTextWriter.CreateOwnedStream do
    try
      AddShort('{'#13#10'"ErrorCode":');
      Add(call.OutStatus);
      AddShort(#13#10'"ErrorText":"');
      AddJSONEscape(pointer(ErrorMsg));
      AddShort('"'#13#10'}');
      SetText(Call.OutBody);
    finally
      Free;
    end;
end;


function TSQLRestServer.LaunchCallBack(var aParams: TSQLRestServerCallBackParams): boolean;

var Method: TMethod;
    Invoke: TSQLRestServerCallBack absolute Method;
begin
  result := false;
  if (aParams.MethodIndex<0) or (self=nil) then
    exit;
  Method.Code := pointer(fPublishedMethods.List[aParams.MethodIndex].Tag);
  // launch the method found
{$ifdef WITHLOG}
  aParams.Log.Log(sllServiceCall,fPublishedMethods.List[aParams.MethodIndex].Name,self);
{$endif}
  result := true; // mark method found and executed 
  try
    Method.Data := Self;
    Invoke(aParams);

    inc(fStats.fServices);

  except
    on E: Exception do // execution errors are intercepted and returned as such
      aParams.Error('Exception %: %',
       [PShortString(PPointer(PtrInt(E)+vmtClassName)^)^,E.Message],
       HTML_SERVERERROR);
  end;
end;

type
  TServiceInternalMethod = (imFree, imContract, imSignature);

const
................................................................................
    exit;
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  result := (fServices as TServiceContainerServer).AddInterface(
    aInterfaces,aInstanceCreation,aContractExpected);
end;

function TSQLRestServer.LaunchService(var aParams: TSQLRestServerCallBackParams): boolean;

var Service: TServiceFactory;
    method, JSON: RawUTF8;
    Values: TPUtf8CharDynArray;
    ServiceParams: PUTF8Char;
    i, m, ServiceID: integer;
    internal: TServiceInternalMethod;
begin
  result := false;
  if Services=nil then
    exit;
  // 1. retrieve request parameters according to routing scheme
  ServiceID := aParams.ID;
  case ServicesRouting of
  rmRest: begin
    i := Services.fListInterfaceMethod.IndexOf(aParams.URI);
    if i<0 then
      exit; // no specific message: it may be a valid request



    i := PtrInt(Services.fListInterfaceMethod.Objects[i]);
    m := i shr 16;
    Service := Services.Index(i and $ffff);
    if Service=nil then
      exit;
    if aParams.Call.InBody<>'' then  // parameters sent as JSON array (the Delphi/AJAX way)
      ServiceParams := pointer(aParams.Call.InBody) else begin
      JSON := UrlDecode(aParams.Parameters); // optional URI decoding (the HTML way)
      ServiceParams := pointer(JSON);
    end;
    if ServiceID<0 then
      ServiceID := 0;
    {$ifdef WITHLOG}
    aParams.Log.Log(sllServiceCall,aParams.URI,self);
    {$endif}
  end;
  rmJSON_RPC: begin
    Service := Services[aParams.URI];
    if Service=nil then // Unknown service
      exit; // not a valid JSON-RPC service
    JSON := aParams.Call.InBody; // in-place parsing -> private copy
    JSONDecode(JSON,['method','params','id'],Values,True);
    if Values[0]=nil then // Method name required
      exit; 
    result := true; // sounds like a valid JSON-RPC signature
    method := Values[0];
    ServiceParams := Values[1];
    ServiceID := GetCardinal(Values[2]);
    m := Service.fInterface.FindMethodIndex(method);
    if m>=0 then
      inc(m,length(SERVICE_PSEUDO_METHOD)) else begin
      for internal := low(TServiceInternalMethod) to high(TServiceInternalMethod) do
        if IdemPropNameU(method,SERVICE_PSEUDO_METHOD[internal]) then begin
          m := ord(internal);
          break;
        end;
      if m<0 then begin
        aParams.Error('Unknown method');
        exit;
      end;
    end;
    {$ifdef WITHLOG}
    aParams.Log.Log(sllServiceCall,'%.%',[aParams.URI,method],self);
    {$endif}
  end;
  else exit;
  end;
  // 2. this is a valid service call -> handle request
  if ServiceParams=nil then begin
    aParams.Error('Parameters required');
    exit;
  end;


  inc(fStats.fServices);
  case m of
  ord(imFree): // "method":"_free_" to release sicClientDriven..sicPerGroup
    if ServiceID<=0 then // expects an instance ID to be released
      exit else
      m := -1; // notify ExecuteMethod() to release the internal instance
  ord(imContract): begin // "method":"_contract_" to retrieve the implementation contract

    aParams.Returns('{"result":['+Service.ContractExpected+'],"id":0}');
    result := true;
    exit; // "id":0 for this method -> no instance was created
  end;
  ord(imSignature): begin // "method":"_signature_" to retrieve the implementation signature
    if TServiceContainerServer(Services).PublishSignature then begin

      aParams.Returns('{"result":['+Service.Contract+'],"id":0}');
      result := true; // "id":0 for this method -> no instance was created
    end;
    exit; // not allowed to publish signature
  end;
  else
    dec(m,length(SERVICE_PSEUDO_METHOD)); // index of operation in fMethods[]
  end;
  if (aParams.Session>CONST_AUTHENTICATION_NOT_USED) and (m>=0) and
     (aParams.SessionGroup-1 in Service.fExecution[m].Denied) then begin
    aParams.Error('Unauthorized method');
    exit;
  end;
  TServiceFactoryServer(Service).ExecuteMethod(aParams,m,ServiceID,ServiceParams);

  result := true; // notify method found (any error status is in aResult)
end;

function TSQLRestServer.RunBatch(aStatic: TSQLRestServerStatic;
  aTable: TSQLRecordClass; var aCall: TSQLRestServerCallBackParams): boolean;
var EndOfObject: AnsiChar;
    wasString, OK: boolean;
    TableName, Value, ErrMsg: RawUTF8;
    URIMethod, RunningBatchURIMethod: TSQLURIMethod;
    RunningBatchStatic: TSQLRestServerStatic; { TODO: allow nested batch between tables? }
    Sent, Method, MethodTable: PUTF8Char;
    Props: TSQLRecordProperties;
    i, ID, Count: integer;
    Results: TIntegerDynArray;
    RunTable: TSQLRecordClass;
    RunStatic: TSQLRestServerStatic;
begin
  result := false;
  Sent := pointer(aCall.Call.InBody);
  if (self=nil) or (Sent=nil) then
    exit;
  if aTable<>nil then begin
    // unserialize expected sequence array as '{"Table":["cmd":values,...]}'
    while Sent^<>'{' do inc(Sent);
    if Sent^<>'{' then
      exit;
................................................................................
        RunningBatchURIMethod := URIMethod;
      end;
      // process CRUD method operation
      case URIMethod of
      mDELETE: begin // '{"Table":[...,"DELETE":ID,...]}' or '[...,"DELETE@Table":ID,...]'
        ID := GetInteger(GetJSONField(Sent,Sent,@wasString,@EndOfObject));
        if (ID<=0) or wasString or
           not RecordCanBeUpdated(RunTable,ID,seDelete,@ErrMsg) then begin
          aCall.Error(ErrMsg,HTML_NOTMODIFIED);
          exit;
        end;
        if RunStatic<>nil then
          OK := RunStatic.EngineDelete(RunTable,ID) else
          OK := EngineDelete(RunTable,ID);
        if OK then begin
          fCache.NotifyDeletion(RunTable,ID);
          if (RunningBatchStatic<>nil) or
             AfterDeleteForceCoherency(RunTable,ID) then
            Results[Count] := HTML_SUCCESS; // 200 OK
        end;
      end;
      mPOST: begin // '{"Table":[...,"POST":{object},...]}' or '[...,"POST@Table":{object},...]'
        Value := JSONGetObject(Sent,nil,EndOfObject);
        if (Sent=nil) or
           not RecordCanBeUpdated(RunTable,0,seAdd,@ErrMsg)  then begin
          aCall.Error(ErrMsg,HTML_NOTMODIFIED);
          exit;
        end;
        if RunStatic<>nil then
          ID := RunStatic.EngineAdd(RunTable,Value) else
          ID := EngineAdd(RunTable,Value);
        Results[Count] := ID;
        fCache.Notify(RunTable,ID,Value,soInsert);
      end;
      mPUT: begin // '{"Table":[...,"PUT":{object},...]}' or '[...,"PUT@Table":{object},...]'
................................................................................
    while Sent^<>'}' do inc(Sent);
    result := Sent^='}';
  end else
    result := true;
  // send back operation status array
  for i := 0 to Count-1 do
    if Results[i]<>HTML_SUCCESS then begin
      aCall.Call.OutBody := IntegerDynArrayToCSV(Results,Count,'[',']');
      exit;
    end;
  aCall.Call.OutBody := '["OK"]';  // to save bandwith if no adding



























end;

function StringToMethod(const method: RawUTF8): TSQLURIMethod;
const NAME: array[mGET..high(TSQLURIMethod)] of string[7] = (
  'GET','POST','PUT','DELETE','BEGIN','END','ABORT','LOCK','UNLOCK','STATE');
var URIMethodUp: string[7];
begin
................................................................................
    for result := low(NAME) to high(NAME) do
      if URIMethodUp=NAME[result] then
        exit;
  end;
  result := mNone;
end;

procedure TSQLRestServer.URI(var Call: TSQLRestServerURIParams);

var BlobFieldName: RawUTF8;
    Static: TSQLRestServerStatic;
    Engine: TSQLRestServer;
    i,j,L: PtrInt;
    SQLSelect, SQLWhere, SQLSort, SQLDir, SQL, ErrMsg: RawUTF8;
    SQLStartIndex, SQLResults: integer;
    StaticKind: (sNone, sInMemory, sVirtual);
    SQLisSelect, OK: boolean;
    URI: TSQLRestServerCallBackParams;
    Session: TAuthSession;
    SessionAccessRights: TSQLAccessRights; // session may be deleted meanwhile
    P: PUTF8Char;
    Blob: PPropInfo;
{$ifdef WITHSTATPROCESS}
    timeStart,timeEnd: Int64;
{$endif}





begin



{$ifdef WITHLOG}


















  URI.Log := SQLite3Log.Enter;
{$endif}

{$ifdef WITHSTATPROCESS}
  QueryPerformanceCounter(timeStart);
{$endif}
  URI.Call := @Call;
  // 0. always return internal database state count (even if URI is '')
  Call.OutInternalState := InternalState; // other threads may change it
  L := length(Call.url);
  inc(fStats.fIncomingBytes,L+length(call.method)+length(call.InHead)+length(call.InBody)+12);
  // 1. retrieve URI expecting 'ModelRoot[/TableName[/ID[/BlobFieldName]]]' format
  i := 0;
  if (Call.url<>'') and (Call.url[1]='/') then inc(i); // URL may be '/path'
  j := length(Model.Root);
  if (i+j>L) or (not(Call.url[i+j+1] in [#0,'/','?'])) or
     (StrCompIL(pointer(PtrInt(Call.url)+i),pointer(Model.Root),j,0)<>0) then begin

    URI.Error('Invalid Root',HTML_NOTFOUND);

    inc(fStats.fInvalid);
    exit; // bad ModelRoot -> caller can try another TSQLRestServer
  end;
  Call.OutStatus := HTML_BADREQUEST; // default error code is 400 BAD REQUEST
  URI.URI := copy(Call.url,j+i+2,maxInt);
  i := PosEx(RawUTF8('/'),URI.URI,1);
  if i>0 then begin
    URI.Parameters := @URI.URI[i+1];
    URI.ID := GetNextItemCardinal(URI.Parameters,'/');
    if (URI.ID>0) and (URI.Parameters<>nil) then begin
      // for URL like "ModelRoot/TableName/ID/BlobFieldName"
      P := PosChar(URI.Parameters,'?');
      if P=nil then
        BlobFieldName := URI.Parameters else
        SetString(BlobFieldName,PAnsiChar(URI.Parameters),P-URI.Parameters);
    end;
    SetLength(URI.URI,i-1);
    j := PosEx(RawUTF8('?'),Call.url,1);
    if j>0 then // '?select=...&where=...' or '?where=...'
      URI.Parameters := @Call.url[j+1] else
      URI.Parameters := nil;
  end else begin
    URI.ID := -1;
    i := PosEx(RawUTF8('?'),Call.url,1);
    if i>0 then begin // '?select=...&where=...' or '?where=...'
      URI.Parameters := @Call.url[i+1];
      i := PosEx(RawUTF8('?'),URI.URI);
      if i>0 then
        dec(i);
      SetLength(URI.URI,i);
    end else
      URI.Parameters := nil; // no parameter
  end;
................................................................................
        end;
    if Static<>nil then
      Engine := Static;
  end;
  // 2. handle security
  Session := nil;
  if HandleAuthentication then begin
    URI.Session := CONST_AUTHENTICATION_SESSION_NOT_STARTED;
    // check session_signature=... parameter
    if URI.Parameters<>nil then begin
      // expected format is 'session_signature='Hexa8(SessionID)+Hexa8(TimeStamp)+
      // Hexa8(crc32('SessionID+HexaSessionPrivateKey'+Sha256('salt'+PassWord)+
      //   Hexa8(TimeStamp)+url))
      i := L-(17+24);
      if (i>0) and // should be LAST parameter in URL
         IdemPChar(@Call.url[i],'SESSION_SIGNATURE=') and
         HexDisplayToCardinal(@Call.url[i+18],URI.Session) then begin
        EnterCriticalSection(fSessionCriticalSection);
        try
          Session := SessionAccess(URI);
          if Session.IsValidURL(Call.url,i-2) then begin
            // supplied RestAccessRights is ignored and replaced by the user rights
            {$ifdef WITHLOG}
            URI.Log.Log(sllUserAuth,'%/%',[Session.User.LogonName,URI.Session],self);
            {$endif}
            move(Session.fAccessRights,SessionAccessRights,sizeof(TSQLAccessRights));
            Call.RestAccessRights := @SessionAccessRights;
          end else
            // mark invalid query authentication
            Session := nil;
        finally
          LeaveCriticalSection(fSessionCriticalSection);
        end;
      end;
    end;
    if (Session=nil) and ((URI.MethodIndex<0) or
      not IntegerScanExists(pointer(fPublishedMethodsUnauthenticated),
        fPublishedMethodsUnauthenticatedCount,URI.MethodIndex)) then begin
      // /auth + /timestamp are e.g. allowed services without signature
      URI.Error('',HTML_FORBIDDEN); // 403 in case of authentication failure
      // 401 Unauthorized response MUST include a WWW-Authenticate header,
      // which is not what we used, so we won't send 401 error code but 403
      inc(fStats.fInvalid);
      exit; // authentication error -> caller can try to open another session
    end;
  end else begin // default unique session if authentication is not enabled
    URI.Session := CONST_AUTHENTICATION_NOT_USED;
    URI.SessionUser := 0;
    URI.SessionGroup := 0;
  end;
  // 3. call appropriate database commands
  URI.Method := StringToMethod(call.method);




  case URI.Method of
  mLOCK,mGET: begin
    if URI.Table=nil then begin
      if (URI.Method<>mLOCK) and
          // GET ModelRoot/MethodName + parameters sent in URI
         ((URI.MethodIndex<0) or not LaunchCallBack(URI)) then
        if (URI.URI='') or not (reService in Call.RestAccessRights^.AllowRemoteExecute) or
           // GET ModelRoot/Service.Method[/ID] + parameters sent as JSON or in URI
           not LaunchService(URI) then begin
          if (Call.InBody='') and (URI.Parameters<>nil) and
             (reUrlEncodedSQL in Call.RestAccessRights^.AllowRemoteExecute) then begin
            // GET with a SQL statement sent in URI, as sql=....
            while not UrlDecodeValue(URI.Parameters,'SQL=',SQL,@URI.Parameters) do
              if URI.Parameters=nil then break;
          end else
            // GET with a SQL statement sent as UTF-8 body
            SQL := Call.InBody;
          SQLisSelect := isSelect(pointer(SQL));
          if (SQL<>'') and
            (SQLisSelect or (reSQL in Call.RestAccessRights^.AllowRemoteExecute)) then begin
            // no user check for SELECT: see TSQLAccessRights.GET comment
            Static := InternalAdaptSQL(Model.GetTableIndexFromSQLSelect(SQL,false),SQL);
            if Static<>nil then
              Engine := Static;
            Call.OutBody := Engine.EngineList(SQL);
            // security note: only first statement is run by EngineList()
            if Call.OutBody<>'' then begin // got JSON list '[{...}]' ?
              Call.OutStatus := HTML_SUCCESS;  // 200 OK
              if not SQLisSelect then
                inc(fStats.fModified);
            end;
          end;
        end;
    end else
    // here, Table<>nil and TableIndex in [0..MAX_SQLTABLES-1]
    if not (URI.TableIndex in Call.RestAccessRights^.GET) then // check User Access
      Call.OutStatus := HTML_FORBIDDEN else begin
      if URI.ID>0 then begin
        // GET ModelRoot/TableName/ID[/BlobFieldName] to retrieve one member,
        // with or w/out locking, or a specified BLOB field content
        if URI.Method=mLOCK then // LOCK is to be followed by PUT -> check user
          if not (URI.TableIndex in Call.RestAccessRights^.PUT) then
            Call.OutStatus := HTML_FORBIDDEN else
            if Model.Lock(URI.TableIndex,URI.ID) then
              URI.Method := mGET; // mark successfully locked
        if URI.Method<>mLOCK then
          if BlobFieldName<>'' then begin
            // GET ModelRoot/TableName/ID/BlobFieldName: retrieve BLOB field content
            Blob := URI.Table.RecordProps.BlobFieldPropFromRawUTF8(BlobFieldName);
            if Blob<>nil then begin
              if Engine.EngineRetrieveBlob(URI.Table,URI.ID,Blob,TSQLRawBlob(Call.OutBody)) then begin
                Call.OutHead := HEADER_CONTENT_TYPE+
                  GetMimeContentType(pointer(Call.OutBody),Length(Call.OutBody));
                Call.OutStatus := HTML_SUCCESS; // 200 OK

              end;
            end else begin
              // GET ModelRoot/TableName/ID/MethodName: try MethodName
              URI.MethodIndex := fPublishedMethods.Find(BlobFieldName);
              if URI.MethodIndex>=0 then
                LaunchCallBack(URI);
            end;
          end else begin
            // GET ModelRoot/TableName/ID: retrieve a member content, JSON encoded
            Call.OutBody := fCache.Retrieve(URI.TableIndex,URI.ID);
            if Call.OutBody='' then begin
              // get JSON object '{...}'
              Call.OutBody := Engine.EngineRetrieve(URI.TableIndex,URI.ID);
              // cache if expected
              fCache.Notify(URI.TableIndex,URI.ID,Call.OutBody,soSelect);
            end;
            if Call.OutBody<>'' then // if something was found
              Call.OutStatus := HTML_SUCCESS; // 200 OK
          end;
      end else
      // ModelRoot/TableName with 'select=..&where=' or YUI paging
      if URI.Method<>mLOCK then begin // LOCK not available here
        SQLSelect := 'RowID'; // if no select is specified (i.e. ModelRoot/TableName)
        // all IDs of this table are returned to the client
        SQLWhere := '';
        if URI.Parameters<>nil then begin // '?select=...&where=...' or '?where=...'
          SQLStartIndex := 0;
          SQLResults := 0;
          if URI.Parameters^<>#0 then
................................................................................
          end;
          if (SQLResults<>0) and not ContainsUTF8(pointer(SQLWhere),'LIMIT ') then
            SQLWhere := FormatUTF8('% LIMIT % OFFSET %',[SQLWhere,SQLResults,SQLStartIndex]);
        end;
        SQL := Model.Props[URI.Table].SQLFromSelectWhere(SQLSelect,trim(SQLWhere));
        if (Static<>nil) and (StaticKind=sInMemory) then
          // manual retrieval (no SQLite3 module available for fStaticData[])
          Call.OutBody := Static.EngineList(SQL) else
          // TSQLVirtualTableJSON/External will rely on their SQLite3 module
          Call.OutBody := EngineList(SQL);
        if Call.OutBody<>'' then  // got JSON list '[{...}]' ?
          Call.OutStatus := HTML_SUCCESS;  // 200 OK
      end;
    end;
  end;
  mUNLOCK: begin
    // ModelRoot/TableName/ID to unlock a member
    if not (URI.TableIndex in Call.RestAccessRights^.PUT) then
      Call.OutStatus := HTML_FORBIDDEN else
    if (URI.Table<>nil) and (URI.ID>0) and
       Model.UnLock(URI.Table,URI.ID) then
      Call.OutStatus := HTML_SUCCESS; // 200 OK
  end;
  mSTATE: begin
    // STATE method for TSQLRestClientURI.ServerInternalState
    // this method is called with Root (-> Table=nil -> Static=nil)
    // we need a specialized method in order to avoid fStats.Invalid increase
    Call.OutStatus := HTML_SUCCESS;
    for i := 0 to high(fStaticData) do
      if fStaticData[i]<>nil then
        if fStaticData[i].RefreshedAndModified then begin
          inc(InternalState); // force refresh
          break;
        end;
  end else
  // write methods (mPOST, mPUT, mDELETE...) are handled separately
  if (URI.Table<>nil) or (URI.Method<>mPOST) or // check thread-safe call of service
     not (reService in Call.RestAccessRights^.AllowRemoteExecute) or
     // POST ModelRoot/Service.Method[/ID] + parameters sent as JSON or in URI
     not LaunchService(URI) then
  // now we have to handle a write to the DB (POST/PUT/DELETE...)
  if AcquireWrite(URI) then // make it thread-safe and transaction-safe
  try
    case URI.Method of
    mPOST: begin       // POST=ADD=INSERT
      if URI.Table=nil then begin
        // ModelRoot with free SQL statement sent as UTF-8 (only for Admin group)
        // security note: multiple SQL statements can be run in EngineExecuteAll()
        if (reSQL in Call.RestAccessRights^.AllowRemoteExecute) and
           EngineExecuteAll(Call.InBody) then begin
          Call.OutStatus := HTML_SUCCESS; // 200 OK
          inc(fStats.fModified);
        end;
      end else
      // here, Table<>nil and TableIndex in [0..MAX_SQLTABLES-1]
      if not (URI.TableIndex in Call.RestAccessRights^.POST) then // check User
        Call.OutStatus := HTML_FORBIDDEN else
      if URI.ID<0 then begin
        // ModelRoot/TableName with possible JSON SentData: create a new member
        URI.ID := Engine.EngineAdd(URI.Table,Call.InBody);
        if URI.ID<>0 then begin
          Call.OutStatus := HTML_CREATED; // 201 Created
          Call.OutHead := 'Location: '+URI.URI+'/'+
            {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(URI.ID);
          fCache.Notify(URI.TableIndex,URI.ID,Call.InBody,soInsert);
          inc(fStats.fModified);
        end;
      end else
        // ModelRoot/TableName/0 = BATCH sequence '{"Table":["cmd":values,...]}'
        if not (URI.TableIndex in Call.RestAccessRights^.PUT) or
           not (URI.TableIndex in Call.RestAccessRights^.DELETE) then // POST already checked
          Call.OutStatus := HTML_FORBIDDEN else
          if RunBatch(Static,URI.Table,URI) then
            Call.OutStatus := HTML_SUCCESS;
    end;
    mPUT: begin        // PUT=UPDATE
      if URI.MethodIndex>=0 then
        // PUT ModelRoot/MethodName (e.g. ModelRoot/Batch)
        LaunchCallBack(URI) else
      if URI.ID>0 then begin
        // PUT ModelRoot/TableName/ID[/BlobFieldName] to update member/BLOB content

        if not (URI.TableIndex in Call.RestAccessRights^.PUT) then // check User
          Call.OutStatus := HTML_FORBIDDEN else
        if not RecordCanBeUpdated(URI.Table,URI.ID,seUpdate,@ErrMsg) then
          Call.OutStatus := HTML_NOTMODIFIED else begin
          OK := false;
          if BlobFieldName<>'' then begin
            // PUT ModelRoot/TableName/ID/BlobFieldName: update BLOB field content
            Blob := URI.Table.RecordProps.BlobFieldPropFromRawUTF8(BlobFieldName);
            if Blob<>nil then begin
              OK := Engine.EngineUpdateBlob(URI.Table,URI.ID,Blob,Call.InBody);
            end else begin
              // PUT ModelRoot/TableName/ID/MethodName: try MethodName
              URI.MethodIndex := fPublishedMethods.Find(BlobFieldName);
              if URI.MethodIndex>=0 then
                LaunchCallBack(URI);
            end;
          end else begin
            // ModelRoot/TableName/ID with JSON SentData: update a member
            OK := Engine.EngineUpdate(URI.Table,URI.ID,Call.InBody);
            if OK then
              fCache.NotifyDeletion(URI.TableIndex,URI.ID); // flush (no CreateTime in JSON)
          end;
          if OK then begin
            Call.OutStatus := HTML_SUCCESS; // 200 OK
            inc(fStats.fModified);
          end;
        end;
      end else
      if URI.Parameters<>nil then // e.g. from TSQLRestClient.EngineUpdateField
        // PUT ModelRoot/TableName?setname=..&set=..&wherename=..&where=..
        if not (URI.TableIndex in Call.RestAccessRights^.PUT) then // check User
          Call.OutStatus := HTML_FORBIDDEN else begin
          repeat
            UrlDecodeValue(URI.Parameters,'SETNAME=',SQLSelect);
            UrlDecodeValue(URI.Parameters,'SET=',SQLDir);
            UrlDecodeValue(URI.Parameters,'WHERENAME=',SQLSort);
            UrlDecodeValue(URI.Parameters,'WHERE=',SQLWhere,@URI.Parameters);
          until URI.Parameters=nil;
          if (SQLSelect<>'') and (SQLDir<>'') and (SQLSort<>'') and (SQLWhere<>'') then
            if Engine.EngineUpdateField(URI.Table,SQLSelect,SQLDir,SQLSort,SQLWhere) then begin
              Call.OutStatus := HTML_SUCCESS; // 200 OK
              inc(fStats.fModified);
            end;
        end;
    end;
    mDELETE:
      if URI.Table<>nil then
        if URI.ID>0 then
          // ModelRoot/TableName/ID to delete a member

          if not (URI.TableIndex in Call.RestAccessRights^.DELETE) then // check User
            Call.OutStatus := HTML_FORBIDDEN else
          if not RecordCanBeUpdated(URI.Table,URI.ID,seDelete,@ErrMsg) then
            Call.OutStatus := HTML_NOTMODIFIED else begin
            if Engine.EngineDelete(URI.Table,URI.ID) and
               AfterDeleteForceCoherency(URI.Table,URI.ID) then begin
              Call.OutStatus := HTML_SUCCESS; // 200 OK
              fCache.NotifyDeletion(URI.TableIndex,URI.ID);
              inc(fStats.fModified);
            end;
          end else
        if URI.Parameters<>nil then
          if (not (URI.TableIndex in Call.RestAccessRights^.DELETE)) or
             (not (reUrlEncodedDelete in Call.RestAccessRights^.AllowRemoteExecute)) then
            Call.OutStatus := HTML_FORBIDDEN else begin
            // ModelRoot/TableName?WhereClause to delete members
            SQLWhere := Trim(UrlDecode(URI.Parameters));
            if SQLWhere<>'' then begin
              if Delete(URI.Table,SQLWhere) then begin
                Call.OutStatus := HTML_SUCCESS; // 200 OK
                inc(fStats.fModified);
              end;
            end;
          end;
    mBEGIN: begin      // BEGIN TRANSACTION
      // TSQLVirtualTableJSON/External will rely on SQLite3 module
      // and also TSQLRestServerStaticInMemory, since COMMIT/ROLLBACK have Static=nil
      if TransactionBegin(URI.Table,URI.Session) then begin
        if (Static<>nil) and (StaticKind=sVirtual) then
          Static.TransactionBegin(URI.Table,URI.Session);
        Call.OutStatus := HTML_SUCCESS; // 200 OK
      end;
    end;
    mEND: begin        // END=COMMIT
      // this method is called with Root (-> Table=nil -> Static=nil)
      if fTransactionTable<>nil then
        Static := StaticVirtualTable[fTransactionTable];
      Commit(URI.Session);
      if Static<>nil then
        Static.Commit(URI.Session);
      Call.OutStatus := HTML_SUCCESS; // 200 OK
    end;
    mABORT: begin      // ABORT=ROLLBACK
      // this method is called with Root (-> Table=nil -> Static=nil)
      if fTransactionTable<>nil then
        Static := StaticVirtualTable[fTransactionTable];
      RollBack(URI.Session);
      if Static<>nil then
        Static.RollBack(URI.Session);
      Call.OutStatus := HTML_SUCCESS; // 200 OK
    end;
    end;
  finally
    ReleaseWrite;
  end else
    // AcquireWrite(SessionID) returned false (e.g. endless transaction)
    Call.OutStatus := HTML_TIMEOUT; // 408 Request Time-out
  end;
  // 4. returns expected result to the client
  if Call.OutStatus in [HTML_SUCCESS,HTML_CREATED] then begin
    inc(fStats.fResponses);
    {$ifdef WITHLOG}
    URI.Log.Log(sllServer,'% % -> %',[Call.Method,URI.URI,Call.OutStatus],self);
    {$endif}
  end else begin
    inc(fStats.fInvalid);
    if Call.OutBody='' then // if no custom error message, compute it now as JSON
      URI.Error(ErrMsg,Call.OutStatus);
  end;
  inc(fStats.fOutcomingBytes,length(Call.OutHead)+length(Call.OutBody)+16);
  if (Static<>nil) and (StaticKind=sInMemory) then
    // force always refresh for Static table
    Call.OutInternalState := cardinal(-1) else
    // database state may have changed above
    Call.OutInternalState := InternalState;
{$ifdef WITHSTATPROCESS}
  QueryPerformanceCounter(timeEnd);
  inc(fStats.ProcessTimeCounter,timeEnd-timeStart);
{$endif}
end;

procedure TSQLRestServer.Stat(var aParams: TSQLRestServerCallBackParams);
begin
  aParams.Returns(Stats.DebugMessage); // transmitted as JSON object

end;

procedure TSQLRestServer.TimeStamp(var aParams: TSQLRestServerCallBackParams);
begin

  aParams.Returns(Int64ToUtf8(ServerTimeStamp),HTML_SUCCESS,TEXT_CONTENT_TYPE_HEADER);

end;

procedure TSQLRestServer.CacheFlush(var aParams: TSQLRestServerCallBackParams);
begin
  if aParams.Table=nil then
    Cache.Flush else
    if aParams.ID=0 then
      Cache.Flush(aParams.Table) else
      Cache.SetCache(aParams.Table,aParams.ID);
  aParams.Success;
end;

procedure TSQLRestServer.Batch(var aParams: TSQLRestServerCallBackParams);
begin
  if (aParams.Method=mPUT) and RunBatch(nil,nil,aParams) then
    aParams.Success else
    aParams.Error; 

end;

function Nonce(Previous: boolean): RawUTF8;
var Tix: cardinal;
    tmp: RawByteString;
begin
  Tix := GetTickCount div (1000*60*5); // valid for 5*60*1000 ms = 5 minutes
  if Previous then
    dec(Tix);
  SetString(tmp,PAnsiChar(@Tix),sizeof(Tix));
  result := SHA256(tmp);
end;

procedure TSQLRestServer.Auth(var aParams: TSQLRestServerCallBackParams);

procedure CreateNewSession(var User: TSQLAuthUser; var aParams: TSQLRestServerCallBackParams);
var Session: TAuthSession;
begin
  if User.fID=0 then begin
    {$ifdef WITHLOG}
    aParams.Log.Log(sllUserAuth,
      'User.LogonName=% not found in AuthUser table',[User.LogonName],self);
    {$endif}
    exit; // unknown user -> error 404
  end;
  Session := TAuthSession.Create(self,User);
  try
    if aParams.Call.InHead<>'' then begin
      Session.fSentHeaders := aParams.Call.InHead;
      Session.fRemoteIP := FindIniNameValue(pointer(aParams.Call.InHead),'REMOTEIP: ');
    end;
    aParams.Returns(['result',Session.fPrivateSalt,'logonname',User.LogonName]);
    User := nil; // will be freed by TAuthSession.Destroy
    if fSessions=nil then
      fSessions := TObjectList.Create;
    fSessions.Add(Session);
    Session := nil; // will be freed by fSessions
  finally
    Session.Free;
................................................................................
    InDataEnc: RawUTF8;
    CtxArr: TDynArray;
    Now: QWord;
    SecCtxIdx: Integer;
    OutData: RawByteString;
{$endif}
begin

  if not UrlDecodeNeedParameters(aParams.Parameters,'UserName') then begin
    aParams.Error('Expect UserName parameter');
    exit;
  end;
  EnterCriticalSection(fSessionCriticalSection);
  try
    if UrlDecodeNeedParameters(aParams.Parameters,'Session') then begin
      // GET ModelRoot/auth?UserName=...&Session=... -> release session
      while aParams.Parameters<>nil do begin
        UrlDecodeValue(aParams.Parameters,'USERNAME=',aUserName);
        UrlDecodeCardinal(aParams.Parameters,'SESSION=',aSessionID,@aParams.Parameters);
      end;
      if (fSessions<>nil) and 
         // allow only to delete its own session - ticket [7723fa7ebd]
         (aSessionID=aParams.Session) then
        for i := 0 to fSessions.Count-1 do
          with TAuthSession(fSessions.List[i]) do
          if fIDCardinal=aSessionID then begin
            SessionDelete(i);
            aParams.Success;
            break;
          end;
      exit; // unknown session -> error 404
    end else
    if UrlDecodeNeedParameters(aParams.Parameters,'PassWord,ClientNonce') then begin
      // GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=... -> handshaking
      while aParams.Parameters<>nil do begin
        UrlDecodeValue(aParams.Parameters,'USERNAME=',aUserName);
        UrlDecodeValue(aParams.Parameters,'PASSWORD=',aPassWord);
................................................................................
          SecCtxIdx := CtxArr.New; // add a new entry to fSSPIAuthContexts[]
          InvalidateSecContext(fSSPIAuthContexts[SecCtxIdx]);
          fSSPIAuthContexts[SecCtxIdx].ID := fSSPIAuthCounter;
          Inc(fSSPIAuthCounter);
        end;
        // call SSPI provider
        if ServerSSPIAuth(fSSPIAuthContexts[SecCtxIdx], Base64ToBin(InDataEnc), OutData) then begin
          aParams.Returns(['result','','id',Int64(fSSPIAuthContexts[SecCtxIdx].ID),
            'data',BinToBase64(OutData)]);

          exit; // 1st call: send back OutData to the client
        end;
        // 2nd call: user was authenticated -> release used context
        ServerSSPIAuthUser(fSSPIAuthContexts[SecCtxIdx], aUserName);
        {$ifdef WITHLOG}
        SQLite3Log.Family.SynLog.Log(sllUserAuth,
          'Windows Authentication success for %',[aUserName],self);
................................................................................
        CreateNewSession(User,aParams);
      finally
        User.Free;
      end;
{$endif}
    end else
      // only UserName=... -> return hexadecimal nonce content valid for 5 minutes
      aParams.Results([Nonce(false)]);
  finally
    LeaveCriticalSection(fSessionCriticalSection);
  end;

end;

procedure TSQLRestServer.SessionDelete(aSessionIndex: integer);
begin
  if (self<>nil) and (cardinal(aSessionIndex)<cardinal(fSessions.Count)) then
  with TAuthSession(fSessions.List[aSessionIndex]) do begin
    if Services is TServiceContainerServer then
      TServiceContainerServer(Services).OnCloseSession(IDCardinal);
    {$ifdef WITHLOG}
    SQLite3Log.Family.SynLog.Log(sllUserAuth,'Deleted session %/%',[User.LogonName,IDCardinal],self);
    {$endif}
    fSessions.Delete(aSessionIndex);
  end;
end;

function TSQLRestServer.SessionAccess(var aContext: TSQLRestServerCallBackParams): TAuthSession;
var i: integer;
    Now: cardinal;
begin // caller shall be locked via fSessionCriticalSection
  if (self<>nil) and (fSessions<>nil) then begin
    // first check for outdated sessions to be deleted
    Now := GetTickCount;
    for i := fSessions.Count-1 downto 0 do
      with TAuthSession(fSessions.List[i]) do
        if Now<LastAccess then // 32 bit overflow occured
          fLastAccess := Now else
................................................................................
        if QWord(Now)>QWord(LastAccess)+QWord(TimeOut) then
          SessionDelete(i);
    // retrieve session
    for i := 0 to fSessions.Count-1 do begin
      result := TAuthSession(fSessions.List[i]);
      if result.IDCardinal=aContext.Session then begin
        result.fLastAccess := Now; // refresh session access timestamp
        aContext.SessionUser := result.User.fID;
        aContext.SessionGroup := result.User.GroupRights.fID;
        exit;
      end;
    end;
  end;
  result := nil;
end;

................................................................................
      fChild[fMasterThreadChildIndex] := nil;
      InterlockedDecrement(fChildCount);
    end;
  inherited;
end;

procedure TSQLRestServerNamedPipeResponse.Execute;
var call: TSQLRestServerURIParams;


    Code: integer;
    Ticks, Sleeper, ClientTimeOut: cardinal;
    Available: cardinal;

begin
  if (fPipe=0) or (fPipe=INVALID_HANDLE_VALUE) or (fServer=nil) then
    exit;
  fServer.BeginCurrentThread(self);
  Ticks := 0;
  Sleeper := 0;
  ClientTimeOut := GetTickCount+30*60*1000; // disconnect client after 30 min of inactivity
................................................................................
    while not Terminated do
    if // (WaitForSingleObject(fPipe,200)=WAIT_OBJECT_0) and  = don't wait
       PeekNamedPipe(fPipe,nil,0,nil,@Available,nil) and (Available>=4) then begin
      FileRead(fPipe,Code,4);
      if (Code=integer(MAGIC_SYN)) // magic word for URI like request
        and not Terminated then
        try
          call.Url := ReadString(fPipe);
          call.Method := ReadString(fPipe);
          call.InBody := ReadString(fPipe);

          call.RestAccessRights := @SUPERVISOR_ACCESS_RIGHTS;
          // it's up to URI overriden method to implement access rights
          fServer.URI(call);
          FileWrite(fPipe,call.OutStatus,sizeof(cardinal)*2);
          WriteString(fPipe,call.OutHead);
          WriteString(fPipe,call.OutBody);
          FlushFileBuffers(fPipe); // Flush the pipe to allow the client to read

          Ticks := GetTickCount+20; // start sleeping after 20 ms
          ClientTimeOut := Ticks+30*60*1000;
          if ClientTimeOut<Ticks then // avoid 32 bits overflow
            ClientTimeOut := 30*60*1000;
          Sleeper := 0;
          Sleep(0);
        except
................................................................................
      Sleep(Sleeper); // doesn't slow down connection but decreases CSwitch
      Ticks := 0;
      if GetTickCount>ClientTimeOut then
        break; // disconnect client after 30 min of inactivity
    end else
      Sleep(0);
  finally
    fServer.Stats.ClientDisconnect;
    DisconnectNamedPipe(fPipe);
    CloseHandle(fPipe);
  end;
end;


{ TSQLRestClientURINamedPipe }
................................................................................
{$endif}
  CreatePipe;
  if (Pipe=INVALID_HANDLE_VALUE) or (Pipe=ERROR_PIPE_BUSY) then
    for Retry := 1 to NAMEDPIPE_RETRYCOUNT_IFBUSY do begin
      if WaitNamedPipe(pointer(fPipeName),1000) then begin
        // 1000 since we have sleep(128) in TSQLRestServerNamedPipe.EngineExecute
        CreatePipe;
        if Pipe<>ERROR_PIPE_BUSY then
          break;
      end;
      sleep(200); // wait for TSQLRestServerNamedPipe.EngineExecute to be reached
    end;
  if (Pipe=INVALID_HANDLE_VALUE) or (Pipe=ERROR_PIPE_BUSY) then begin
    {$ifdef WITHLOG}
    Log.Log(sllError,'"%" when connecting to %',[
................................................................................
function TServiceFactoryServer.CreateInstance: TInterfacedObject;
begin
  if fImplementationClassWithCustomCreate then
    result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create else
    result := fImplementationClass.Create;
end;

procedure TServiceFactoryServer.ExecuteMethod(
  var aParams: TSQLRestServerCallBackParams;
  aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char);

procedure Error(Msg: PUTF8Char);
begin
  aParams.Error(Msg,
    [UnCamelCase(ServiceInstanceImplementationToText(InstanceCreation))]);
end;
var Inst: TServiceFactoryServerInstance;
    WR: TTextWriter;
    entry: PInterfaceEntry;
    ThreadServer: PServiceRunningContext;
begin

  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
................................................................................
    sicShared:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven, sicPerSession, sicPerUser, sicPerGroup: begin
      if InstanceCreation=sicClientDriven then
        Inst.InstanceID := aInstanceID else
        if aParams.Session>CONST_AUTHENTICATION_NOT_USED then
          case InstanceCreation of // authenticated user -> handle context
          sicPerSession: Inst.InstanceID := aParams.Session;
          sicPerUser:    Inst.InstanceID := aParams.SessionUser;
          sicPerGroup:   Inst.InstanceID := aParams.SessionGroup;
          end else begin
            Error('% mode expects an authenticated session');
            exit;
          end;
      if InternalInstanceRetrieve(Inst,aMethodIndex) then begin

        aParams.Success;
        exit; // {"method":"_free_", "params":[], "id":1234}
      end;
    end;
  end;
  if Inst.Instance=nil then begin

    Error('% instance not found or deprecated');



    exit;
  end;
  // 2. call method implementation
  try
    entry := Inst.Instance.GetInterfaceEntry(fInterface.fInterfaceIID);
    if entry=nil then
      exit;
    ThreadServer := @ServiceContext;
    WR := TJSONSerializer.CreateOwnedStream;
    try
      with ThreadServer^ do begin
        Factory := self;
        Session := @aParams;
      end; // RunningThread is already set at thread initialization
      // root/calculator {"method":"add","params":[1,2]} -> {"result":[3],"id":0}
      try
        WR.AddShort('{"result":[');
        if not fInterface.fMethods[aMethodIndex].InternalExecute(
            [PAnsiChar(Inst.Instance)+entry^.IOffset],aParamsJSONArray,WR,
             aParams.Call.OutHead,fExecution[aMethodIndex].Options) then
          exit; // wrong request
        if aParams.Call.OutHead='' then begin // <>'' for TServiceCustomAnswer
          WR.AddShort('],"id":');
          WR.Add(Inst.InstanceID); // only used in sicClientDriven mode
          WR.AddShort('}');
        end;
        WR.SetText(aParams.Call.OutBody);
        aParams.Call.OutStatus := HTML_SUCCESS; 
      except
        on E: Exception do
          aParams.Error('Exception %: %',
           [PShortString(PPointer(PtrInt(E)+vmtClassName)^)^,E.Message],


           HTML_SERVERERROR);
      end;
    finally
      with ThreadServer^ do begin
        Factory := nil;
        Session := nil;
      end;
      WR.Free;

Changes to SQLite3/mORMotFastCgiServer.pas.

592
593
594
595
596
597
598
599
600
601
602
603



604

605
606
607
608
609
610
611


612
613
614
615
616
617
618
procedure TFastCGIServer.LogOut;
begin
  ; // do nothing by default
end;

procedure TFastCGIServer.ProcessRequest(const Request: RawUTF8);
var Head: RawUTF8;
begin
  if Server=nil then
    ResetRequest else
    with Server.URI(fRequestURL, fRequestMethod, Request,



      fResponseContent, Head, @SUPERVISOR_ACCESS_RIGHTS) do

      fResponseHeaders := FormatUTF8(
        'Status: %'#13#10'Server-InternalState: %'#13#10+
        'X-Powered-By: TFastCGIServer http://synopse.info'#13#10+
        'Content-Type: '+JSON_CONTENT_TYPE+#13#10+
        'Content-Length: %'#13#10+
        '%', // a void line will be appened in SendResponse() method
        [Lo,Hi,length(fResponseContent),Head]);


end;

function TFastCGIServer.ReadPacked: RawUTF8;
{$ifdef MSWINDOWS}
var L: integer;
{$endif}
begin






|



|
>
>
>
|
>
|
|
|
|
|
|
<
>
>







592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614

615
616
617
618
619
620
621
622
623
procedure TFastCGIServer.LogOut;
begin
  ; // do nothing by default
end;

procedure TFastCGIServer.ProcessRequest(const Request: RawUTF8);
var call: TSQLRestServerURIParams;
begin
  if Server=nil then
    ResetRequest else
  with call do begin
    Url := fRequestURL;
    Method := fRequestMethod;
    InBody := Request;
    RestAccessRights := @SUPERVISOR_ACCESS_RIGHTS;
    Server.URI(call);
    fResponseHeaders := FormatUTF8(
      'Status: %'#13#10'Server-InternalState: %'#13#10+
      'X-Powered-By: TFastCGIServer http://synopse.info'#13#10+
      'Content-Type: '+JSON_CONTENT_TYPE+#13#10+
      'Content-Length: %'#13#10+
      '%', // a void line will be appened in SendResponse() method

      [OutStatus,OutInternalState,length(OutBody),OutHead]);
  end;
end;

function TFastCGIServer.ReadPacked: RawUTF8;
{$ifdef MSWINDOWS}
var L: integer;
{$endif}
begin

Changes to SQLite3/mORMotHttpServer.pas.

462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485




486
487
488
489
490
491
492
493
494
495
496

497
498
499
500
501
502
503
  if (Self<>nil) and (cardinal(Index)<cardinal(length(fDBServers))) then
    fDBServers[Index].RestAccessRights := Value;
end;

function TSQLHttpServer.Request(
  const InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
    out OutContent, OutContentType, OutCustomHeader: RawByteString): cardinal;
var URL, Head: RawUTF8;
    i: integer;
    P: PUTF8Char;
begin
  if (InURL='') or (InMethod='') or
     (OnlyJSONRequests and
      not IdemPChar(pointer(InContentType),'APPLICATION/JSON')) then
    // wrong Input parameters or not JSON request: 400 BAD REQUEST
    result := 400 else begin
    if InURL[1]='/' then  // trim any left '/' from URL
      URL := copy(InURL,2,maxInt) else
      URL := InURL;
    result := 404; // page not found by default (in case of wrong URL)
    for i := 0 to high(fDBServers) do
    with fDBServers[i] do
      if Server.Model.URIMatch(URL) then
      with Server.URI(URL,InMethod,InContent,RawUTF8(OutContent),Head,RestAccessRights) do begin




        result := Lo;
        P := pointer(Head);
        if IdemPChar(P,'CONTENT-TYPE: ') then begin
          // change mime type if modified in HTTP header (e.g. GET blob fields)
          OutContentType := GetNextLine(P+14,P);
          Head := P;
        end else
          // default content type is JSON
          OutContentType := JSON_CONTENT_TYPE;
        Head := Trim(Trim(Head)+#13#10'Server-InternalState: '+Int32ToUtf8(Hi));
        OutCustomHeader := Head;

        break;
      end;
  end;
end;

procedure TSQLHttpServer.HttpThreadTerminate(Sender: TObject);
var i: integer;






|









|
|



|
|
>
>
>
>
|
|



|



|
|
>







462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
  if (Self<>nil) and (cardinal(Index)<cardinal(length(fDBServers))) then
    fDBServers[Index].RestAccessRights := Value;
end;

function TSQLHttpServer.Request(
  const InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
    out OutContent, OutContentType, OutCustomHeader: RawByteString): cardinal;
var call: TSQLRestServerURIParams;
    i: integer;
    P: PUTF8Char;
begin
  if (InURL='') or (InMethod='') or
     (OnlyJSONRequests and
      not IdemPChar(pointer(InContentType),'APPLICATION/JSON')) then
    // wrong Input parameters or not JSON request: 400 BAD REQUEST
    result := 400 else begin
    if InURL[1]='/' then  // trim any left '/' from URL
      call.Url := copy(InURL,2,maxInt) else
      call.Url := InURL;
    result := 404; // page not found by default (in case of wrong URL)
    for i := 0 to high(fDBServers) do
    with fDBServers[i] do
      if Server.Model.URIMatch(call.Url) then begin
        call.Method := InMethod;
        call.InHead := InHeaders;
        call.InBody := InContent;
        call.RestAccessRights := RestAccessRights;
        Server.URI(call);
        result := call.OutStatus;
        P := pointer(call.OutHead);
        if IdemPChar(P,'CONTENT-TYPE: ') then begin
          // change mime type if modified in HTTP header (e.g. GET blob fields)
          OutContentType := GetNextLine(P+14,P);
          call.OutHead := P;
        end else
          // default content type is JSON
          OutContentType := JSON_CONTENT_TYPE;
        OutCustomHeader := Trim(Trim(call.OutHead)+#13#10'Server-InternalState: '+
          Int32ToUtf8(call.OutInternalState));
        OutContent := call.OutBody;
        break;
      end;
  end;
end;

procedure TSQLHttpServer.HttpThreadTerminate(Sender: TObject);
var i: integer;

Changes to SQLite3/mORMotSQLite3.pas.

1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281







1282
1283

1284
1285
1286
1287
1288
1289
1290
    on ESQLite3Exception do
      result := nil;
  end;
end;

function TSQLRestClientDB.InternalURI(const url, method: RawUTF8;
  Resp, Head, SendData: PRawUTF8): Int64Rec;
var R,H,S: RawUTF8; // temp '' string to be used when no PString is provided
begin
  if Resp=nil then Resp := @R;
  if Head=nil then Head := @H;
  if SendData=nil then SendData := @S;
  result := fServer.URI(url,method,SendData^,Resp^,Head^,@FULL_ACCESS_RIGHTS);







  if (result.Hi=0) and (fServer.DB.InternalState<>nil) then
    result.Hi := fServer.DB.InternalState^; // manual update if necessary

end;

function TSQLRestClientDB.InternalCheckOpen: boolean;
begin
  result := true;
end;







|

|
|
|
|
>
>
>
>
>
>
>
|
|
>







1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
    on ESQLite3Exception do
      result := nil;
  end;
end;

function TSQLRestClientDB.InternalURI(const url, method: RawUTF8;
  Resp, Head, SendData: PRawUTF8): Int64Rec;
var call: TSQLRestServerURIParams;
begin
  call.Url := url;
  call.Method := method;
  if SendData<>nil then
    call.InBody := SendData^;
  call.RestAccessRights := @FULL_ACCESS_RIGHTS;
  fServer.URI(call);
  if Head<>nil then
    Head^ := call.OutHead;
  if Resp<>nil then
    Resp^ := call.OutBody;
  result.Lo := call.OutStatus;
  if (call.OutInternalState=0) and (fServer.DB.InternalState<>nil) then
    result.Hi := fServer.DB.InternalState^ else // manual update if necessary
    result.Hi := call.OutInternalState;
end;

function TSQLRestClientDB.InternalCheckOpen: boolean;
begin
  result := true;
end;

Changes to SynCrtSock.pas.

1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
....
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
      result := result*10+c;
    inc(P);
  end;
end;

const
  DEFAULT_AGENT = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows; FREE)';
  
  
function THttpClientSocket.Request(const url, method: RawByteString;
  KeepAlive: cardinal; const Header, Data, DataType: RawByteString; retry: boolean): integer;
procedure DoRetry(Error: integer);
begin
  if retry then // retry once -> return error if already retried
    result := Error else begin
................................................................................
    Suspended := False;
end;

constructor THttpApiServer.CreateClone(From: THttpApiServer);
begin
  inherited Create(false);
  fReqQueue := From.fReqQueue;
  fOnRequest := From.OnRequest;
  fCompress := From.fCompress;
  OnHttpThreadTerminate := From.OnHttpThreadTerminate;
  fCompressAcceptEncoding := From.fCompressAcceptEncoding;
end;

destructor THttpApiServer.Destroy;
var i: Integer;






|







 







|







1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
....
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
      result := result*10+c;
    inc(P);
  end;
end;

const
  DEFAULT_AGENT = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows; FREE)';
                     
  
function THttpClientSocket.Request(const url, method: RawByteString;
  KeepAlive: cardinal; const Header, Data, DataType: RawByteString; retry: boolean): integer;
procedure DoRetry(Error: integer);
begin
  if retry then // retry once -> return error if already retried
    result := Error else begin
................................................................................
    Suspended := False;
end;

constructor THttpApiServer.CreateClone(From: THttpApiServer);
begin
  inherited Create(false);
  fReqQueue := From.fReqQueue;
  fOnRequest := From.fOnRequest;
  fCompress := From.fCompress;
  OnHttpThreadTerminate := From.OnHttpThreadTerminate;
  fCompressAcceptEncoding := From.fCompressAcceptEncoding;
end;

destructor THttpApiServer.Destroy;
var i: Integer;

Changes to SynSelfTests.pas.

103
104
105
106
107
108
109
110
111
112

113
114
115
116
117
118
119
...
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
....
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
....
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
....
5422
5423
5424
5425
5426
5427
5428









5429
5430
5431
5432
5433
5434
5435
....
5637
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
....
5858
5859
5860
5861
5862
5863
5864
5865
5866
5867
5868
5869
5870
5871
5872
5873

5874
5875
5876
5877
5878
5879
5880
5881
5882
5883
5884
5885
5886
5887
5888
5889
5890
5891
5892
5893



5894
5895
5896
5897
5898
5899
5900
{$endif}
  SysUtils,
{$ifndef FPC}
{$ifndef LVCL}
  Contnrs,
  SynDB,
{$endif}
{$ifndef DELPHI5OROLDER}
{$ifndef CPU64}
  SynSQLite3,

{$ifndef LVCL}
  SynDBSQLite3,
  mORMotDB,
{$endif}
  mORMot,
  mORMotSQLite3,
  mORMotHttpServer,
................................................................................
     - if called from ModelRoot/DataAsHex with GET or PUT methods,
       TSQLRestServer.URI will leave aRecord=nil before launching it
     - implementation must return the HTTP error code (e.g. 200 as success)
     - Table is overloaded as TSQLRecordPeople here, and still match the
       TSQLRestServerCallBack prototype: but you have to check the class
       at runtime: it can be called by another similar but invalid URL, like
       ModelRoot/OtherTableName/ID/DataAsHex }
    function DataAsHex(var aParams: TSQLRestServerCallBackParams): Integer;
    {/ method used to test the Server-Side ModelRoot/Sum or
       ModelRoot/People/Sum Requests
      - implementation of this method returns the sum of two floating-points,
        named A and B, as in the public TSQLRecordPeople.Sum() method,
        which implements the Client-Side of this service
      - Table nor ID are never used here }
    function Sum(var aParams: TSQLRestServerCallBackParams): Integer;
  end;

{$ifndef LVCL}
  /// a test case which will test most external DB functions of the mORMotDB unit
  // - the external DB will be in fact a SynDBSQLite3 instance, expecting a
  // test.db3 SQlite3 file available in the current directory, populated with
  // some TSQLRecordPeople rows
................................................................................
    Check(V[3]=nil);
    J := BinToBase64WithMagic(U);
    check(PInteger(J)^ and $00ffffff=JSON_BASE64_MAGIC);
{$ifndef DELPHI5OROLDER}
    check(BlobToTSQLRawBlob(pointer(J))=U);
    Base64MagicToBlob(@J[4],K);
    check(BlobToTSQLRawBlob(pointer(K))=U);
    J := TSQLRestServer.JSONEncodeResult([r]);
    Check(SameValue(GetExtended(pointer(JSONDecode(J)),err),r));
    {$ifdef USEVARIANTS}
    with TTextWriter.CreateOwnedStream do
    try
      AddVariantJSON(a);
      Add(',');
      AddVariantJSON(r);
      Add(',');
................................................................................
    FV: TFV;
    ModelC: TSQLModel;
    Client: TSQLRestClientDB;
    ClientDist: TSQLRestClientURI;
    Server: TSQLRestServer;
    aStatic: TSQLRestServerStaticInMemory;
    Curr: Currency;
    DaVinci, s, h: RawUTF8;
    Refreshed: boolean;
    J: TSQLTableJSON;
    i, n, nupd, ndx: integer;
    IntArray, Results: TIntegerDynArray;
    Data: TSQLRawBlob;
    DataS: THeapMemoryStream;
    a,b: double;
................................................................................
       end;
    result := true;
  finally
    Free;
    T.Free;
  end;
end;









begin
  V := TSQLRecordPeople.Create;
  VA := TSQLRecordPeopleArray.Create;
{$ifndef LVCL}
  VO := TSQLRecordPeopleObject.Create;
{$endif}
  V2 := nil;
................................................................................
          Check(aStatic<>nil);
          aStatic.LoadFromJSON(JS); // test Add()
          for i := 0 to aStatic.Count-1 do begin
            Check(Client.Retrieve(aStatic.ID[i],V),'test statement+bind speed');
            Check(V.SameRecord(aStatic.Items[i]),'static retrieve');
          end;
          // test our 'REST-minimal' SELECT statement SQL engine
          Server.URI(Client.SessionSign('/root/People?select=%2A&where=id%3D012'),'GET','',s,h,@SUPERVISOR_ACCESS_RIGHTS);
          Check(Hash32(S)=$9B10BE36);
          Server.URI(Client.SessionSign('/root/People?select=%2A&where=id%3D:(012):'),'GET','',s,h,@SUPERVISOR_ACCESS_RIGHTS);
          Check(Hash32(S)=$9B10BE36);
          Server.URI(Client.SessionSign('/root/People?select=%2A&where=LastName%3D%22M%C3%B4net%22'),'GET','',s,h,@SUPERVISOR_ACCESS_RIGHTS);
          Check(Hash32(S)=$7D90E964);
          Server.URI(Client.SessionSign('/root/People?select=%2A&where=YearOfBirth%3D1873'),'GET','',s,h,@SUPERVISOR_ACCESS_RIGHTS);
          Check(Hash32(S)=$F0818745);
          Server.URI(Client.SessionSign('/root/People?select=%2A'),'GET','',s,h,@SUPERVISOR_ACCESS_RIGHTS);
          Check(Hash32(S)=$17AE45E3);
          // test Retrieve() and Delete()
          Server.ExportServer; // initialize URIRequest() with the aStatic database
          USEFASTMM4ALLOC := true; // getmem() is 2x faster than GlobalAlloc()
          ClientDist := TSQLRestClientURIDll.Create(ModelC,URIRequest);
          try
            SetLength(IntArray,(aStatic.Count-1)shr 2);
            for i := 0 to high(IntArray) do begin
................................................................................
{$ifdef UNICODE}
{$WARNINGS ON} // don't care about implicit string cast in tests
{$endif}


{ TSQLRestServerTest }

function TSQLRestServerTest.DataAsHex(var aParams: TSQLRestServerCallBackParams): Integer;
var aData: TSQLRawBlob;
begin
  result := 404; // invalid Request
  if (self=nil) or (aParams.Table=nil) or
      not aParams.Table.InheritsFrom(TSQLRecord) or
    (aParams.Context.ID<0) then
    exit; // we need a valid record and its ID
  if not RetrieveBlob(TSQLRecordPeople,aParams.Context.ID,'Data',aData) then

    exit; // impossible to retrieve the Data BLOB field
  aParams.Resp := JSONEncodeResult([SynCommons.BinToHex(aData)]);
  // idem: aResp := JSONEncode(['result',BinToHex(aRecord.fData)],TempMemoryStream);
  result := 200; // success
end;

function TSQLRestServerTest.Sum(var aParams: TSQLRestServerCallBackParams): Integer;
var a,b: Extended;
begin
  if not UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin
    result := 404; // invalid Request
    exit;
  end;
  while aParams.Parameters<>nil do begin
    UrlDecodeExtended(aParams.Parameters,'A=',a);
    UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
  end;
  aParams.Resp := JSONEncodeResult([a+b]);
  // same as : aResp := JSONEncode(['result',a+b],TempMemoryStream);
  result := 200; // success



end;


{$ifndef LVCL}

{ TSQLRecordPeopleObject }







<


>







 







|






|







 







|
|







 







|







 







>
>
>
>
>
>
>
>
>







 







|
<
|
<
|
<
|
<
|
<







 







|


<
|
<
<
|
|
>
|
<
<
<


|


|
<
<
<
|
|
|
|
<
<
<
>
>
>







103
104
105
106
107
108
109

110
111
112
113
114
115
116
117
118
119
...
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
....
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
....
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
....
5422
5423
5424
5425
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
5439
5440
5441
5442
5443
5444
....
5646
5647
5648
5649
5650
5651
5652
5653

5654

5655

5656

5657

5658
5659
5660
5661
5662
5663
5664
....
5862
5863
5864
5865
5866
5867
5868
5869
5870
5871

5872


5873
5874
5875
5876



5877
5878
5879
5880
5881
5882



5883
5884
5885
5886



5887
5888
5889
5890
5891
5892
5893
5894
5895
5896
{$endif}
  SysUtils,
{$ifndef FPC}
{$ifndef LVCL}
  Contnrs,
  SynDB,
{$endif}

{$ifndef CPU64}
  SynSQLite3,
{$ifndef DELPHI5OROLDER}
{$ifndef LVCL}
  SynDBSQLite3,
  mORMotDB,
{$endif}
  mORMot,
  mORMotSQLite3,
  mORMotHttpServer,
................................................................................
     - if called from ModelRoot/DataAsHex with GET or PUT methods,
       TSQLRestServer.URI will leave aRecord=nil before launching it
     - implementation must return the HTTP error code (e.g. 200 as success)
     - Table is overloaded as TSQLRecordPeople here, and still match the
       TSQLRestServerCallBack prototype: but you have to check the class
       at runtime: it can be called by another similar but invalid URL, like
       ModelRoot/OtherTableName/ID/DataAsHex }
    procedure DataAsHex(var aParams: TSQLRestServerCallBackParams);
    {/ method used to test the Server-Side ModelRoot/Sum or
       ModelRoot/People/Sum Requests
      - implementation of this method returns the sum of two floating-points,
        named A and B, as in the public TSQLRecordPeople.Sum() method,
        which implements the Client-Side of this service
      - Table nor ID are never used here }
    procedure Sum(var aParams: TSQLRestServerCallBackParams);
  end;

{$ifndef LVCL}
  /// a test case which will test most external DB functions of the mORMotDB unit
  // - the external DB will be in fact a SynDBSQLite3 instance, expecting a
  // test.db3 SQlite3 file available in the current directory, populated with
  // some TSQLRecordPeople rows
................................................................................
    Check(V[3]=nil);
    J := BinToBase64WithMagic(U);
    check(PInteger(J)^ and $00ffffff=JSON_BASE64_MAGIC);
{$ifndef DELPHI5OROLDER}
    check(BlobToTSQLRawBlob(pointer(J))=U);
    Base64MagicToBlob(@J[4],K);
    check(BlobToTSQLRawBlob(pointer(K))=U);
{    J := TSQLRestServer.JSONEncodeResult([r]);
    Check(SameValue(GetExtended(pointer(JSONDecode(J)),err),r)); }
    {$ifdef USEVARIANTS}
    with TTextWriter.CreateOwnedStream do
    try
      AddVariantJSON(a);
      Add(',');
      AddVariantJSON(r);
      Add(',');
................................................................................
    FV: TFV;
    ModelC: TSQLModel;
    Client: TSQLRestClientDB;
    ClientDist: TSQLRestClientURI;
    Server: TSQLRestServer;
    aStatic: TSQLRestServerStaticInMemory;
    Curr: Currency;
    DaVinci, s: RawUTF8;
    Refreshed: boolean;
    J: TSQLTableJSON;
    i, n, nupd, ndx: integer;
    IntArray, Results: TIntegerDynArray;
    Data: TSQLRawBlob;
    DataS: THeapMemoryStream;
    a,b: double;
................................................................................
       end;
    result := true;
  finally
    Free;
    T.Free;
  end;
end;
procedure Direct(const URI: RawUTF8; Hash: cardinal);
var call: TSQLRestServerURIParams;
begin
  call.Method :='GET';
  call.Url := Client.SessionSign(URI);
  call.RestAccessRights := @SUPERVISOR_ACCESS_RIGHTS;
  Server.URI(call);
  Check(Hash32(call.OutBody)=Hash);
end;
begin
  V := TSQLRecordPeople.Create;
  VA := TSQLRecordPeopleArray.Create;
{$ifndef LVCL}
  VO := TSQLRecordPeopleObject.Create;
{$endif}
  V2 := nil;
................................................................................
          Check(aStatic<>nil);
          aStatic.LoadFromJSON(JS); // test Add()
          for i := 0 to aStatic.Count-1 do begin
            Check(Client.Retrieve(aStatic.ID[i],V),'test statement+bind speed');
            Check(V.SameRecord(aStatic.Items[i]),'static retrieve');
          end;
          // test our 'REST-minimal' SELECT statement SQL engine
          Direct('/root/People?select=%2A&where=id%3D012',$9B10BE36);

          Direct('/root/People?select=%2A&where=id%3D:(012):',$9B10BE36);

          Direct('/root/People?select=%2A&where=LastName%3D%22M%C3%B4net%22',$7D90E964);

          Direct('/root/People?select=%2A&where=YearOfBirth%3D1873',$F0818745);

          Direct('/root/People?select=%2A',$17AE45E3);

          // test Retrieve() and Delete()
          Server.ExportServer; // initialize URIRequest() with the aStatic database
          USEFASTMM4ALLOC := true; // getmem() is 2x faster than GlobalAlloc()
          ClientDist := TSQLRestClientURIDll.Create(ModelC,URIRequest);
          try
            SetLength(IntArray,(aStatic.Count-1)shr 2);
            for i := 0 to high(IntArray) do begin
................................................................................
{$ifdef UNICODE}
{$WARNINGS ON} // don't care about implicit string cast in tests
{$endif}


{ TSQLRestServerTest }

procedure TSQLRestServerTest.DataAsHex(var aParams: TSQLRestServerCallBackParams);
var aData: TSQLRawBlob;
begin

  if (self=nil) or (aParams.Table<>TSQLRecordPeople) or (aParams.ID<0) then


    aParams.Error('Need a valid record and its ID') else
  if RetrieveBlob(TSQLRecordPeople,aParams.ID,'Data',aData) then
    aParams.Results([SynCommons.BinToHex(aData)]) else
    aParams.Error('Impossible to retrieve the Data BLOB field');



end;

procedure TSQLRestServerTest.Sum(var aParams: TSQLRestServerCallBackParams);
var a,b: Extended;
begin
  if UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin



    while aParams.Parameters<>nil do begin
      UrlDecodeExtended(aParams.Parameters,'A=',a);
      UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
    end;



    aParams.Results([a+b]);
  end else
    aParams.Error('Missing Parameter');
end;


{$ifndef LVCL}

{ TSQLRecordPeopleObject }