#1 2024-04-21 00:02:57

RaelB
Member
Registered: 2010-08-04
Posts: 57

Update record (via Orm.Retrieve) only works once

Hi,

I've got a small app, based on the martin-doyle InterfacedBasedServices demo.

Here are some pertinent code snippets:

  TNotebook = packed record
    ID: TID;
    Title: string;
  end;

  TNotebookArray = array of TNotebook;

  INotebookService = interface(IInvokable)
    ['{644FEAD9-5DCC-4C61-9740-C6FE8AD2CDAB}']
    function Add(var Notebook: TNotebook): TServiceCustomStatus;
    function Get(const NotebookId: TID; var Notebook: TNotebook): TServiceCustomStatus;
    function List(var Notebooks: TNotebookArray): TServiceCustomStatus;
    function Update(const Notebook: TNotebook): TServiceCustomStatus;
    function Delete(const NotebookId: TID): TServiceCustomStatus;
  end;

  TOrmNotebook = class(TOrm)
  private
    FCreatedAt: TCreateTime;
    FModifiedAt: TModTime;
    FTitle: string;
    FUserId: TSessionUserId;
  public
  published
    property CreatedAt: TCreateTime read FCreatedAt write FCreatedAt;
    property ModifiedAt: TModTime read FModifiedAt write FModifiedAt;
    property Title: string read FTitle write FTitle;
    property UserId: TSessionUserId read FUserId write FUserId;
  end;

function TNotebookService.Update(const Notebook: TNotebook): TServiceCustomStatus;
var
  OrmNotebook: TOrmNotebook;
begin
  OrmNotebook := TOrmNoteBook.Create;
  try                                                       
    if Self.Server.Orm.Retrieve(Notebook.ID, OrmNoteBook, True) then
    begin
      OrmNotebook.Title := Notebook.Title;
      Self.Server.Orm.Update(OrmNotebook);
      Result := HTTP_SUCCESS;
    end
    else
    begin
      Result := HTTP_NOTFOUND;
    end;
  finally
    OrmNotebook.Free;
  end;
end;

constructor TSampleServer.Create(aModel: TOrmModel; const aDBFileName:
    TFileName);
begin
  inherited Create(AModel, ADBFileName, True);

  ServiceDefine(TNotebookService, [INotebookService], sicShared);
  ...
end;

The first time the client is calling NotebookService.Update, Orm.Retrieve returns true, so the update code executes. The second time it is called (a few seconds later), Orm.Retrieve returns false, so the result is HTTP_NOTFOUND. However, the input parameter, i.e. Notebook.ID is the same in both cases, so I don't understand why Orm.Retrieve returns false.

Any ideas why this is happening?

(The client is a vcl TRestHttpClient)

Offline

#2 2024-04-21 06:53:49

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,686
Website

Re: Update record (via Orm.Retrieve) only works once

I am not able to reproduce it here.

Try to debug a little (at least enable the logs in verbose mode), to see what is happenning.

Offline

#3 2024-04-21 15:40:42

RaelB
Member
Registered: 2010-08-04
Posts: 57

Re: Update record (via Orm.Retrieve) only works once

Log when update called the first time:

