#1 Re: mORMot 1 » MVCServer Sample (30) - Redirection issue » 2019-12-31 14:47:59

Yes, that's one way. My initial idea was to have a MVC web application (just pure html, created with mustache templates) which at the same time could be used e.g. by a single page javascript web app which just calls the same urls but appends json to the existing urls. That would work if the redirection also were to work with the .../json urls.
If that redirection doesn't work, I need two different implementations of a server method for basically the same thing (e.g. in the case of the Login method of the example). Maybe that's a better way.

#2 mORMot 1 » basilique project from ekon » 2019-12-30 15:06:52

martin.suer
Replies: 3

Hi,

any news on that project? I think it could be a great real world project to learn from.

Martin

#3 mORMot 1 » MVCServer Sample (30) - Redirection issue » 2019-12-30 15:00:35

martin.suer
Replies: 3

Hi,

from the documentation I understand, that I can just append a 'json' to a url (as the last part before any parameters) and then just get the data context instead of the processed mustache template.
This basically works great but there's a problem with redirecting in this case.

Using the Sample "30 - MVC Server" I can call .../blog/login?LogonName=usrname&PlainPassword=abcd and get redirected to .../blog/default - which is correct.
However, if I call .../blog/login/json?LogonName=usrname&PlainPassword=abcd then I get redirected to .../blog/login/Default which doesn't exist and causes an Error 400. Somehow rewriting the url doesn't work if "json" is appended ...

If in this second case the redirection would redirect to .../blog/Default/json it would be possible to write an mvc application which uses html templates for creating html and at the same time I could write a fat client in some client language or a javascript client which makes use of the .../json urls. That way I would only have one server which can be used by different types of client easily.

Currently I can't do that because the automatic TMVCAction redirection doesn't work for urls which have the .../json appended.

Martin

#5 mORMot 1 » Mustache question - Accessing higher level context » 2016-02-23 16:28:30

martin.suer
Replies: 2

Hi,

if I have a nested data structure (context) that's passed to a template, can I access variables of an outer context from within a nested context?

sth. like this:

{{#ParentList}}
  {{#Children}}
     {{Name}} is a child of {{#../Father}}
  {{/Children}}
{{/ParentList}}

I understand that within the {{#Children}} tag, the context is switched to the actual element of the Children list. So if I want to access sth. that is up a level in the context hierarchy, is that possible somehow or do I have to create my data structures so that the Father value is an element of the Child to get what I want?

If it's not possible, wouldn't that be a useful extension to the mustache implementation of mORMot?

Thanks
Martin

#6 mORMot 1 » Sample 30 MVC Server - Login/Logout question » 2016-02-15 16:10:45

martin.suer
Replies: 1

Hi Arnaud,

in Sample 30 MVC Server there is a Login / Logout mechanism using a session. In the SAD in 19.2.6 you state "
Of course, you should never trust the cookie content ". My question is: Is this authentication mechanism in the blog example considered to be secure (assuming the web app runs over https) or not or to which extent? Are there scenarios where it's not secure?

Thanks
Martin

#7 Re: mORMot 1 » OFF-TOPIC: Merry christmas... » 2015-12-25 08:43:53

Merry Xmas from Korschenbroich (near Düsseldorf, Germany)

All the best to everyone and to Arnaud and the mORMots...

Martin

#8 mORMot 1 » TSMTPServer and ISMTPServerConnection usage » 2015-11-27 14:37:55

martin.suer
Replies: 4

Hi,

have had some trouble sending Mail with Indy TIdSmtp component and came across your TSMTPServer and related classes in dddInfraEmailer.pas.
Figured I can use them stand alone this kind:

var
  smtp : TSMTPServer;
  smtpconnection : ISMTPServerConnection;
  rcpt : TRawUTF8DynArray;
begin
  SetLength(rcpt,1);
  rcpt[0] := 'mail@abc.xyz';
  smtp := TSMTPServer.Create(TSMTPServerSocketConnection,'mailserver.local',25,'','');
  try
    smtpconnection := TSMTPServerSocketConnection.Create(smtp);
    smtpconnection.SendEmail(rcpt,'sender@xxx.zzz','Mailsubject','','Bodycontent');
    smtpconnection := nil;
  finally
    smtp.Free;
  end;
end;

A mail is actually sent with that code. My question is: Without setting up a Rest Server with DDD Mail daemon etc, is the code above the way to go for just sending a simple mail ?

Thanks
Martin

#9 Re: mORMot 1 » Interface based services using TSQLRecord with nested records as param » 2015-06-10 15:20:03

btw. thanks for all the efforts you put in this great framework

#12 Re: Delphi » GetIt.RealName := 'GetItIfItWontHurtOurSells' » 2015-06-09 19:36:06

I am mainly using Delphi because mORMot exists. I really like Object Pascal and Delphi but mORMot gives me the power do develop Enterprise grade Applications. Plus: since it's open source you can dig into the source if you ever have a real problem AND  I have never experienced such a responsive forum for any other framework

#14 mORMot 1 » Interface based services using TSQLRecord with nested records as param » 2015-06-09 19:13:50

martin.suer
Replies: 7

Hi Arnaud,

I am heavily making use of nested (Delphi-) records in my TSQLRecord descendants as published properties like this:

  TLand = packed record
    LKZ, LandName: RawUTF8;
  end;

  TAdresse = packed record
    Name, Zusatz, Strasse, Plz, Ort: RawUTF8;
    Land: TLand;
  end;
  
  TSQLMandant = class(TSQLRecord)
  private
    FMandantCode: RawUTF8;
    FAdresse: TAdresse;
  published
    property MandantCode: RawUTF8 read FMandantCode write FMandantCode;
    property Adresse: TAdresse read FAdresse write FAdresse;
  end;

I am using Windows8.1 with Delphi XE5 in this case.
Everything worked fine with the ORM Part of the framework.

Once I started to make more and more use of interface based services I came across a weired bug.

I assumed I could pass every TSQLRecord as an out parameter in an interface based service method.
Unfortunately it turns out that this is not true for TSQLRecord datastructures which contain
nested records.

Example interface:

  IServiceMandant = interface(IInvokable)
    ['{C1108544-FB6C-4582-A947-C9E3844C3CB6}']
    procedure GetMandant(MandantID: RawUTF8; out Mandant: TSQLMandant);
  end;

After some time of debugging I have found the following issue:

The remote method is executed correctly, the result is correctly serialized to JSON and passed back to the client which starts
to deserialize the result to the TSQLRecord-Object. At some Point the
method JSONToObject in mORMot.pas calls RecordLoadJSON in line 40562.

RecordLoadJSON in SynCommons.pas itself calls Reader:

    JSON := Reader(JSON,Rec,wasValid);
    if not wasValid then
      exit;
    if (JSON<>nil) and (JSON^<>#0) then begin
      EndOfObj := JSON^;
      inc(JSON);
    end else
      EndOfObj := #0;

The Reader already has moved the JSON pointer after the last character of the JSON-record-content at this point
but now the inc(JSON) moves it an additional character forward.

In my case the closing '}' of the TSQLRecord which contained the nested (Delphi-) record was skipped.

In Line 40687 in JSONToObject of mORMot.pas the EndOfObject Pointer is checked...

Valid := (EndOfObject='}');

That then leads to a false Valid flag which later causes the method InternalProcess to raise an exception in line 44663ff

            R := JSONToObject(V^,R,valid);
            if not valid then
              RaiseError('returned object',[]);

When I uncomment the inc(JSON) in RecordLoadJSON in SynCommons.pas I mentioned earlier, my TSQLRecord Objects which contain nested records can also
successfully be used as parameters in interface based service methods.

I don't know if this really is a valid fix for all situations or has any unwanted sideeffects where the inc(JSON) must be there and the issue
must be fixed somehow differently.

#15 mORMot 1 » supported datatypes in ddd podo » 2015-06-04 18:11:13

martin.suer
Replies: 6

Are arrays or lists supported as property types for ddd podos? (the TDomUser example doesn't use them)

#16 Re: mORMot 1 » Question on DTOs, Entities and Aggregate Roots » 2015-02-03 22:17:06

http://dddsample.sourceforge.net/index.html This link provides some solid information and example code (in Java) and helped me to get a quick start on DDD along side with Eric Evan's book

#17 Re: mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-16 15:16:55

I could arrange with defining the text constants and calling RegisterCustomJSONSerializerFromText but that doesn't work either.

In your post http://synopse.info/forum/viewtopic.php … 438#p14438 your generated code "sounds quite correct" but doesn't compile either.


The J in variable tmp is not defined.

tmp.J.SetLength(1);

#18 Re: mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-16 10:03:50

also the generated types for the record are

  :TRecB.:1 = record
    v1: Integer;
    v2: Integer;
  end;

  TRecB = record
    a1: array of :TRecB.:1;
    a2: array of Integer;
    v1: Integer;
  end;

#19 Re: mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-16 09:56:56

Hi ab,

I can't confirm that. I have changed the TRecB to your proposal (cut & pasted directly from the forum) and the generated code then looks like this:

function Variant2TRecB(const Value: variant): TRecB;
begin
  if VariantType(Value.a1)=jvArray then
    for var i := 0 to integer(Value.a1.length)-1 do
      result.a1.Add(:TRecB.:1(Value.a1[i]));
  if VariantType(Value.a2)=jvArray then
    for var i := 0 to integer(Value.a2.length)-1 do
      result.a2.Add(Integer(Value.a2[i]));
  result.v1 := Value.v1;
end;

the context is:

    {
      name: "TRecB",
      fields: 
      [
        {
          typeWrapper: "wArray",
          typeSource: "",
          typeDelphi: null,
          typePascal: null,
          typeCS: null,
          typeJava: null,
          isArray: true,
          propName: "a1",
          fullPropName: "a1",
          isSimple: null,
          nestedSimpleArray: {
            typeWrapper: "wRecord",
            typeSource: ":TRecB.:1",
            typeDelphi: ":TRecB.:1",
            typePascal: ":TRecB.:1",
            typeCS: ":TRecB.:1",
            typeJava: ":TRecB.:1",
            isRecord: true,
            toVariant: ":TRecB.:12Variant",
            fromVariant: "Variant2:TRecB.:1",
            nestedIdentation: "  ",
            isSimple: null
          }
        },
        {
          typeWrapper: "wArray",
          typeSource: "",
          typeDelphi: null,
          typePascal: null,
          typeCS: null,
          typeJava: null,
          isArray: true,
          propName: "a2",
          fullPropName: "a2",
          isSimple: null,
          nestedSimpleArray: {
            typeWrapper: "wInteger",
            typeSource: "Integer",
            typeDelphi: "Integer",
            typePascal: "Integer",
            typeCS: "integer",
            typeJava: "int",
            nestedIdentation: "  ",
            isSimple: true
          }
        },
        {
          typeWrapper: "wInteger",
          typeSource: "Integer",
          typeDelphi: "Integer",
          typePascal: "Integer",
          typeCS: "integer",
          typeJava: "int",
          propName: "v1",
          fullPropName: "v1",
          isSimple: true
        }
      ]
    }

#21 Re: mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-15 22:25:52

found the problem in the context, both arrays are tagged as simple arrays:

{
      name: "TRecB",
      fields: 
      [
        {
          typeWrapper: "wArray",
          typeSource: "",
          typeDelphi: null,
          typePascal: null,
          typeCS: null,
          typeJava: null,
          isArray: true,
          propName: "a1",
          fullPropName: "a1",
          isSimple: null,
          nestedSimpleArray: {
            typeWrapper: "wRecord",
            typeSource: "TRecA",
            typeDelphi: "TRecA",
            typePascal: "TRecA",
            typeCS: "TRecA",
            typeJava: "TRecA",
            isRecord: true,
            toVariant: "TRecA2Variant",
            fromVariant: "Variant2TRecA",
            nestedIdentation: "  ",
            isSimple: null
          }
        },
        {
          typeWrapper: "wArray",
          typeSource: "",
          typeDelphi: null,
          typePascal: null,
          typeCS: null,
          typeJava: null,
          isArray: true,
          propName: "a2",
          fullPropName: "a2",
          isSimple: null,
          nestedSimpleArray: {
            typeWrapper: "wInteger",
            typeSource: "Integer",
            typeDelphi: "Integer",
            typePascal: "Integer",
            typeCS: "integer",
            typeJava: "int",
            nestedIdentation: "  ",
            isSimple: true
          }
        },
        {
          typeWrapper: "wInteger",
          typeSource: "Integer",
          typeDelphi: "Integer",
          typePascal: "Integer",
          typeCS: "integer",
          typeJava: "int",
          propName: "v1",
          fullPropName: "v1",
          isSimple: true
        }
      ]
    }

#22 Re: mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-15 22:14:13

and with my fix applied the generated code changes to

function Variant2TRecB(const Value: variant): TRecB;
begin
  if VariantType(Value.a1)=jvArray then
    for var i := 0 to integer(Value.a1.length)-1 do
      result.a1.Add(Variant2TRecA(Value.a1[i]));
  if VariantType(Value.a2)=jvArray then
    for var i := 0 to integer(Value.a2.length)-1 do
      result.a2.Add(Variant2Integer(Value.a2[i]));
  result.v1 := Value.v1;
end;

which then calls the right conversion function Variant2RecA but breaks the typecast for Integer in the next for loop. So my fix doesn't work.
The type of the array member must be taken into account there

#23 Re: mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-15 22:05:59

ok, more details... I have done some more testing. (XE7,SMS2.1.2,Win8.1)

The following type in delphi

type
  TRecA=packed record
    v1,v2:integer;
  end;
  TRecB=packed record
    a1:array of TRecA;
    a2:array of integer;
    v1:integer;
  end;

results to this generated code in mORMotClient.pas (sms)

function Variant2TRecB(const Value: variant): TRecB;
begin
  if VariantType(Value.a1)=jvArray then
    for var i := 0 to integer(Value.a1.length)-1 do
      result.a1.Add(TRecA(Value.a1[i]));
  if VariantType(Value.a2)=jvArray then
    for var i := 0 to integer(Value.a2.length)-1 do
      result.a2.Add(Integer(Value.a2[i]));
  result.v1 := Value.v1;
end;

compiling that with sms raises
Syntax Error: Not a method [line: 226, column: 35, file: mORMotClient]

#24 Re: mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-15 21:09:02

I am talking about a record that contains an array of another record.

type
  Ta=record
    ...
  end;
  Tb=record
    arr:array of Ta;
    ...
  end;

so you may be right...

But a matter of fact is that the existing template generates code that can't be compiled by sms for the above data structure. I'm using such structure as a DTO in an interfaced service.

My fix resolves the issue for the above structure but I am not sure if it breaks something with arrays of simple types...

#25 mORMot 1 » Bug (and Fix) in CrossPlatform Template SmartMobileStudio.pas.mustache » 2015-01-15 16:32:40

martin.suer
Replies: 16

Line 152 needs to be changed to

      result.{{fullPropName}}.Add(Variant2{{typePascal}}(Value.{{fullPropName}}[i]));

I have added Variant2 so that the generated conversion function is called instead of the typecast. The tyecast of a variant to a recordtype is not supported in sms.

#26 mORMot 1 » TTextWriter.AddJSONReformat raises AV » 2015-01-05 11:44:18

martin.suer
Replies: 1

Hi,

TTextWriter.AddJSONReformat does support empty arrays but not empty objects.

So the following JSON leads to an AV if passed to AddJSONReformat

{ "empty": {} }

I came across this because I use the function with some non mORMot generated JSON which contains such empty objects.

Martin

#27 Re: mORMot 1 » Firebird transactions » 2014-11-30 22:31:07

the solution from the firebird point of view (how transactions work in firebird) would be to use different transactions for reading and writing.

for reading: transactions with til = read-read-commited for reading operations, those don't cause harm if retaining is used
for writing: transactions with til = snapshot, those mustn't be retained

I don't know if and how that can be achieved with zeos

#28 Re: mORMot 1 » ZEOS + Firebird + Update-Problem » 2014-09-01 20:20:23

Firebird transactions work a little different than in other DBs. Firebird uses mvcc (Multi Version concurrency Control). To those not familar with this interesting concept, it is worth reading about it.
To make a Long Story short, you should try to change the transaction Isolation Level from the Firebird default of snapshot to readcommited.

#30 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-17 20:14:53

CreateAndFillPrepare works.

The context is:

{"time":"2014-08-17 22:14:04","year":2014,"mORMotVersion":"1.18","root":"root","orm":[{"tableName":"Customer","className":"TSQLCustomer","fields":[{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":1,"name":"Name","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"comma":","},{"typeName":"ptInteger","typeDelphi":"integer","typePascal":"integer","typeCS":"integer","typeJava":"int","index":2,"name":"Age","sql":5,"sqlName":"sftInteger","typeKind":0,"typeKindName":"sftUnspecified","attr":0}],"isInMormotPas":null,"comma":","},{"tableName":"AuthUser","className":"TSQLAuthUser","fields":[{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":1,"name":"LogonName","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":1,"unique":true,"width":20,"comma":","},{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":2,"name":"DisplayName","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"width":50,"comma":","},{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":3,"name":"PasswordHashHexa","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"width":64,"comma":","},{"typeName":"ptCardinal","typeDelphi":"cardinal","typePascal":"cardinal","typeCS":"uint","typeJava":"long","index":4,"name":"GroupRights","sql":6,"sqlName":"sftID","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"comma":","},{"typeName":"ptRawByteString","typeDelphi":"TSQLRawBlob","typePascal":"TSQLRawBlob","typeCS":"byte[]","typeJava":"byte[]","isBlob":true,"toVariant":"BlobToVariant","fromVariant":"VariantToBlob","index":5,"name":"Data","sql":15,"sqlName":"sftBlob","typeKind":3,"typeKindName":"sftBlob","attr":0}],"isInMormotPas":true,"comma":","},{"tableName":"AuthGroup","className":"TSQLAuthGroup","fields":[{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":1,"name":"Ident","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":1,"unique":true,"width":50,"comma":","},{"typeName":"ptInteger","typeDelphi":"integer","typePascal":"integer","typeCS":"integer","typeJava":"int","index":2,"name":"SessionTimeout","sql":5,"sqlName":"sftInteger","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"comma":","},{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":3,"name":"AccessRights","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"width":1600}],"isInMormotPas":true,"comma":null}],"soa":null,"authClass":"TSQLRestServerAuthenticationDefault","uri":"root/wrapper/context","host":"localhost:888","port":888}

haven't found out why it's not null

am using xe6 (without update)

#31 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-17 20:07:51

the comma - bug you're referring to has been a different one
that was an encoding bug

in this case the "comma" variable in the template context shouldn't exist for the last field but it is set to ","

#32 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-17 20:04:10

I have the latest source (nightly build, downloaded today)
Both issues are not related to each other.

generated source:

/// remote access to a mORMot server using SmartMobileStudio
// - retrieved from http://localhost:888/root/wrapper/SmartMobileStudio/mORMotClient.pas.txt
// at 2014-08-17 22:02:42 using "SmartMobileStudio.pas.mustache" template
unit mORMotClient;

{
  WARNING:
    This unit has been generated by a mORMot 1.18 server.
    Any manual modification of this file may be lost after regeneration.

  Synopse mORMot framework. Copyright (C) 2014 Arnaud Bouchez
    Synopse Informatique - http://synopse.info

  This unit is released under a MPL/GPL/LGPL tri-license,
  and therefore may be freely included in any application.

  This unit would work on Smart Mobile Studio 2.1 and later.
}

interface

uses
  SmartCL.System,
  System.Types,
  SynCrossPlatformSpecific,
  SynCrossPlatformREST;


type
  /// map "Customer" table
  TSQLCustomer = class(TSQLRecord)
  protected
    fName: string; 
    fAge: integer; 
    // those overriden methods will emulate the needed RTTI
    class function ComputeRTTI: TRTTIPropInfos; override;
    procedure SetProperty(FieldIndex: integer; const Value: variant); override;
    function GetProperty(FieldIndex: integer): variant; override;
  public
    property Name: string read fName write fName;
    property Age: integer read fAge write fAge;
  end;
  

const
  /// the server port, corresponding to http://localhost:888
  SERVER_PORT = 888;


/// return the database Model corresponding to this server
function GetModel: TSQLModel;

/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port 888
// - secure connection will be established via TSQLRestServerAuthenticationDefault
// with the supplied credentials
// - request will be asynchronous, and trigger onSuccess or onError event
procedure GetClient(const aServerAddress, aUserName,aPassword: string;
  onSuccess, onError: TSQLRestEvent; aServerPort: integer=SERVER_PORT);


implementation


{ TSQLCustomer }

class function TSQLCustomer.ComputeRTTI: TRTTIPropInfos;
begin
  result := TRTTIPropInfos.Create(
    ['Name','Age',],
    [sftUnspecified,sftUnspecified,]);
end;

procedure TSQLCustomer.SetProperty(FieldIndex: integer; const Value: variant);
begin
  case FieldIndex of
  0: fID := Value;
  1: fName := Value;
  2: fAge := Value;
  end;
end;

function TSQLCustomer.GetProperty(FieldIndex: integer): variant;
begin
  case FieldIndex of
  0: result := fID;
  1: result := fName;
  2: result := fAge;
  end;
end;


function GetModel: TSQLModel;
begin
  result := TSQLModel.Create([TSQLCustomer,TSQLAuthUser,TSQLAuthGroup],'root');
end;

procedure GetClient(const aServerAddress, aUserName,aPassword: string;
  onSuccess, onError: TSQLRestEvent; aServerPort: integer);
begin
  var client := TSQLRestClientHTTP.Create(aServerAddress,aServerPort,GetModel,true);
  client.Connect(
  lambda
    try
      if client.ServerTimeStamp=0 then begin
        if Assigned(onError) then
          onError(client);
        exit;
      end;
      if not client.SetUser(TSQLRestServerAuthenticationDefault,aUserName,aPassword) then begin
        if Assigned(onError) then
          onError(client);
        exit;
      end;
      if Assigned(onSuccess) then
        onSuccess(client);
    except
      if Assigned(onError) then
        onError(client);
    end;
  end,
  onError);
end;


end.

using this way:

procedure TForm1.W3Button1Click(Sender: TObject);
var
  Client : TSQLRestClientURI;
  customer : TSQLCustomer;
  s : String;
begin
  GetClient('127.0.0.1','User','synopse',
    lambda (aClient: TSQLRestClientURI)
      Client := aClient;
      customer := TSQLCustomer.CreateAndFillPrepare(Client,'','',[]);
      while customer.FillOne do
      begin
        s := customer.Name + ' ' + IntToStr(customer.Age);
        ShowMessage(s);
      end;
    end,
    lambda
      ShowMessage('Impossible to connect to the server!');
    end);
end;

(which works with this simple TSQLCustomer and the deleted commata)

#33 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-17 19:00:01

Arnaud,

I am using it correctly. It seems that it has to do with the TSQLRecord I have used to generate the mORMotClient (number or types of the properties, don't know yet...).
Anyway. I am starting with something easier and get another issue:

Using this simple server:

program mORMotWrapGenTest;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  Classes,
  SynCommons,
  mORMot,
  mORMotWrappers,
  mORMotHttpServer,
  mORMotSQLite3,
  SynSQLite3Static;

type
  TSQLCustomer = class(TSQLRecord)
  private
    fName: RawUTF8;
    fAge: integer;
  published
    property Name : RawUTF8 read fName write fName;
    property Age : integer read fAge write fAge;
  end;

var
  FModel: TSQLModel;
  FServer: TSQLRestServer;
  FHTTPServer: TSQLHttpServer;
  fname : String;
  customer : TSQLCustomer;
begin
  fModel := TSQLModel.Create([TSQLCustomer]);
  fname := ChangeFileExt(paramstr(0), '.db3');
  if FileExists(fname) then
    DeleteFile(fname);
  fServer := TSQLRestServerDB.Create(fModel, fname, True);
  fServer.CreateMissingTables;
  AddToServerWrapperMethod(fServer,
    ['..\..\..\CrossPlatform\templates', '..\..\..\..\CrossPlatform\templates']);
  fHTTPServer := TSQLHttpServer.Create('888',[fServer],'+',useHttpApiRegisteringURI);
  fHTTPServer.AccessControlAllowOrigin := '*';
  customer := TSQLCustomer.Create;
  customer.Name := 'Testname';
  customer.Age := 20;
  fServer.Add(customer,true);
  customer.Free;
  WriteLn ('Server is running. Hit any key to exit.');
  ReadLn;
end.

the generated code for smart contains:

class function TSQLCustomer.ComputeRTTI: TRTTIPropInfos;
begin
  result := TRTTIPropInfos.Create(
    ['Name','Age',],
    [sftUnspecified,sftUnspecified,]);
end;

look at the comma after 'Age' and after sftUnspecified

The corresponding template context contains those commata.

When I manually correct the generated sms mORMotClient (removing the commata) I can use the unit with smart and CreateAndFillPrepare is working with that unit...

#34 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-15 18:57:50

Arnaud,

just a quick question:

are you sure that CreateAndFillPrepare is generally working with the generated client code in the latest code revision?
Whatever I do, I always get Error 400 in the response.
Other Methods ( Retrieve, Add, etc ) are working.

have tried with generated code for sms and cross plattform.

If it works for others, I have to investigate what I am doing wrong maybe on the server side but everything looks like explained in your docs.

#35 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-14 07:59:57

I should note that I've created the code with XE6 ...

#36 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-14 07:52:28

Ok, I have prepared some code:

program mORMotWrapGenTest;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  Classes,
  SynCommons,
  mORMot,
  mORMotWrappers,
  mORMotHttpClient,
  mORMotHttpServer,
  mORMotSQLite3,
  SynSQLite3Static;

type
  TAddress = packed record
    Street, Zip, City : RawUTF8;
  end;

  TSex = (male, female);

  TPerson = packed record
    Name, Phone : RawUTF8;
    Sex : TSex;
  end;
  TContacts = array of TPerson;

  TOrderPosition = class(TCollectionItem)
  private
    fDate: TDateTime;
    fDescription: RawUTF8;
    fVolume: double;
  published
    property Date: TDateTime read fDate write fDate;
    property Description: RawUTF8 read fDescription write fDescription;
    property Volume: double read fVolume write fVolume;
  end;

  TOrdersCollection = class(TInterfacedCollection)
  private
    function GetCollItem(Index: Integer): TOrderPosition;
  protected
    class function GetClass: TCollectionItemClass; override;
  public
    function Add: TOrderPosition;
    property Item[index: Integer]: TOrderPosition
      read GetCollItem; default;
  end;

  TSQLCustomer = class(TSQLRecord)
  private
    fName: RawUTF8;
    fAddress: TAddress;
    fContacts: TContacts;
    fOrders: TOrdersCollection;
  public
    constructor Create; override;
    destructor Destroy; override;
  published
    property Name : RawUTF8 read fName write fName;
    property Address : TAddress read fAddress write fAddress;
    property Contacts : TContacts read fContacts write fContacts;
    property Orders : TOrdersCollection read fOrders write fOrders;
  end;

  ICustomerMgmt = interface (IInvokable)
    ['{9FE0D344-4488-4460-81D7-C89F8D29EC4A}']
    procedure MoveCustomerToNewAddress(CustomerID : integer;
      const NewAddress: TAddress);
    procedure AddContactToCustomer(CustomerID : integer;
      const NewContact: TPerson);
  end;

  IOrderMgmt = interface (IInvokable)
    ['{BF335418-61B4-4657-AA84-314FEE6BB342}']
    function CreateOrderForCustomer(WithCustomerID : integer) : integer;
  end;

  TCustomerMgmt = class(TInterfacedObject, ICustomerMgmt)
  public
    procedure MoveCustomerToNewAddress(CustomerID : integer;
      const NewAddress: TAddress);
    procedure AddContactToCustomer(CustomerID : integer;
      const NewContact: TPerson);
  end;

  TOrderMgmt = class(TInterfacedObject, IOrderMgmt)
  public
    function CreateOrderForCustomer(WithCustomerID : integer) : integer;
  end;

var
  FModel: TSQLModel;
  FServer: TSQLRestServer;
  FHTTPServer: TSQLHttpServer;

{ TOrderMgmt }

function TOrderMgmt.CreateOrderForCustomer(WithCustomerID: integer): integer;
begin
  Result := WithCustomerID; // just to return something - it's only a test
end;

{ TCustomerMgmt }

procedure TCustomerMgmt.AddContactToCustomer(CustomerID: integer;
  const NewContact: TPerson);
begin
  // Read Customer, Add NewContact to Contacts, Save Customer
end;

procedure TCustomerMgmt.MoveCustomerToNewAddress(CustomerID: integer;
  const NewAddress: TAddress);
begin
  // Read Customer, assign new Address, Save Customer
end;

{ TOrdersCollection }

function TOrdersCollection.Add: TOrderPosition;
begin
  result := inherited Add as TOrderPosition;
end;

class function TOrdersCollection.GetClass: TCollectionItemClass;
begin
  result := TOrderPosition;
end;

function TOrdersCollection.GetCollItem(Index: Integer): TOrderPosition;
begin
  result := Items[index] as TOrderPosition;
end;

{ TSQLCustomer }

constructor TSQLCustomer.Create;
begin
  inherited;
  fOrders := TOrdersCollection.Create;
end;

destructor TSQLCustomer.Destroy;
begin
  fOrders.Free;
  inherited;
end;

begin
  fModel := TSQLModel.Create([TSQLCustomer]);
  fServer := TSQLRestServerDB.Create(fModel, ChangeFileExt(paramstr(0), '.db3'),True);
  fServer.CreateMissingTables;
  AddToServerWrapperMethod(fServer,
    ['..\..\..\CrossPlatform\templates', '..\..\..\..\CrossPlatform\templates']);
  fServer.ServiceRegister(TCustomerMgmt,[TypeInfo(ICustomerMgmt)],sicSingle);
  fServer.ServiceRegister(TOrderMgmt,[TypeInfo(IOrderMgmt)],sicSingle);
  fHTTPServer := TSQLHttpServer.Create('888',[fServer],'+',useHttpApiRegisteringURI);
  fHTTPServer.AccessControlAllowOrigin := '*';
  WriteLn ('Server is running. Hit any key to exit.');
  ReadLn;
end.

In the generated wrapper some types are missing:

* TPerson - which is used in an interface method procedure AddContactToCustomer(CustomerID: integer; NewContact: TPerson; onSuccess: procedure(); onError: TSQLRestEvent);

* The generated type TSQLCustomer has a property Orders: TOrdersCollection read fOrders write fOrders; but the type TOrdersCollection is missing.

#37 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-13 18:29:04

I will create some code that causes the issues I've discovered and that's free of other overhead and share it in the forum

#38 Re: mORMot 1 » Generates Cross-Platform mORMot Clients » 2014-08-13 14:35:41

This is really exceptionel, superb, awesome....

I did a quick test with some of my models and it generally works but I have some issues with the generated code.

1) when I have a TSQLRecord that has 3 different published boolean properties I get code like this generated:

...
type // define some enumeration types, used below
  Boolean = (False, True);
  Boolean = (False, True);
  Boolean = (False, True);
...

which obviously can't be compiled.

I have more issues and am willing to send you my model code for testing purposes

#39 Re: mORMot 1 » Using AuthUser to Select Table-Rows » 2014-08-09 18:17:48

Hi, I guess you two are misunderstanding each other on this topic...

itSDS wants to write an application where all users share the same db (shema) but each user has its own restricted view on all tables in a way where each user can only see the data he has written. E.g. each user manages some projects and a timetable. Each user should only see his own projects and his own timetable. Therefore itSDS has asked for how to automatically append sth. like "userid = authuser.id" to all where clauses...

AB talked about adding additional data to a user record like a picture of the user or some other data. That could be in the TSQLAuthUser Table in the blob field data but like AB mentioned is better put in a different table and then associated with the TSQLAuthUser. Also one can create a table that inherits from TSQLAuthUser and has more specific additional fields.

If I am right with my assumption about what itSDS wants to achieve, IMHO that is application specific / domain specific and has nothing to do with the framework. However a hook that would allow to modify a query / where clause before the query get's executed could be useful in some hypothetic cases.
But I almost always would recommend to write a specific service that handles your applications needs instead of blowing up the core and walk away from the kiss principle.

Martin

#40 Re: mORMot 1 » Retrieve new ID value before adding a TSQLrecord » 2014-07-24 09:49:23

Imagine the record with the highest id has been deleted. Can you tell right away what the next ID will be?
It's an implementation detail and you should never rely on that.

#41 Re: mORMot 1 » mORMot.JSONToObject issue with object containing a record field » 2014-07-20 21:37:03

Created the ticket with attached sample code to reproduce the problem.

#43 mORMot 1 » mORMot.JSONToObject issue with object containing a record field » 2014-07-20 16:00:48

martin.suer
Replies: 4

Hi AB,

using XE5 and the nightly build downloaded this morning,

I have a TSQLRecord descendant that contains a collection field. That collection contains derived CollectionItems. The derived CollectionItem class contains a field that is a record.

Serializing my TSQLRecord descendant to the DB works nicely. And since I use XE5, the record field in the collectiontem object is serialized nicely to json without any further registration calls beforehand.

However unserializing that TSQLRecord doesn't work. The collection isn't unserialized because in mORMot.JSONToObject in Line 31397:

        if Kind<>tkClass then
          exit; // true nested object should begin with '[' or '{' 

Kind contains tkRecord and so at that point JSONToObject exits.

So in other words I can write my TSQLRecord to the db but can't read it because JSONToObject does expect a class. A Record is not supported there.
Would be great if that's added there.

What do you think?

Martin

#45 Re: mORMot 1 » XE4 and FireMonkey » 2014-06-27 16:45:08

Hi Arnaud,

with XE6 I needed to add some changes in SynCrossPlatformSpecific.pas and SynCrossPlatformRest.pas to get the sample which I posted in post #40 running successfully !!!! Wohooo !!!!

It successfully retrieved a record from within the iOS-Simulator running on a Mac accessing a mORMot Server running on a different PC.

These are the changes:

In SynCrossPlatformSpecific I did the following:

changed line 324

   result := ThttpBody(TEncoding.UTF8.GetBytes(Text));

changed line 348

   Text := TEncoding.UTF8.GetString(TBytes(Body));

in the empty line 108 I added

   {$ifdef NEXTGEN} type AnsiChar = byte; {$endif}

And in SynCrossPlatformRest I changed the lines 833 and the following ones:

  while i<=length(aValue) do begin
{$ifndef NEXTGEN}
    inc(n);
{$endif}
    c := ord(aValue[i]);
    case c of
    ord('+'):
      utf8[n] := AnsiChar(' ');
    ord('%'): begin
      if i+2<=len then
        utf8[n] := AnsiChar(HexDecode(aValue[i+1],aValue[i+2])) else
        utf8[n] := AnsiChar('?');
      inc(i,2);
    end;
    else if c>127 then
      utf8[n] := AnsiChar('?') else
      utf8[n] := AnsiChar(c);
    end;
    inc(i);
{$ifdef NEXTGEN}
    inc(n);
{$endif}
  end;

Without these changes compilation failed for ios and android target on XE6.

The issues were type conversion errors between array of byte and TBytes . Also the utf8 variable is defined as a byte array for NEXTGEN in your code and in the UrlDecode procedure chars have been assigned to it.

I am wondering how itSDS was able to compile the units for iOS target ?????

#46 Re: mORMot 1 » changed properties » 2014-06-22 21:08:31

AWESOME !!!!

This is a killer feature - along with the many others available in mORMot - !

#47 Re: mORMot 1 » XE4 and FireMonkey » 2014-06-21 08:54:35

The invalid typecast error comes from the following:

function IsModTime(PropInfo: TRTTIPropInfo): boolean;
begin
  result := IdemPropName(string(PropInfo^.PropType^.Name),'TModTime');
end;

because PropInfo^.PropType^.Name is of type TSymbolName from unit System.Typeinfo and that is defined as follows in XE6

type
{$IFDEF NEXTGEN}
  TSymbolName = Byte;
{$ELSE  NEXTGEN}
  TSymbolNameBase = string[255];
  TSymbolName = type TSymbolNameBase;
{$ENDIF NEXTGEN}

so for NEXTGEN it is a Byte, otherwise a String[255]...

of course a Byte can't be compared to 'TModTime'

If I have some spare time I try to figure out what's the reason ...
My first Idea is that TSymbolName may have no meaning in NEXTGEN and is just a spare byte but who knows...

#48 Re: mORMot 1 » XE4 and FireMonkey » 2014-06-20 15:09:47

ab,

here we go (I used the latest nightly build)

I created a sample FMX mobile client with this code

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  FMX.Edit;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  SynCrossPlatformCrypto,
  SynCrossPlatformJSON,
  SynCrossPlatformREST,
  SynCrossPlatformSpecific;

// didn't want to change the sample unit, so pasted the definition here
// for testing purpose that should do it

type
  TSQLSampleRecord = class(TSQLRecord)
  private
    fQuestion: RawUTF8;
    fName: RawUTF8;
    fTime: TModTime;
  published
    property Time: TModTime read fTime write fTime;
    property Name: RawUTF8 read fName write fName;
    property Question: RawUTF8 read fQuestion write fQuestion;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  database : TSQLRest;
  model : TSQLModel;
  rec : TSQLSampleRecord;
begin
  model := TSQLModel.Create([TSQLSampleRecord]);
  database := TSQLRestClientHTTP.Create(Edit1.Text,8080,model);
  TSQLRestClientHTTP(database).SetUser(TSQLRestAuthenticationDefault, 'User','synopse');
  rec := TSQLSampleRecord.Create (database,1);
  Label1.Text := rec.Name;
  FreeAndNil(rec);
  FreeAndNil(database);
  FreeAndNil(model);
end;

end.

When I try to compile, the compiler first tells me that Contnrs.dcu can't be found:

[DCC Fataler Fehler] SynCrossPlatformJSON.pas(63): F1026 Datei nicht gefunden: 'C:\src\MobileMormotTest\Contnrs.dcu'

According to Embarcaderos White Paper http://edn.embarcadero.com/article/43073 in section 5.1 states that the oldstyle Contnrs unit is not available on the mobile platform...

So just to get further I exchanged Contnrs with System.Generics.Collections and added a type declaration for TObjectList:

uses
  SysUtils,
  Classes,
  System.Generics.Collections,
  Variants,
  TypInfo;

type
  TObjectList = TObjectList<TObject>;
  TStringDynArray = array of string;
  TVariantDynArray = array of variant;

That leads to a couple of new error messages:

Abh‰ngigkeiten des Projekts werden ¸berpr¸ft...
Compilieren von Project1.dproj (Debug, iOSSimulator)
dcc Befehlszeile f¸r "Project1.dpr"
  c:\xe6\embarcadero\studio\14.0\bin\dccios32.exe -$O- -$W+ --no-config -M -Q -TX. -[... deleted some pathnames here ...]  -NBC:\Users\Public\Documents\Embarcadero\Studio\14.0\Dcp\iOSSimulator -NO.\iOSSimulator\Debug   Project1.dpr   
[DCC Hinweis] SynCrossPlatform.inc(64): H2586 Alte '$IFEND'-Direktive gefunden. ƒndern Sie diese Direktive in '$ENDIF', oder aktivieren Sie $LEGACYIFEND
[DCC Hinweis] SynCrossPlatform.inc(67): H2586 Alte '$IFEND'-Direktive gefunden. ƒndern Sie diese Direktive in '$ENDIF', oder aktivieren Sie $LEGACYIFEND
[DCC Hinweis] SynCrossPlatform.inc(70): H2586 Alte '$IFEND'-Direktive gefunden. ƒndern Sie diese Direktive in '$ENDIF', oder aktivieren Sie $LEGACYIFEND
[DCC Hinweis] SynCrossPlatform.inc(74): H2586 Alte '$IFEND'-Direktive gefunden. ƒndern Sie diese Direktive in '$ENDIF', oder aktivieren Sie $LEGACYIFEND
[DCC Fehler] SynCrossPlatformJSON.pas(692): E2003 Undeklarierter Bezeichner: 'WideString'
[DCC Fehler] SynCrossPlatformJSON.pas(1152): E2089 Ung¸ltige Typumwandlung
[DCC Fehler] SynCrossPlatformJSON.pas(1157): E2089 Ung¸ltige Typumwandlung
[DCC Fehler] SynCrossPlatformJSON.pas(1169): E2089 Ung¸ltige Typumwandlung
[DCC Fehler] SynCrossPlatformJSON.pas(1189): E2003 Undeklarierter Bezeichner: 'GetWideStrProp'
[DCC Fehler] SynCrossPlatformJSON.pas(1192): E2003 Undeklarierter Bezeichner: 'GetUnicodeStrProp'
[DCC Fehler] SynCrossPlatformJSON.pas(1231): E2003 Undeklarierter Bezeichner: 'SetWideStrProp'
[DCC Fehler] SynCrossPlatformJSON.pas(1236): E2003 Undeklarierter Bezeichner: 'SetUnicodeStrProp'
[DCC Warnung] SynCrossPlatformJSON.pas(1350): W1000 Symbol 'TList' ist veraltet
[DCC Warnung] SynCrossPlatformJSON.pas(1351): W1000 Symbol 'TList' ist veraltet
[DCC Warnung] SynCrossPlatformJSON.pas(1351): W1000 Symbol 'Count' ist veraltet
[DCC Warnung] SynCrossPlatformJSON.pas(1354): W1000 Symbol 'TList' ist veraltet
[DCC Warnung] SynCrossPlatformJSON.pas(1354): W1000 Symbol 'Count' ist veraltet
[DCC Warnung] SynCrossPlatformJSON.pas(1355): W1000 Symbol 'TList' ist veraltet
[DCC Warnung] SynCrossPlatformJSON.pas(1355): W1000 Symbol 'List' ist veraltet
[DCC Fehler] SynCrossPlatformJSON.pas(1392): E2089 Ung¸ltige Typumwandlung
[DCC Fehler] SynCrossPlatformJSON.pas(1413): E2003 Undeklarierter Bezeichner: 'ShortString'
[DCC Fataler Fehler] Unit1.pas(31): F2063 Verwendete Unit 'SynCrossPlatformJSON.pas' kann nicht compiliert werden
Misslungen
Verstrichene Zeit: 00:00:00.4

will leave it with that for now.

#49 Re: mORMot 1 » SQLite3 DB Verses Firebird » 2014-06-20 10:35:21

I am a long time Delphi and Firebird user and started lurking at mORMot 6 month ago. Your question has no simple single right answer.
I liked Firebird very much for its stability, easy administration and full fledged Enterprise features (transactions, mvcc, stored procedures and trigger) and it has been quite performant. If one is looking for an RDBMS I would always vote for firebird if it’s able to scale up to your (customers) need (compared to e.g. Oracle) and you’re looking for a cheap solution.

Using an ORM like mORMot however raises a lot of totally other questions. Suddenly the DB will be used more or less as a simple storage  engine and you won’t need most of the features that firebird delivers. You probably won’t make a lot of use of stored procedures and triggers anymore, instead you write your code in Delphi and do most stuff outside the db.

Also the data model you are dealing with will be slightly different. The hardest point to me has been changing my mind from Relations to Objects and DDD.

Imagine you build a relational database schema. Theoretically you go and normalize your data to 1) keep your model consistent and 2) have the ability to perform all kind of possible queries. Practically in a real world project exactly that will quickly lead to unperformant databases because you have to do too much joins to get your query results. As a consequence you make compromises with your normalization and do the math and think about which queries are the ones you want to be answered in your application often and performant and then carefully adjust the model of your tables according to your needs.

Using an ORM you use an opposite aproach. You build your Objects according to the needs of your Business Processes or Domain. Unfortunately, since these Objects in the end are stored in a relational database you most likely or often also have to tweak your Object Model a little because you want to have the values that you use in query selection predicates (known as your sql where clause) to be mapped to single columns (in most cases). Speaking in DDD Terms, your aggregate objects content is usually not fully exposed to SQL and if you want your database to perform the selection instead of your Delphi code, you need to take that into account while designing your aggregate objects.

So, mORMot gives you the tools to use everything you need but of course can’t magically solve all data design questions. You have to do that on your own and if you come from a relational world and Delphi RAD that’s going to take some time (at least that is my experience).
But as far as I can tell it’s worth it. You get much cleaner code, your code is more maintainable and it’s more performant (if you build your object model right ;-) )

Finally coming back to your question: If you use mORMot, you should start using SQLite. It’s built in. 0-administration. You won’t need any firebird specific things. Also SQLite has one big advantage and that is TEXT-colums have no significant maximum length whereas firebirds varchar field has. And the operations that are used by mORMot are reasonably faster with SQLite3. This is not saying SQLite3 is better than firebird. It’s just better in conjunction with mORMot (or any other orm). However mORMot allows you also to use firebird for some or all your data tables. So you can do that if you have a good reason for it and need some data stored in firebird instead of SQLite.

#50 Re: mORMot 1 » XE4 and FireMonkey » 2014-06-19 18:56:00

It doesn't compile for iOS Target or Android Target yet. There are several issues. Several used String Types are not available in Nextgen. TObjectlist is not available. The whole Contnrs Unit is not available.
Do you want me to provide more Information? I can Test on xe5 & xe6 for iOS and Android.

Board footer

Powered by FluxBB