#1 Re: mORMot 2 » The Big Divide » 2024-10-15 12:05:03

tbo

The first time I promoted mORMot, I would have shared the points from the opening post. Later I thought, no matter what you say, many developers don't want to rethink. Today I promote mORMot with the slogan: It's on your computer in 3 minutes and removed without a trace in 3 seconds if you don't like it. Often I show concrete source code including the uses part. So it works with copy&paste.

I therefore tend to agree with @squirrel, but I'm not someone who knows anything about marketing.

With best regards
Thomas

#2 Re: mORMot 2 » How to use Authentication by user/password? » 2024-10-10 20:36:40

tbo

I have made the source code for the article example compilable with Delphi7. It can be downloaded from this post.

With best regards
Thomas

#3 Re: mORMot 2 » How to use Authentication by user/password? » 2024-10-09 16:49:11

tbo
mdbs99 wrote:

I don't want to use "model", but for testing I added

CreateWithOwnModel([TAuthGroup, TAuthUser], {HandleUserAuthentication=} True);

like the example, but then I got errors:

You have to write:

CreateWithOwnModel([TAuthGroup, TFileAuthUser], {HandleUserAuthentication=} True, ROOT_NAME_FILE);

Compatibility with Delphi 7 should be possible. Remove the inline variables. You can rewrite the function u_ServiceUtils.CheckFileName with mORMot's own functions to get rid of TPath. You can replace TWebBrowser component with a TMemo. Then only text files are possible.

With best regards
Thomas

#4 Re: mORMot 2 » How to use Authentication by user/password? » 2024-10-09 11:20:56

tbo

Perhaps this example will help you. Take a closer look at function TCustomServiceObject.GetSessionUserDirName.

With best regards
Thomas

#5 Re: mORMot 2 » Multiple field index » 2024-10-04 16:37:08

tbo
sag2007 wrote:

What is wrong with me??

The order of call is incorrect. You must call CreateMissingTables first and then CreateSqlMultiIndex. Look in unit test.orm.extdb.pas to see how it is implemented there. Or do it in the function InitializeTable of the ORM object.

With best regards
Thomas

#6 Re: mORMot 2 » issue with _Safe and InitArrayFromCsvFile » 2024-10-03 19:45:17

tbo
danielkuettner wrote:

That _Safe could return a read only fake DocVariantData is perhaps a little bit too much and contrary to the name „_safe“.

No. That is why you can write:

var
  v: Variant;
  isPeaPuree: Boolean;
begin
  isPeaPuree := _Safe(v).O_['One'].O_['Two'].O_['Three'].B['IsPeaPuree'];
  ShowMessage(IsPeaPuree.ToString(TUseBoolStrs.True));

You can easily reproduce the problem with the Init function. Switch on ReportMemoryLeaksOnShutdown. Write the following:

var
  names: TRawUtf8DynArray;
begin
  names := ['one', 'two'];
  names := Nil;

Set a Breakpoint at names := Nil. When you have reached this point during debugging, press Ctrl+Alt+D and open the disassembly window. Now you can see that the compiler has automatically inserted a call to DynArrayClear for this Nil assignment. However, if you write Pointer(names) := Nil, this does not happen and the dynamic array can no longer be cleaned up at the end. In this case, FastMM will report the lost memory.

With best regards
Thomas

#7 Re: mORMot 2 » issue with _Safe and InitArrayFromCsvFile » 2024-10-02 20:51:25

tbo
danielkuettner wrote:

How should I call clear here? What would clear do with an empty []?

v.Clear;
TDocVariantData(v).Clear;

Set breakpoints as described in article under point 3. Then you can see what is going on.

With best regards
Thomas

#8 Re: mORMot 2 » issue with _Safe and InitArrayFromCsvFile » 2024-10-02 19:01:39

tbo
danielkuettner wrote:

Can you explain a little bit.

Maybe this article will help you.

With best regards
Thomas

#9 Re: mORMot 1 » TOrmTableDataSet3 being Read-Only after Loading Data via CreateFromJso » 2024-09-23 19:13:45

tbo
youssef wrote:

However, I keep getting the error => TOrmTableDataSet3: Cannot modify a read-only dataset.

What is your question?

With best regards
Thomas

#10 Re: mORMot 2 » How to efficiently obtain 'dataset' using internal SQLite3 » 2024-09-09 21:01:37

tbo
Junior/RO wrote:

Can't find your article.

You must follow the link in the announcement. I write my posts in Delphi-Praxis Forum.