20240421 11111121  !  +    mormot.soa.client.TServiceFactoryClient(02a53968).InternalInvoke INotebookService.Update({ID:9,Title:"General"}) 
20240421 11111121  !  +    	mormot.rest.http.client.TRestHttpClientSocket(029d52c0).InternalUri POST
20240421 11111121  ! trace 		mormot.rest.http.client.TRestHttpClientSocket(029d52c0) InternalRequest POST calling THttpClientSocket(029bbfb0).Request
20240421 11111121  "  +    server.TSampleServer(029fc120).URI POST root/NotebookService.Update?session_signature=0009b4c900274a19f9436761 in=24 B
20240421 11111121  " call  	server.TSampleServer(029fc120) NotebookService.Update[{ID:9,Title:"General"}]
20240421 11111121  " DB    	server.TSampleServer(029fc120) prepared 258us mORMotNotesVCL.db SELECT ID,CreatedAt,ModifiedAt,Title,UserId FROM Notebook WHERE RowID=?  [{id:2,detail:"SEARCH Notebook USING INTEGER PRIMARY KEY (rowid=?)"}] 
20240421 11111121  " SQL   	server.TSampleServer(029fc120) 492us id=9 SELECT ID,CreatedAt,ModifiedAt,Title,UserId FROM Notebook WHERE RowID=?
20240421 11111121  " cache 	mormot.db.raw.sqlite3.TSqlDatabase(02a53628) mORMotNotesVCL.db cache flushed
20240421 11111121  " DB    	server.TSampleServer(029fc120) prepared 151us mORMotNotesVCL.db UPDATE Notebook SET ModifiedAt=?,Title=?,UserId=? WHERE RowID=?  [{id:4,detail:"SEARCH Notebook USING INTEGER PRIMARY KEY (rowid=?)"}] 
20240421 11111121  " SQL   	server.TSampleServer(029fc120) 4.22ms  UPDATE Notebook SET ModifiedAt=:(135843590859):,Title=:('General'):,UserId=:(3): WHERE RowID=:(9):
20240421 11111121  " srvr  	User  Interface POST root/NotebookService.Update=200 out=16 B in 5.85ms
20240421 11111121  " ret   	mormot.rest.server.TRestServerRoutingRest(032c4e70) {"result":[200]}
20240421 11111121  "  -    00.005.985
20240421 11111121  ! clnt  		mormot.rest.http.client.TRestHttpClientSocket(029d52c0) POST root/NotebookService.Update?session_signature=0009b4c900274a19f9436761 status=200 len=16 state=0
20240421 11111121  !  -    	00.007.824
20240421 11111121  ! ret   	mormot.soa.client.TServiceFactoryClient(02a53968) {"result":[200]}
20240421 11111121  !  -    00.008.093

Log when called the second time:

20240421 11112003  !  +    mormot.soa.client.TServiceFactoryClient(02a53968).InternalInvoke INotebookService.Update({ID:9,Title:"Generalxxx"}) 
20240421 11112003  !  +    	mormot.rest.http.client.TRestHttpClientSocket(029d52c0).InternalUri POST
20240421 11112003  ! trace 		mormot.rest.http.client.TRestHttpClientSocket(029d52c0) InternalRequest POST calling THttpClientSocket(029bbfb0).Request
20240421 11112003  %  +    server.TSampleServer(029fc120).URI POST root/NotebookService.Update?session_signature=0009b4c900274a3b461dac76 in=27 B
20240421 11112003  % call  	server.TSampleServer(029fc120) NotebookService.Update[{ID:9,Title:"Generalxxx"}]
20240421 11112003  % srvr  	User  Interface POST root/NotebookService.Update=404 out=16 B in 210us
20240421 11112003  % ret   	mormot.rest.server.TRestServerRoutingRest(032c5170) {"result":[404]}
20240421 11112003  %  -    00.000.320
20240421 11112003  ! clnt  		mormot.rest.http.client.TRestHttpClientSocket(029d52c0) POST root/NotebookService.Update?session_signature=0009b4c900274a3b461dac76 status=404 len=16 state=0
20240421 11112003  !  -    	00.003.037
20240421 11112003  ! trace 	mormot.rest.http.client.TRestHttpClientSocket(029d52c0) POST root/NotebookService.Update returned 404 (Not Found) with message {"result":[404]}
20240421 11112003  ! ret   	mormot.soa.client.TServiceFactoryClient(02a53968) {"result":[404]}
20240421 11112003  !  -    00.003.342

On second call, it's not that the db query fails, it's that the db layer is not being called.

I have prepared a sample project:
https://www.dropbox.com/scl/fi/8s8axgdc … sfbrl&dl=0

It's a delphi project.
Steps to reproduce:
1. Press Connect
2. Type in notebook title edit, then press Add, to add an entry. (e.g. General)
3. Click on the entry in the list box (this fills the notebook edit)
4. Modify the title. (e.g. Generalxxx)
5. Press Edit, this works as expected
repeat 3 and 4. This time it shows message "Error updating.. " etc.

Offline

#4 2024-04-22 14:32:45

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,686
Website

Re: Update record (via Orm.Retrieve) only works once

With minimal debugging, we can see that the record is LOCKed so Update() fails.

As stated by the Retrieve() documentation:

    // - if ForUpdate is true, the REST method is LOCK and not GET: it tries to lock
    // the corresponding record, then retrieve its content; caller has to call
    // UnLock() method after Value usage, to release the record

You did not call UnLock().

So ForUpdate should be left to its default false value.

Two remarks:
- use a separated TRestServerDB for the persistence, and a TRestServerFullMemory for the services publication;
- use a lock to protect the TRestServerDB process if you make some nested ORM calls like Retrieve + Update, otherwise it may not be thread safe.

But thanks a lot for creating a minimal sample, to make it easy to guess the problem.

Offline

#5 2024-04-22 15:08:35

RaelB
Member
Registered: 2010-08-04
Posts: 57

Re: Update record (via Orm.Retrieve) only works once

Thanks a lot for the info. I'm going to be away for a few days. I will follow up when I return...

Offline

#6 2024-05-01 13:22:06

RaelB
Member
Registered: 2010-08-04
Posts: 57

Re: Update record (via Orm.Retrieve) only works once

I was curious to know what "minimal debugging" you had done?

Looking at the source, I could see a number of nested classes, and "method redirection". So I think it would take considerable time to gain some familiarity with that part of the code. Hence, I'm curious if you have some tips to bypass that, as a first step ..

Offline

#7 2024-05-01 14:32:30

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,686
Website

Re: Update record (via Orm.Retrieve) only works once

For instance:
- Identify the interface type definition;
- Look at the comments of the used method (e.g. Retrieve);
- Adapt the code to follow what is described here (e.g. "if ForUpdate is true, call Unlock to release the record").

We try to make the comments always accurate and up-to-date. It is the actual documentation.
If you find something wrong, please feel free to report.

Offline

#8 2024-05-02 00:25:14

RaelB
Member
Registered: 2010-08-04
Posts: 57

Re: Update record (via Orm.Retrieve) only works once

Thanks. I was looking for the right class instead of looking for the interface.

Offline

#9 2024-05-05 00:25:19

RaelB
Member
Registered: 2010-08-04
Posts: 57

Re: Update record (via Orm.Retrieve) only works once

ab wrote:

- use a separated TRestServerDB for the persistence, and a TRestServerFullMemory for the services publication;

1) With the current implementation, the service is inheriting from TInjectableObjectRest, and so has convenient and automatic access to the Server/DB instance. If there are 2 separate servers, how can I access the db server from the service method implementation, which is being defined for the memory server. i.e in TNotebookService.Update, Self.Server would be the TRestServerFullMemory. How to access a separate TRestServerDB instance?

2) Just to clarify, why you make this recommendation. I can see that in a more complex situation, it will be a good idea/more flexible setup. But in a simple "CRUD app" type of situation, where each server call is going to essentially make a call to the db layer, is there going to be an advantage to this setup?

Thanks

Offline

#10 2024-05-05 13:41:07

tbo
Member
Registered: 2015-04-20
Posts: 353

Re: Update record (via Orm.Retrieve) only works once

RaelB wrote:

How to access a separate TRestServerDB instance?

You can find posts on this topic in this forum.

type
  TCustomServiceObject = class(TInjectableObjectRest, ICustomService)
  protected
    function InternalAdd(const pmcItem: TOrmCustomRecord; pmForceID: Boolean = False): TID;
  public
    function GetDBDataRestServer: TRestServerDB; overload;
    function GetDBBlobRestServer: TRestServerDB; overload;
  end;

function TCustomServiceObject.InternalAdd(const pmcItem: TOrmCustomRecord; pmForceID: Boolean): TID;
var
  restServer: TRestServerDB;
begin
  restServer := GetDBDataRestServer;
  if (restServer <> Nil) and (pmcItem <> Nil) then
    Result := restServer.Server.Add(pmcItem, True, pmForceID)
  else
    Result := -1;
end;

function TCustomServiceObject.GetDBDataRestServer: TRestServerDB;
begin
  with TRestAdminServer(Server) do
    Result := RestServerPool.FindRestServer(GetDBDataRestServerID(ServiceRunningContext.Request.Session));
end;

function TCustomServiceObject.GetDBBlobRestServer: TRestServerDB;
begin
  with TRestAdminServer(Server) do
    Result := RestServerPool.FindRestServer(GetDBBlobRestServerID(ServiceRunningContext.Request.Session));
end;

With the technique shown above, you can also manage several separate databases for each organization without any problems. If you only want to use several databases, you do not need a pool and can access them directly. Then the following is enough:

type
  TServerMain = class(TSynPersistent)
  private
    FHttpServer: TVGSHttpServer;
    FDBDataRestServer: TDBDataRestServer;
    FDBBlobRestServer: TDBBlobRestServer;

With best regards
Thomas

Offline

Board footer

Powered by FluxBB