With best regards
Thomas

#11 Re: mORMot 2 » How to efficiently obtain 'dataset' using internal SQLite3 » 2024-09-08 19:04:21

tbo
hieroly wrote:

..., and there's no escaping the need to use reporting components like Fastreport, which require associating a Datasource or Dataset.

This is not absolutely necessary. I have written an article about it. Here is the announcement in this forum.

With best regards
Thomas

#12 Re: mORMot 2 » Where do I start? » 2024-09-07 11:27:05

tbo
Kabiri wrote:

Regarding the servers, I realized that when I use TRestServerDB, the user or client can easily access the tables. Authentication might prevent this access, but from what I’ve studied, it doesn’t seem to check the access details. For example, imagine I have a "status" field that marks records as deleted (without physically deleting them). In the method for listing data, I don’t send these records, but since the client has access to the table through ORM, they can retrieve those records.
Also, when I use TRestServerFullMemory, I no longer have access to the ORM.

You don't read the answers to your questions carefully. You're making it difficult for everyone here to help you. You have already received useful links for the start in post #2. Have you studied this example, which only uses an HTTP server? Once you have found the entry point, you can search the mORMot source code for it and quickly find the answer to your questions. This example only uses a REST server and shows the use of the ORM. These two examples (here and here) show how REST and HTTP servers work together without using the ORM. Why should it be necessary to expose access to the ORM? Use TRestServerFullMemory as Main and only open access to the ORM in the interface-based services. You can combine everything and have already been shown how to do it here. Please let us help you too. Read through the tips you have received again.

With best regards
Thomas

#13 Re: mORMot 2 » Where do I start? » 2024-08-15 19:37:37

tbo
anouri wrote:

As you can see in the code above, I pass the dbconnection to the InvoiceService constructor.

You must keep in mind that with the method you used to register the services, they are always sicShared. This is unimportant in a simple example, but you must take it into account for a server. But it is not necessary and with a little preparation you can implement it as shown in some examples.

Define your RestServer as follows:

type
  TMainRestServer = class(TRestServerFullMemory)
  strict private
    FConnectionPool: TSqlDBOdbcConnectionProperties;
  public
    property ConnectionPool: TSqlDBOdbcConnectionProperties
      read FConnectionPool;

CustomService as follows:

type
  ICustomService = interface(IInvokable)
    function GetConnection(out pmoConnection: TSqlDBConnection): Boolean;
  end;

type
  TCustomServiceObject = class(TInjectableObjectRest, ICustomService)
  protected
    function GetConnection(out pmoConnection: TSqlDBConnection): Boolean;

function TCustomServiceObject.GetConnection(out pmoConnection: TSqlDBConnection): Boolean;
begin
  Result := False;
  if TMainRestServer(Server).ConnectionPool <> Nil then
  begin
    pmoConnection := TMainRestServer(Server).ConnectionPool.ThreadSafeConnection;
    Result := (pmoConnection <> Nil);
  end;
end;

And use it in your services as follows:

var
  json: RawUtf8;
  dbConn: TSqlDBConnection;
  dbStmt: ISqlDBStatement;
begin
  if GetConnection(dbConn) then
  begin
    dbStmt := dbConn.NewStatementPrepared(...);
    dbStmt.ExecutePreparedAndFetchAllAsJson(False, json);

With best regards
Thomas

#14 mORMot 2 » Function ObjArrayDelete() access violation » 2024-07-10 09:20:54

tbo
Replies: 1

mORMot2, Commit 7777, Delphi 12.1, 32Bit

Access violation for these combinations in function ObjArrayDelete().

...ObjArrayHelper.Delete(aIndex: Integer): Boolean;
var
  Count: Integer;
begin
  Count := Length(Self);
  if (Count > 0)
    and (aIndex >= 0)
    and (aIndex < Count) then
  begin
    // Ok
    ObjArrayDelete(Self, aIndex);
  
    // Ok
    ObjArrayDelete(Self, Self[aIndex]);

    // raised exception class EAccessViolation with message 'Access violation at address 00FBCB6E. Read of address 70505408'.
    ObjArrayDelete(Self, Count, Self[aIndex]);

    // raised exception class EAccessViolation with message 'Access violation at address 00A2CB6E. Read of address 70505408'.
    ObjArrayDelete(Self, aIndex, False, @Count);

With best regards
Thomas

#15 Re: mORMot 2 » Where do I start? » 2024-07-03 19:16:08

tbo
Kabiri wrote:

I reviewed the mormot2 examples. In the examples, everyone uses the model. sad

Why is this a problem?

With best regards
Thomas

#16 Re: mORMot 2 » Is it possible to make https requests through mORMot ? » 2024-06-26 20:30:05

tbo
uses
  mormot.core.base,
  mormot.core.data,
  mormot.core.os,
  mormot.net.client;

var
  content: RawByteString;
begin
  content := HttpGet('https://tile.openstreetmap.org/10/549/339.png');
  if content <> '' then
    FileFromString(content, ChangeFileExt(ExeVersion.ProgramFileName, '.png'));

With best regards
Thomas

#17 mORMot 2 » function TrimLeft(), one character too few » 2024-06-04 14:00:54

tbo
Replies: 1

Unit: mormot.core.unicode, commit 7584
Function: function TrimLeft(const S: RawUtf8): RawUtf8;

There should be a +1 at the very end of the last line, otherwise one character too few will be returned.

if i = 1 then
  result := S
else
  FastSetString(result, @PByteArray(S)[i - 1], l - i + 1);

With best regards
Thomas

#18 Re: mORMot 2 » Update record (via Orm.Retrieve) only works once » 2024-05-05 13:41:07

tbo
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

#19 Re: mORMot 2 » Commit bf116a0 leads to IPO in Delphi 12 » 2024-03-26 13:50:14

tbo
ab wrote:

Anyway, I will revert it because it seems to be unneeded and unsafe - premature optimization or over optimization.
Which is the root of all evil, said. wink

Are you sure that's what you wanted?

 
else if (StartPos = 0) and
        (Len = L) and
        (PStrCnt(PAnsiChar(pointer(fDataString)) - _STRCNT)^ = 1) then
  FastAssignUtf8(Text, fDataString) // fast return the fDataString instance
else

The description says: "fast return the fDataString instance", but the second part of the sentence says: "and set src to Nil". After calling function FastAssignUtf8, "fDataString" is empty. The function should then better called: GetAsTextAndEmptyStreamIfLenIsSize(). big_smile

With best regards
Thomas

#20 Re: mORMot 2 » Issue: TSynLogFileView.Select » 2024-03-22 16:02:40

tbo

Commit 7267 tested and it works. Thank you very much and have a nice weekend.

With best regards
Thomas

#21 Re: mORMot 2 » Issue: TSynLogFileView.Select » 2024-03-22 15:46:58

tbo
ab wrote:

I don't see when fThreads[] could be = 0, unless the log file input is corrupted / was not generated by TSynLog.

If remote logging is used (TRestHttpsClient.CreateForRemoteLogging and LogView), this may be the case.

With best regards
Thomas

#22 mORMot 2 » Issue: TSynLogFileView.Select » 2024-03-22 15:07:19

tbo
Replies: 3

mORMot2, Commit 7264 (fedb2ee), Delphi 12, 32Bit
Unit mormot.core.log

Line 7756 should be changed. It should be checked that (fThreads[Index] > 0), otherwise the call to function GetBitPtr could run into nothing.

function TSynLogFileView.Select(aRow: integer): integer;
...
    for i := 0 to Count - 1 do
    begin
      if fLevels[i] in fEvents then
      begin
        if (fThreads = nil)
          or GetBitPtr(pointer(fThreadSelected), fThreads[i] - 1) then  // <-- here
        begin

With best regards
Thomas

#23 mORMot 2 » Issue: TSynLogFileView.SearchPreviousText » 2024-03-15 16:08:22

tbo
Replies: 1

mORMot2, Commit 7243 (ee6c774), Delphi 12, 32Bit
Unit mormot.core.log

Line 7550 should be changed. The search should not be started from "fCount" but from "fSelectedCount".

function TSynLogFileView.SearchPreviousText(const aPattern: RawUtf8; aRow: integer): PtrInt;
...  
  // search from end
  // for result := fCount - 1 downto aRow + 1 do  <= This line should be changed
  for result := fSelectedCount - 1 downto aRow + 1 do
    if LineContains(aPattern, fSelected[result]) then
      exit;  

With best regards
Thomas

#24 Re: mORMot 1 » ORM and Dynamic models » 2024-03-14 17:35:22

tbo
Vit wrote:

You can see, I'm pretty beginner, but it is quite interesting framework in general and this topic as well.

Read this article to find out more about DocVariant. The help describes how the properties are mapped.

With best regards
Thomas

#25 Re: mORMot 1 » ORM and Dynamic models » 2024-03-12 16:50:03

tbo
Vit wrote:

This is not clear for me, but will my possible solution "map" on DB field to one vaiants property?

Do Mormot fw have such a example? ORM implementation widely found in examples, but this - not.

You can read this article in Delphi-Praxis forum. It can also be found in the examples. To query this field directly via SQL, it uses the SQLite syntax.

With best regards
Thomas

#26 Re: mORMot 2 » Where is CurrentServiceContext in mORMot 2? » 2024-02-12 19:37:30

tbo
imperyal wrote:

I'm currently refactoring our code to use mORMot version 2 and I can't find the function CurrentServiceContext (previously found on the mORMot.pas unit) to get the TServiceRunningContext.

Unit mormot.rest.server
ServiceRunningContext, or better use TInjectableObjectRest for Interface-based Services.

With best regards
Thomas

#27 Re: mORMot 2 » THttpClientSocket.Post using THttpMultiPartStream » 2024-02-11 14:38:29

tbo
bigheart wrote:

I think, I don't seem to know how to use THttpMultiPartStream and THttpClientSocket.Post function.

Please give me some advice.

Did you watch the stream before sending it? The following simple test looks ok to me:

var
  mpStream: THttpMultiPartStream;
  fileStream: THandleStream;
begin
  mpStream := THttpMultiPartStream.Create;
  try
    mpStream.AddContent('Field01', '100', TEXT_CONTENT_TYPE);
    mpStream.AddContent('Field02', '{"Name": "Thomas"}', JSON_CONTENT_TYPE);
    mpStream.AddFileContent('Data', 'data.json', '{"Name": "Thomas","Nickname": "tbo"}', JSON_CONTENT_TYPE, 'binary');
    mpStream.AddFile('Test', MakePath([Executable.ProgramFilePath, 'test.json']));
    mpStream.AddFile('Image', MakePath([Executable.ProgramFilePath, 'image.png']));
    fileStream := TFileStreamEx.Create(MakePath([Executable.ProgramFilePath, 'mpTest.dat']), fmCreate);
    try
      mpStream.Flush;
      StreamCopyUntilEnd(mpStream, fileStream);
    finally
      fileStream.Free;
    end;
  finally
    mpStream.Free;
  end;

With best regards
Thomas

#28 Re: mORMot 2 » Bug: AV if returned object has empty string fields » 2024-02-03 22:50:44

tbo
ab wrote:

Do you have more clue?

Sorry Arnaud, I made a stupid copy-paste mistake. Everything is fine with mORMot. The nasty thing was, the Delphi debugger totally misled me. I shouldn't work until the middle of the night. The only good thing was that I got some new ideas from all the debugging. Sorry again for the false alarm.

With best regards
Thomas

#29 mORMot 2 » Bug: AV if returned object has empty string fields » 2024-02-03 00:03:43

tbo
Replies: 3

mORMot2, Commit 6984, Delphi XE, 32Bit

If the returned object has empty string fields, an AV occurs. Fields with text content have no problems.

Error message:

An exception of class EAccessViolation with message "Access violation at address 00604E98" has occurred. Reading address 0EFFFFF8 occurred.

The call stack is:

mormot.core.data.TRawUtf8Interning.Unique(???,'',0)
mormot.core.json._JL_RawUtf8($2ACEADC {''},$12EC84)
mormot.core.json._JL_RttiCustomProps('q',$12EC84)
mormot.core.json._JL_RttiCustom('q',$12EC84)
mormot.core.json._JL_RttiObjectWithID('¬ê¬'#2#$12#$F'P',$12EC84)
mormot.core.interfaces.TInterfaceMethodArgument.SetFromJson(...)

JSON looks like this:

'{RowID:0,ActiveState:0,CreatedAt:0,ModifiedAt:0,RecVersion:0,Username:"",Password:""},false]'

Function FastAssignNew in TRawUtf8Interning.Unique() fails:

procedure TRawUtf8Interning.Unique(
...
  if (aText = nil) or
     (aTextLen <= 0) then
    FastAssignNew(aResult)

With values: aText = '' and aTextLen = 0

With best regards
Thomas

#30 Re: mORMot 2 » How to make where in column text json » 2024-02-01 14:39:31

tbo
mrbar2000 wrote:

I appreciate any guidance or suggestions you can offer.

In SQLite version 3.38.0 or higher, it can be queried using the following SQL syntax as described in this article (near the end).

With best regards
Thomas

#31 Re: mORMot 2 » Communication between microservices » 2024-01-22 21:14:21

tbo
Basti-Fantasti wrote:

Do you know if there's a sample project available which shows the rest server pool usage ...

Sorry no, I have already written about this, but can't find the thread (I post in several forums). Perhaps you will find some inspiration in this thread. Another reader may have links at hand. The easiest way is to start from an example and incorporate the various techniques described. The help is written very detailed.

With best regards
Thomas

#32 Re: mORMot 2 » Communication between microservices » 2024-01-22 19:44:56

tbo
Basti-Fantasti wrote:

But in this scenario all functions would be inside a monolithic server process. As in a normal rest server implementation I could have several endpoints/routes but I could not replace only one part/route while keeping the rest of the other modules unchanged. I would always need to deploy a complete new server binary.

No, you can specify server URI and port and set up several Rest clients as required.

Generally: How you organize your data is up to you. You can have one or more databases for all customers, or one or more databases for each customer. The interfaces only do your routing and the necessary administration. What happens in the background afterwards is up to you. Example: Access via a pool of servers would be:

function TCustomServiceObject.GetReportRestOrm: IRestOrm;
begin
  with TAdminRestServer(Server) do
    Result := RestServerPool.FindReportRestServer(GetReportRestServerID(ServiceRunningContext.Request.Session)).Orm;
end;

function TReportService.UpdateSource(const pmcRowID: TID; const pmcSource: RawBlob): Boolean;
var
  orm: IRestOrm;
begin
  orm := GetReportRestOrm;
  if orm <> Nil then
    Result := orm.UpdateBlob(TOrmReport, pmcRowID, 'Source', pmcSource)
  else
    Result := False;
end;

Here you fetch the corresponding RestServer via a SessionID. The data can come from wherever you want. It can also be another service, created by you or by an outside service provider.

PS: My examples are simple to get started, but can also be easily expanded.

With best regards
Thomas

#33 Re: mORMot 2 » Communication between microservices » 2024-01-22 18:46:40

tbo
Basti-Fantasti wrote:

Thanks vor your reply, but how can i handle this, when I have a Setup Like this:

Myservice1.exe
Myservice2.exe
Myservice3.exe
Guiapp.exe

If you want to run multiple HttpServers, I see no advantage in this, you can instantiate the clients as follows:

TRestHttpClient.Create(ServerURI, TOrmModel.Create([], ROOT_NAME_SERVER), ServerPort, {Https=} (ServerPort = 443));

For an introduction to the topic, you can read articles Introduction to method-based services and Introduction to Interface-based Services in Delphi-Praxis forum.

With best regards
Thomas

#34 Re: mORMot 2 » Communication between microservices » 2024-01-22 16:48:18

tbo
Basti-Fantasti wrote:

Do I need to publish each service on a separate port on my PC, and directly connect my App which wants to fetch data from different services to each Service:Port individually?

You can register several services for one RestServer and one root name:

function TXRestServer.InitializeServices: Boolean;
begin
  Result := (ServiceDefine(TX1Service, [IX1], sicSingle) <> Nil);
  Result := Result and (ServiceDefine(TX2Service, [IX2], sicClientDriven) <> Nil);

You can register several RestServers for one HttpServer and port:

FHttpServer := TRestHttpServer.Create(pmcPort, [FXRestServer, FYRestServer], '+' {DomainName}, useHttpSocket);

Depending on the root name for a RestServer, it could look like this (example: root for X-RestServer is "store" and for Y-RestServer is "admin"):

domain.com/store
domain.com/admin

With best regards
Thomas

#35 Re: mORMot 2 » How to use ObjectLoadJson to deserialize json not generated by mORMot? » 2024-01-10 16:16:47

tbo
liam1983 wrote:

My problem is that with 500 child objects in the list, the whole process lasts 10 seconds for deserialisation. Is there a way to speed it up?

Something else is wrong. In this article you will find some benchmark values for comparison. The time for 500 objects should only be a few milliseconds.

With best regards
Thomas

#36 Re: mORMot 2 » How to use ObjectLoadJson to deserialize json not generated by mORMot? » 2024-01-10 14:51:59

tbo
liam1983 wrote:

Valid is true but the object isnt populated with any data.

Only "published" properties are serialized. If you want to serialize other properties, you must register your own functions with TRttiJson.RegisterCustomSerializer().

With best regards
Thomas

#37 mORMot 2 » TRttiCustomProps.NameChange - Access violation » 2024-01-09 18:26:08

tbo
Replies: 1

Delphi 12 Athens, mORMot2 V2.2

Following test case:

type
  TTestItem = record
    Name1: String;
    Name2: String;
  end;

var
  item: TTestItem;
begin
  item.Name1 := 'A';
  item.Name2 := 'B';
  ShowMessage(Utf8ToString(RecordSaveJson(item, TypeInfo(TTestItem))));

All tests except for the last one are ok. This leads to an access violation:

Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChange('Name1', '');  // Ok: {"Name2":"B"}
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1'], ['']);  // Ok: {"Name2":"B"} 
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1', 'Name2'], ['N1', 'N2']);  // Ok: {"N1":"A","N2":"B"}
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1', 'Name2'], ['N1', '']);  // Ok: {"N1":"A"}
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1', 'Name2'], ['', 'N2']);  // Error: Access violation

With best regards
Thomas

#38 Re: mORMot 2 » Error after making a change to martin-doyle\04-InterfacedBasedServices » 2024-01-02 18:57:52

tbo
RaelB wrote:

I imagine the code should actually just modify the log filename, not the full path.

You are right. It should look like this:

procedure TServerLog.ComputeFileName;
begin
  inherited ComputeFileName;
  FFileName := MakePath([ExtractFilePath(FFileName), StringReplace(ExtractFileName(FFileName), ' ', '_', [rfReplaceAll])]);
end;

With best regards
Thomas

#39 Re: mORMot 2 » Error after making a change to martin-doyle\04-InterfacedBasedServices » 2024-01-02 17:09:11

tbo
RaelB wrote:

@Thomas: My problem with your demo smile is that I get this error:

First chance exception at $75ADF932. Exception class EOSException with message 'TFileStreamEx.Create(C:\Delphi\Components_Full\Database\mORMot2\ex\ThirdPartyDemos\tbo\04-HttpServer-InterfaceServices\bin\TestRestServer_20240102_175743.log) failed as ERROR_PATH_NOT_FOUND'. Process TestRestServer.exe (5340)

I have tested it with the following and it works for me without any problems:

  • Windows 10

  • Delphi 12 Athens

  • mORMot2 Version 2.2

Unfortunately, I can't help you.

With best regards
Thomas

#40 Re: mORMot 2 » Error after making a change to martin-doyle\04-InterfacedBasedServices » 2024-01-02 14:51:57

tbo
ab wrote:

Try to define your services BEFORE calling SetUser().

Are you sure? Then all my comments in source code since mORMot1 are wrong:

  // IMPORTANT: First log in and then call ServiceDefine()!
  if not FServerRestClient.SetUser(pmcAdminUsername, pmcAdminPassword, {HashedPassword=} False) then
    Exit(scsErrLoginAdminUser); //=>

  if not InitializeServices then
    Exit(scsErrInitializeServices); //=>
end;

@RaelB: Have you already tried this example?

With best regards
Thomas

#41 mORMot 2 » TSynDaemon.AfterCreate() » 2024-01-01 22:24:59

tbo
Replies: 1

I have switched to the latest mORMot2 version. The following source code has worked so far. Two log files are now created. One for TSynLog and one for TVGServerLog.

type
  TVGServerLog = class(TSynLog)
  protected
    procedure ComputeFileName; override;
  end;

constructor TVGServerDaemon.Create;
begin
  inherited Create(TVGServerSettings, ...
  Settings.LogPath := TFileUtils.GetLogFileFolder(TVGServerSettings(Settings).ServerID);
  Settings.SetLog(TVGServerLog);

Now TSynDaemon.AfterCreate must be overwritten. Name AfterCreate() does not really say what is being done. Perhaps a name like SetLogClassAfterCreate() would be more descriptive. If the LogClass has already been assigned in TSynDaemonSettings Create, it will be overwritten in AfterCreate(). Maybe check for Nil:

procedure TSynDaemon.AfterCreate;
begin
  if RunFromSynTests then
    fSettings.fLogClass := TSynLog // share the same TSynLog for all daemons
  else if fSettings.LogClass = Nil then
    fSettings.SetLog(TSynLog); // real world logging
end;

Best wishes and a Happy New Year 2024 to all.

With best regards
Thomas

#42 Re: mORMot 2 » WebSocketsUpgrade with RemoteLogging caused ESynLogException » 2023-12-13 15:38:15

tbo
jienyuan wrote:

Have any idea? Thank you.

I have created a simple unit for this:

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.UITypes, System.Classes,
  mormot.core.base, mormot.core.text, mormot.core.rtti, mormot.core.log;

type
  TRemoteLogging = class(TComponent)
  strict private
    FLogClass: TSynLogClass;
  protected
    const
      DEFAULT_PORT = 8091;
  public
    destructor Destroy; override;
    procedure Start(pmLogClass: TSynLogClass; const pmcUri: RawUtf8 = '127.0.0.1/LogService');
    procedure Stop;
  end;

implementation

uses
  mormot.net.sock,
  mormot.rest.http.client;

destructor TRemoteLogging.Destroy;
begin
  Stop;
  inherited Destroy;
end;

procedure TRemoteLogging.Start(pmLogClass: TSynLogClass; const pmcUri: RawUtf8);
var
  uri: TUri;
begin
  if pmLogClass <> Nil then
  try
    FLogClass := pmLogClass;
    uri.From(pmcUri, Int32ToUtf8(DEFAULT_PORT));
    TRestHttpsClient.CreateForRemoteLogging(uri.Server, pmLogClass, Utf8ToInteger(uri.Port), uri.Root);
  except
    on E: Exception do
      pmLogClass.Add.Log(sllError, E);
  end;
end;

procedure TRemoteLogging.Stop;
begin
  if FLogClass <> Nil then
    FLogClass.Family.EchoRemoteStop;
end;

It is used like this:

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  ...
  TRemoteLogging.Create(Self).Start(TSynLog);

With best regards
Thomas

#43 Re: mORMot 2 » Output parameters of the RawUtf8 type are incorrectly displayed » 2023-11-09 16:13:08

tbo
lfyey121 wrote:

If errmsg returns a Chinese string, the client displays it incorrectly, but if errmsg is of type string, the return is correct. I don't know why.

With this minimal information, I can only guess:

var s: String := Utf8ToString(errMsg);

With best regards
Thomas

#44 Re: mORMot 2 » Project lazarus not build with mormot2 and zeos » 2023-11-04 13:14:49

tbo
cgsousa21 wrote:

Compile package mormot2 2.0.1: Exit code 1, Errors: 1, Hints: 2 ...

What could be wrong?

I think you are using a mORMot2 version that was created before May 2022. It would be better to update to the current one. That's all I can help with.

With best regards
Thomas

#45 Re: mORMot 2 » is it possible to map ID of a TOrm to a Record variable? » 2023-10-28 22:12:33

tbo
dcoun wrote:

I am using this record to be able to copy data from one TOrmMyclass instance to an other by just copying the myclassdata record variable between them.

Why do you want to do it this way? Does not the following also meet your requirements:

type
  TOrmItem = class(TOrm)
  private
    FField1: Integer;
    FField2: Integer;
    FField3: Integer;
  published
    property Field1: Integer
      read FField1 write FField1;
    property Field2: Integer
      read FField2 write FField2;
    property Field3: Integer
      read FField3 write FField3;
  end;

var
  item1, item2: TOrmItem;
begin
  item1 := TOrmItem.CreateWithID(High(Int64));
  try
    item1.Field1 := 1;
    item1.Field2 := 2;
    item1.Field3 := 3;
    var json: RawJson := item1.GetJsonValues({Expand=} False, {WithID=} True, 'Field1,Field3');
    item2 := TOrmItem.CreateWithID(1);
    try
      ShowMessage(item2.GetJsonValues(True, True, ALL_FIELDS));
      item2.FillFrom(Pointer(json));
      ShowMessage(item2.GetJsonValues(True, True, ALL_FIELDS));
    finally
      item2.Free;
    end;
  finally
    item1.Free;
  end;

The result is:

{"RowID":1,"Field1":0,"Field2":0,"Field3":0}
{"RowID":9223372036854775807,"Field1":1,"Field2":0,"Field3":3}

With best regards
Thomas

#46 Re: mORMot 2 » Difference in URL parsing between mORMot1 and 2 » 2023-10-27 14:23:38

tbo
squirrel wrote:

example-03:
in u_SharedTypes, the following line causes [dcc32 Error] u_SharedTypes.pas(75): E2034 Too many actual parameters
      pmCheckedFileName^ := TPath.Combine(dirName, fileName, False)
Removing the ,false parameter will cause it to compile.

You are right, in Delphi 10.3 the definition still looks like this: "class function TPath.Combine(const Path1, Path2: string): string;".

With best regards
Thomas

#47 Re: mORMot 2 » Difference in URL parsing between mORMot1 and 2 » 2023-10-27 13:29:40

tbo
squirrel wrote:

The example-03 does not compile, ...

The example compiles for me without errors, warnings or hints. You do not specify which Delphi version you are using, nor which mORMot commit.

If the function name is expanded with a slash, the log shows following:

GET Files/GetAllFileNames/=400
mormot.rest.server.TRestServerRoutingRest {  "errorCode":400,  "errorText":"Invalid URI"  }

The following is written in help:

function TServiceCalculator.Add(n1, n2: integer): integer;
will accept such requests:
- URL='root/Calculator.Add' and InBody='[ 1,2 ]'
- URL='root/Calculator.Add?+%5B+1%2C2+%5D' // decoded as ' [ 1,2 ]'
- URL='root/Calculator.Add?n1=1&n2=2'      // in any order, even missing

If the help is correct, it is the expected result.

PS: If you put a breakpoint in function TRestServer.Uri() at position "node := fRouter.Lookup(ctxt);", you can trace it.

With best regards
Thomas

#48 Re: mORMot 2 » Difference in URL parsing between mORMot1 and 2 » 2023-10-27 12:16:46

tbo
squirrel wrote:

Is this the expected behaviour?

I can't reproduce your observation with this example. Please compare what you do differently.

PS: Sorry, I missed the slash at the end. Yes, with slash 400 is returned.

With best regards
Thomas

#49 Re: mORMot 2 » DynArrayLoadJson can not process the JSON with "a", "A" names data? » 2023-10-23 19:13:19

tbo
ab wrote:

It was obvious that a TValuePUtf8Char.ToDouble method was missing.

I would lean towards the following:

type
  TValuePUtf8Char = record
    ...
    function ToFloat: TSynExtended; overload;
    function ToFloat(const pmcDefaultValue: TSynExtended): TSynExtended; overload;

function TValuePUtf8Char.ToFloat: TSynExtended;
begin
  Result := GetExtended(Text);
end;

function TValuePUtf8Char.ToFloat(const pmcDefaultValue: TSynExtended): TSynExtended;
var
  err: Integer;
begin
  Result := GetExtended(Text, err);
  if err <> 0 then
    Result := pmcDefaultValue;
end;

Then it can be written like this:

if pmvContext.ParseObject(['c', 'a', 'A'], @recValues) then
begin
  rec.c := recValues[0].ToFloat;
  rec.R1.a1 := recValues[1].ToFloat(-1);
  rec.R1.a2 := recValues[2].ToFloat(NaN);

With best regards
Thomas

#50 Re: mORMot 2 » DynArrayLoadJson can not process the JSON with "a", "A" names data? » 2023-10-22 15:25:50

tbo
wqmeng wrote:

As you see that we split the JSON text to be a nested record, and would like to DynArrayLoadJson the JSON directly to the recs array.

This does not work automatically. Your definition must correspond to the JSON data format. But you can write and register your own serializer:

type
  TTestRec = packed record
    c: Double;     // -> c
    R1: record
      a1: Double;  // -> a
      a2: Double;  // -> A
    end;
  end;
  PTestRec = ^TTestRec;

  TTestRecArray = array of TTestRec;

  TTestRecArrayFiler = class(TObject)
  public
    class procedure CustomReader(var pmvContext: TJsonParserContext; pmData: Pointer);
  end;

class procedure TTestRecArrayFiler.CustomReader(var pmvContext: TJsonParserContext; pmData: Pointer);
var
  rec: PTestRec absolute pmData;
  recValues: array[0..2] of TValuePUtf8Char;
begin
  if pmvContext.ParseObject(['c', 'a', 'A'], @recValues) then
  begin
    rec.c := GetExtended(recValues[0].Text);
    rec.R1.a1 := GetExtended(recValues[1].Text);
    rec.R1.a2 := GetExtended(recValues[2].Text);
  end;
end;

const
  JSON = '[{"c":"28403.81000000","a":"28420.61000000","A":"0.00351000"},{"c":"0.13690000","a":"0.13930000","A":"408.00000000"}]';
var
  recs: TTestRecArray;
begin
  if DynArrayLoadJson(recs, JSON, TypeInfo(TTestRecArray), Nil, True) then
  begin
    for var i: Integer := 0 to High(recs) do
      ShowMessage(Format('c: %.5f, a: %.5f, A: %.5f', [recs[i].c, recs[i].R1.a1, recs[i].R1.a2]));
  end;
  
initialization
  TRttiJson.RegisterCustomSerializer(TypeInfo(TTestRec), TTestRecArrayFiler.CustomReader, Nil);

With best regards
Thomas

Board footer

Powered by FluxBB