You are not logged in.
I think current repo is good for me.
Subfolder name indicates it's function, very clear.
But the document or TFM(The fuck manual) is too big to read.
To find a correct class to implement my program is hard work too.
So, maybe we could introduce some divided sample project(some "best practices" I think?), more than samples we have got now.
Then freshmen could step up more easily.
Maybe we could maintain some pratices wiki?
Hi,
I've ported MormotReport.pas to mormot.ui.report.pas.
Just some adoption of uses and procedures.
please check it. thanks.
link is here https://github.com/synopse/mORMot2/pull/124
Current static objects version is 3.37.2(?), while mormot.db.raw.sqlite3.static is at version 3.38.0.
You may tempery avoid this issue by comment out this line and relevant calls.
Please stay safe ....
Thanks, ab. you are amazing.
I've walked pass these lines for many times, but found nothing.
This pr do fix this issue.
Thank you again!
regards.
uian
Hi, ab
I've made a pull request to demonstrate this issue.
Ex31-chat server/client works well in mormot1, but can go wrong in mormot2.
regards.
uian
Client subscribes a service by calling service.Subscribe(aClientName, aClientInterface).
Then service will do this work by calling methid InterfaceArrayAdd(fClients, aClientInterface) .
I'll try mormot1 later, with the result posted here.
thanks
Hi ab,I think I may have found the key to the problem. The point is that the same interface is repeatedly released.
When I commented out InterfaceArrayDelete in CallbackReleased, server keeps running without av but callbacks were not released either.
I guess there's something wrong with this point in the framework.
You've mentioned a concept about re-release of interface (weak pointer), is it related here?
regards.
uian
One can override only the same ancestor method. If the method was declared with a different parameters it can be only overload or reintroduce.
You are right.
See TInjectableObjectRest.
This is quite the answser.
More over, you can inherite your service from TInjectableObjectRest, then override the constructor CreateWithResolverAndRest.
This way DI automaticly processed when you define interface implementation.
Regards.
This constructor should be override, not reintroduced.
Otherwise, you should create instance by hand, then register it using overloaded version of ServiceDefine(aSharedImplementation, [aInterfaces])
Edit 2022 01 24 1556
Wired DI features are implemented in REST part of the code, not SOA part.
regards
Hi, ab.
I am trying something similer to restws.longwork, with additional implementation of CallbackReleased procedure.
IDataService = interface(IInvokable)
['{9DDC69FF-5212-4F64-9B98-ABFBF14A27D4}']
....
procedure CallbackReleased(const callback: IInvokable; const interfaceName: RawUTF8);
TDataService = class(TInjectableObjectRest, IDataService)
protected
fClients: TInterfaceDynArray;
....
procedure TDataService.CallbackReleased(const callback: IInvokable; const interfaceName: RawUTF8);
begin
InterfaceArrayDelete(fClients, callback);
end;
When a client released, this procedure is processed then av occurred in mormot.core.interfaces
procedure TInterfaceMethodExecuteRaw.AfterExecute;
var
i, a: PtrInt;
arg: PInterfaceMethodArgument;
begin
// finalize managed arrays for each call
Finalize(fRawUtf8s);
Finalize(fStrings);
Finalize(fWideStrings);
if fMethod^.ArgsManagedFirst >= 0 then
begin
for i := 0 to fMethod^.ArgsUsedCount[imvvObject] - 1 do
fObjects[i].Free;
for i := 0 to fMethod^.ArgsUsedCount[imvvInterface] - 1 do
IUnknown(fInterfaces[i]) := nil; <-------------- av here Line7372
Am I implementing it in a wrong way?
My env: Win10/X64/Delphi XE/git@20220123
Best regards.
I have tried this commit, and it truely worked.
Thanks for your great work!
Hi, ab.
Thanks for your fix, that's efficent and do works for me.
I've digging a new issue.
According to TZipRead.Create(Buf...), directory is not count as Entry.
constructor TZipRead.Create(BufZip: PByteArray; Size: PtrInt; Offset: Int64);
...
if P[-1] = fZipNamePathDelim then
begin
h := hnext;
continue; // ignore void folder entry
end;
...
But, when we need to search a data descriptor before a directory, the result will be descriptor of this directory not that file.
[local file header n] (file n) <-- Entry[n].localoff
[zipped file data n]
[data descriptor n]
[local file header n+1] (directory after target file)
[zipped file data n+1]
[data descriptor n+1]
[local file header n+2] (file under nearby directory) <-- Entry[n+1].localoff
[zipped file data n+2]
[data descriptor n+2]
In this case RetrieveFileInfo will return false.
Regards
I didn't find a good size to reduce mem, I'll try this one.
Thanks ab.
I've made a pr https://github.com/synopse/mORMot2/pull/69, see if it works.
regards.
1.I'll do some test and make a pr if I could fix it.
2.For TZipRead only, I think add a FaverEncode param in constructor might be a good option.
Most of times, one zip file is built with one single Charset, so let the user fix unstanderd files dose make sense.
Hi, ab
I'm working on a project extracting zipped files online and found two issues about TZipRead.
1. TZipRead demonds more too big WorkMem to extract content files when fileinfo is stored in DataDiscriptor.
I must assign WorkingMem to filesize to make it run, even the half will fail.
TZipRead.Create(BufZip: PByteArray; Size: PtrInt; Offset: Int64);
// ...
if e^.localoffs >= Offset then
begin
// can unzip directly from existing memory buffer
e^.local := @BufZip[Int64(e^.localoffs) - Offset];
with e^.local^.fileInfo do
if flags and FLAG_DATADESCRIPTOR <> 0 then
// crc+sizes in "data descriptor" -> call RetrieveFileInfo()
if (zcrc32 <> 0) or
(zzipSize <> 0) or
(zfullSize <> 0) then
raise ESynZip.CreateUtf8('%.Create: data descriptor (MacOS) with ' +
'sizes for % %', [self, e^.zipName, fFileName]);
// ...
In constructor, BuffZip must contain even the first local info to setup Entry.local, else we must call RetrieveFileInfo to get local.
function TZipRead.RetrieveFileInfo(Index: integer;
out Info: TFileInfoFull): boolean;
// ...
if e^.local = nil then
begin
local.DataSeek(fSource, e^.localoffs + fSourceOffset);
if local.fileInfo.flags and FLAG_DATADESCRIPTOR <> 0 then
raise ESynZip.CreateUtf8('%: increase WorkingMem for data descriptor ' +
'(MacOS) support on % %', [self, e^.zipName, fFileName]);
Info.localfileheadersize := local.Size;
end
else
begin
Info.localfileheadersize := e^.local^.Size;
if e^.local^.fileInfo.flags and FLAG_DATADESCRIPTOR <> 0 then
// ...
But in RetrieveFileInfo() Exception will be raised because Entry.local equals nil!
Maybe we should try to setup Entry.local first because we just skipped this step in the constructor?
2.Sometimes charset of filename is not setup correctly in zip files, in that case TZipRead.NameToIndex will not work well.
Can I specify a default encoding type when I open a file, and use this default encoding type (such as UTF8) instead of the OemToFileName when ansi7 detection fails?
Best regards.
I'm sorry, some changes missed in my pull-request.
I can't push that to github.com duel to my terrible network.
Please, somebody fix this pr
filename: packages/lazarus/mormot2.lpk
changes line 12:
original
<OtherUnitFiles Value="../../src/app;../../src/core;../../src/db;../../src/lib;../../src/net;../../src/orm;../../src/rest;../../src/soa;../../src/tools/ecc"/>
changed
<OtherUnitFiles Value="../../src/app;../../src/core;../../src/db;../../src/lib;../../src/net;../../src/orm;../../src/rest;../../src/soa;
// add content begin here
../../src/crypt;
// add content end here
../../src/tools/ecc"/>
In my opinion, master/slave replication function is used for database maintainer, not for regular user.
So, maybe you can implement master/slave replication with the old fasion.
And implement regular function with interface-orinted fasion.
Thank you @mpv, your explanation is very convincing.
Thank you @ab, you've build this awesome framework.
Because TWebBrowser.Navigate() automatically executes UrlEncode, I thought HttpGet should behave the same way.
If httpGet itself is positioned to simply make http requests, that's right.
thx @tbo, will try your approach.
thx @ab, I've post a sample here
Pastebin-uMain.pas.
Pastebin-uMain.dfm.
pls check it.
Delphi XE,Windows 7 X64
thank you.
Address part of uri was not encoded.
when I make a request like this.
HttpGet('http://127.0.0.1/hello world');
Server side Ctxt.Url will be
'/hello'
But if it was made by a TWebBrowser.Navigate(), that Ctxt.Url will be
'/hello%20world'
Hi ab,
I've used mormot2 as working toolbox.
I've making use of THttpServer as a server, while using TWebBrowser as client.
They can work around very well.
When I fetch some data from THttpServer with HttpGet, Uri.Address was truncated before blank.
I've make an additional step, which might be a default behavior of HttpGet.
var
uri: TUri;
begin
if not uri.From(aUrl) then Exit;
HttpGet(Format('%s://%s:%s/%s',[uri.Schema, uri.Server, uri.Port, UrlEncode(uri.Address)]));
end;
Nice work!
Good news of 2021.
I've test this trunk under Win10 X64, code page 936(zh-cn,中文简体).
When I compile and run this test project with D7, 6 asserts failed, but with DXE, about 2,353 asserts failed.
Errors occurred when processing characters. I know it's complicated, Just commit this issue, and hope some HERO could resolve it.
Compiled with D7.
1.2. Core process: - RTTI: 1,338 assertions passed 390us - Url encoding: 200 assertions passed 411us ! - Encode decode JSON: 1 / 427,570 FAILED 28.77s - Wiki markdown to html: 56 assertions passed 731us - Variants: 99 assertions passed 515us ! - Mustache renderer: 5 / 58 FAILED 36.61ms - TDocVariant: 91,785 assertions passed 102.82ms - TDecimal128: 17,446 assertions passed 2.02ms - BSON: 245,072 assertions passed 20.04ms 100000 TBsonObjectID.ComputeNew in 18.91ms i.e. 5,287,368/s, aver. 0us - TSelectStatement: 221 assertions passed 627us - TSynMonitorUsage: 1,202 assertions passed 603us Total failed: 6 / 785,047 - Core process FAILED 28.95s
Compile with DXE.
1.1. Core base: - Ini files: 7,028 assertions passed 33.74ms ! - UTF8: 14,000 / 1,205,874 FAILED 1.18s - Url decoding: 1,101 assertions passed 687us ! - Baudot code: 6,285 / 10,007 FAILED 28.90ms - Iso 8601 date and time: 200,831 assertions passed 17.22ms Total failed: 20,285 / 19,604,610 - Core base FAILED 5.54s 1.2. Core process: ! - Mustache renderer: 5 / 58 FAILED 38.66ms - TDocVariant: 91,785 assertions passed 107.47ms - TDecimal128: 17,446 assertions passed 1.51ms ! - BSON: 8 / 245,072 FAILED 4.40ms 100000 TBsonObjectID.ComputeNew in 3.11ms i.e. 32,144,005/s, aver. 0us Total failed: 13 / 834,650 - Core process FAILED 8.41s 1.4. Core ecc: ! - Certificates and signatures: 6 / 91 FAILED 80.73ms Total failed: 6 / 521,825 - Core ecc FAILED 1.04s
Run log is too large to be post here....
I can't access pastbin.com by now, so I've just trimed error report.
hi, @Xantharim
I've make a sample, which show the same result between IndyHttp and WinHttp(from mORMot->SynCrtSock)
You can check it here.
And the result here
btnWinHttp
{"result":"UNKNOWN_USER","description":"unknown username","customerId":null,"configs":[]}
btnIndy
{"result":"UNKNOWN_USER","description":"unknown username","customerId":null,"configs":[]}
According to the file format defination, we can not get that size before uncompress.
I think @johnnysynop is just worried that we're going to take unnecessary litigation risks.
Perhaps you can submit relevant test methods, steps, and results to support your idea. If possible, submit the relevant patch to resolve the compatibility issues that @ab talked about.
It's always easy to talk, and doing some substantive work is better for the community as a whole.
Good luck.
when I changes the property from TSQLRecord decedent class to TID, ALL TEST GOES FINE!
I think the reason is that TSQLRecord field stores the pointer of that instance.
It is mentioned here: TID Fields.
TSQLRecord published properties do match a class instance pointer, so are 32-bit (at least for Win32/Linux32 executables).
Question is: If these TSQLRecord fields just save dynamic values, then how could we use them?
This is the modified TestProject, all testcases passed.
And more, TSQLRestServerFullMemory server failed the last check, for it supports query with only one field, or it will return nothing.
function TSQLRestStorageInMemory.EngineList(const SQL: RawUTF8;
ForceAJAX: Boolean; ReturnedRowCount: PPtrInt): RawUTF8;
// - GetJSONValues/FindWhereEqual will handle basic REST commands (not all SQL)
// only valid SQL command is "SELECT Field1,Field2 FROM Table WHERE ID=120;",
// i.e one Table SELECT with one optional "WHERE fieldname = value" statement
// - handle also basic "SELECT Count(*) FROM TableName;" SQL statement
// Note: this is sufficient for OneFieldValue() and MultiFieldValue() to work
var MS: TRawByteStringStream;
...
begin
...
Stmt := TSynTableStatement.Create(SQL,
fStoredClassRecordProps.Fields.IndexByName,
fStoredClassRecordProps.SimpleFieldsBits[soSelect]);
try
if (Stmt.SQLStatement='') or // parsing failed
(length(Stmt.Where)>1) or // only a SINGLE expression is allowed yet <--here
not IdemPropNameU(Stmt.TableName,fStoredClassRecordProps.SQLTableName) then
// invalid request -> return ''
exit;
if Stmt.SelectFunctionCount=0 then begin
So I replace TSQLRestServerFullMemory with TSQLRestServerDB. It dose make sense.
I insert detail record like this. Is this a wrong way using TSQLRecord as reference?
var
qMaster: TSQLRecordMaster;
qDetail: TSQLRecordDetail;
begin
...
// create qMaster and qDetail
qDetail.Master:=qMaster;
...
fRest.Add(qDetail);
...
end;
And the query of TSQLRecordDetail looks like this. Is it right?
var
qMaster: TSQLRecordMaster;
qDetail: TSQLRecordDetail;
begin
...
// check value of qMaster
with TSQLRecordDetail.AutoFree(qDetail, fRest, 'Master=?', [qMaster]) do
while qDetail.FillOne do
begin
// do something with qDetail
end;
end;
Hi, I have two TSQLRecord descendant classes, one refs another.
// first class - to be referenced.
TSQLRecordMaster=class(TSQLRecord)
private
fDummyName: RawUTF8;
published
property DummyName: RawUTF8 read fDummyName write fDummyName;
end;
// second class - references first one.
TSQLRecordDetail=class(TSQLRecord)
private
fMaster: TSQLRecordMaster;
fDummyName: RawUTF8;
published
property Master: TSQLRecordMaster read fMaster write fMaster;
property DummyName: RawUTF8 read fDummyName write fDummyName;
end;
both classes' insertion works fine, but the second class query always return nothing.
function TDummyService.DetailGet(aMaster: TMaster; out aDetails: TDetails):
Integer;
var
qMaster: TSQLRecordMaster;
qDetail: TSQLRecordDetail;
begin
Result := 0;
with TSQLRecordMaster.AutoFree(qMaster,fRest,'DummyName=?',
[aMaster.MasterName]) do
if qMaster.FillOne then
with TSQLRecordDetail.AutoFree(qDetail,fRest,'Master=?',
[qMaster]) do
while qDetail.FillOne do // always jump to the end
begin
SetLength(aDetails, Result+1);
aDetails[Result].Master := aMaster;
aDetails[Result].DetailName := qDetail.DummyName;
Inc(Result);
end;
end;
I can find records of second class in the json file, so I'm sure that data exists.
[{"Master":[
{"RowID":1,"DummyName":"This is Master"}]
},{"Detail":[
{"RowID":1,"Master":35972812,"DummyName":"This is detail No.1"}]
}]
But why?
I've test this case On D7 & DXE, same result.
Here(pastbin) comes the full source, so everyone could reproduce this issue.
I'm sorry for my ignorance of forum rules.
You are awesome @ab.
I've failed several times on cloning my fork to local workspace due to my legacy network.
when i've been defeated, awo, it has been done!
I have checked that fix, it works for me!
Thank you for you great work, again.
thanks @mpv, I'll make a pr of this patch.
Hi ab, I've tried make initialization in another procedure, they are ugly.
so I introduced a new constructor of TInjectedObjectRest to make sure something could be done logically when instance was created.
codes here.
original TInjectableObjectRest
TInjectableObjectRest = class(TInjectableObject)
protected
fFactory: TServiceFactoryServer;
fServer: TSQLRestServer;
public
then goes my new constructor
/// initialize an instance, defining one dependency resolver factory and
// restserver
// - the resolver may be e.g. a TServiceContainer
// - once the DI/IoC is defined, will call the AutoResolve() protected method
constructor CreateWithResolverAndRest(aResolver: TInterfaceResolver;
aFactory: TServiceFactoryServer; aServer: TSQLRestServer;
aRaiseEServiceExceptionIfNotFound: boolean=true); virtual;
and new reference class
/// class-reference type (metaclass) of a TInjectableObjectRest type
TInjectableObjectRestClass = class of TInjectableObjectRest;
implimentation of constructor
{ TInjectableObjectRest }
constructor TInjectableObjectRest.CreateWithResolverAndRest(
aResolver: TInterfaceResolver; aFactory: TServiceFactoryServer;
aServer: TSQLRestServer; aRaiseEServiceExceptionIfNotFound: boolean=true);
begin
inherited CreateWithResolver(aResolver, aRaiseEServiceExceptionIfNotFound);
fFactory := aFactory;
fServer := aServer;
end;
finally modified TServiceFactoryServer.CreateInstance
function TServiceFactoryServer.CreateInstance(AndIncreaseRefCount: boolean): TInterfacedObject;
var dummyObj: pointer;
begin
case fImplementationClassKind of
ickWithCustomCreate:
result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create;
ickInjectable:
result := TInjectableObjectClass(fImplementationClass).
CreateWithResolver(Rest.Services,true);
/// modify begin here -- separate ickInjectableRest from previous condition
ickInjectableRest:
result := TInjectableObjectRestClass(fImplementationClass).
CreateWithResolverAndRest(Rest.Services,self,RestServer,true);
/// modify end here
ickFromInjectedResolver: begin
dummyObj := nil;
if not TSQLRestServer(Rest).Services.
TryResolveInternal(fInterface.fInterfaceTypeInfo,dummyObj) then
raise EInterfaceFactoryException.CreateUTF8(
'ickFromInjectedResolver: TryResolveInternal(%)=false',[fInterface.fInterfaceName]);
result := TInterfacedObject(ObjectFromInterface(IInterface(dummyObj)));
if AndIncreaseRefCount then // RefCount=1 after TryResolveInternal()
AndIncreaseRefCount := false else
dec(TInterfacedObjectHooked(result).FRefCount);
end;
....
Could you please merge this patch , only if it is compatible to mormot framework?
Thanks ab, I'll do these works in another procedure.
hi there, I've implemented a class which inherited from TInjectableObjectRest.
I need to do some check on ORM, and boom, AV occurs.
My code is something like this:
constructor TSvcCacheXXX.CreateWithResolver(aResolver: TInterfaceResolver;
aRaiseEServiceExceptionIfNotFound: boolean);
begin
inherited;
fModel := Server.Model; // AV! Server is nil here.
if -1 = fModel.GetTableIndex(TSQLXXX) then
fModel.AddTable(TSQLXXX);
Server.CreateMissingTables;
end;
I've done some work, and found relative code in mormot.pas:
function TServiceFactoryServer.CreateInstance(AndIncreaseRefCount: boolean): TInterfacedObject;
var dummyObj: pointer;
begin
case fImplementationClassKind of
ickWithCustomCreate:
result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create;
ickInjectable, ickInjectableRest: begin
result := TInjectableObjectClass(fImplementationClass).
CreateWithResolver(Rest.Services,true); // Call of CreateWithResolver
if fImplementationClassKind=ickInjectableRest then begin // Factory and Server was assigned AFTER Create call of TInjectableObjectRest.CreateWithResolver
TInjectableObjectRest(result).fFactory := self; // Assign of Factory
TInjectableObjectRest(result).fServer := RestServer; // Assign of Server
end;
end;
....
I'm I making a wrong use-case of TInjectableObjectRest.CreateWithResolver?
Or, should we do some tweak on the logical sequence of creation call and property assignment?
If you are talking about requests of mORMot, this open tickets may help you.
Hi ab,
Example 35 is a great example of DDD, which showing us the architecture and iterative process of a DDD projects.
In the very current state, this project registers a Windows service, which exposes two Delphi interfaces, providing conference booking and BookProcessSettings storage functions.
My question is, how do I consume/use the services provided by these two interfaces?
Would you mind continue iterating this project and show some practice on how you guys do the client part?
Thanks.
Thx ab.
That patch works for me.
Same error, same solution.
I've searched my dev environment, Delphi XE, found no method named TThread.TerminateSet.
I've searched that method on EMB api wiki, and it was first seen on version XE2.
http://docwiki.embarcadero.com/Librarie … dset&go=Go
I think we should make a more accurate condition of CompilerVersion.
By the way XE2 CompilerVersion is 23.
How about trying extract country names of SQLfile into csv format or lines.
Then you could load then using TStringList, and write loops to apply names in "Assign country;Add country" statement.
DDD has too many idea to follow, Rest/Aggregate/EventSourcing/etc
I do learn good stuff in mORMot lib/framework, and I've make a little testcase like DDDPersistence, but far more things are disordered in my mind.
I don't know when to make a Service, or share whitch model between server and client and ....
I mean, mORMot is powerful but complicated, a clean uncoupled architecture example of DDD is appreciated.
And, when "stored AS_UNIQUE" was being commented, this update test would go through successfully.
Which "error"?
cmd.SelectOneByCaption() returns cqrsNotFound, not cqrsSuccess.
Perhaps after the Commit, the cmd instance is not re-usable for a select.
Please try to reassign a new cmd instance before SelectOneByCaption().
I've tried replace cmd with qry "Rest.Services.Resolve(iDomEntityQuery, qry)", qry.SelectOneByCaption() returns cqrsNotFound too.
Code here.
entity := TSomeEntity.Create;
Check(Rest.Services.Resolve(IDomEntityCommand, cmd));
Check(Rest.Services.Resolve(iDomEntityQuery, qry));
try
// check effect of update
for i := 1 to MAX do
begin
UInt32ToUtf8(i, iText);
iText := PreFix + iText;
Check(cqrsSuccess = qry.SelectOneByCaption(iText)); // error occurs here. qry.SelectOneByCaption() returns cqrsNotFound.
Check(1 = qry.GetCount); // error. result is 0
Check(cqrsSuccess = qry.Get(entity)); // error
Check(iText = entity.Caption); // error
end;
Hi ab.
Thanks for your last fix of TSQLRestBatch.Update overthere http://synopse.info/forum/viewtopic.php?id=3183
Now here is another conflict.
When I put a "stored AS_UNIQUE" at definition of TSQLRecord* 's property, Checking of update will fail.
Definition of record.
TSQLRecordSomeEntity = class(TSQLRecord)
protected
fCaption: RawUTF8;
published
property Caption: RawUTF8 read fCaption write fCaption [b]stored AS_UNIQUE[/b]; // changed here
end;
and test of update.
// test update
for i := 1 to MAX do
begin
UInt32ToUtf8(i, iText);
Check(cqrsSuccess = cmd.SelectOneByCaption(iText));
Check(1 = cmd.GetCount);
Check(cqrsSuccess = cmd.Get(entity));
Check(iText = entity.Caption);
iText := PreFix + iText;
entity.Caption := iText;
Check(cqrsSuccess = cmd.Update(entity));
end;
Check(cqrsSuccess = cmd.Commit);
// check effect of update
for i := 1 to MAX do
begin
UInt32ToUtf8(i, iText);
iText := PreFix + iText;
Check(cqrsSuccess = cmd.SelectOneByCaption(iText)); // error occurs here
Check(1 = cmd.GetCount);
Check(cqrsSuccess = cmd.Get(entity));
Check(iText = entity.Caption);
end;
I've inspect result of cmd.GetAll() after update using a DynArray, values retrived is equal to iText "PreFix = iText". But cmd.SelectOneByCaption(iText) fails.
Is there any thing I'v doing wrong?
Here the full/Single unit file. for your convenience.
unit DDDPersistenceMain;
interface
uses
Classes, SysUtils,
SynCommons, mORMot, mORMotDDD,
SynTests;
type
TSomeEntity = class(TSynPersistent)
protected
fCaption: RawUTF8;
published
property Caption: RawUTF8 read fCaption write fCaption;
end;
TSomeEntityObjArray = array of TSomeEntity;
TSQLRecordSomeEntity = class(TSQLRecord)
protected
fCaption: RawUTF8;
published
property Caption: RawUTF8 read fCaption write fCaption stored AS_UNIQUE;
end;
IDomEntityQuery = interface(ICQRSService)
['{74EA5045-2062-47D0-AE0F-E9163BBC731B}']
function SelectOneByCaption(const aCaption: RawUTF8): TCQRSResult;
function SelectAllByCaption(const aCaption: RawUTF8): TCQRSResult;
function SelectAll: TCQRSResult;
function Get(out aAggregate: TSomeEntity): TCQRSResult;
function GetAll(out aAggretates: TSomeEntityObjArray): TCQRSResult;
function GetNext(out aAggregate: TSomeEntity): TCQRSResult;
function GetCount: Integer;
end;
IDomEntityCommand = interface(IDomEntityQuery)
['{FEC02E2A-A76F-4CDD-B378-E4E1EA6043F9}']
function Add(const aAggregate: TSomeEntity): TCQRSResult;
function Update(const aUpdatedAggregate: TSomeEntity): TCQRSResult;
function Delete: TCQRSResult;
function DeleteAll: TCQRSResult;
function Commit: TCQRSResult;
function Rollback: TCQRSResult;
end;
TInfraRepoEntity = class(TDDDRepositoryRestCommand, IDomEntityCommand, IDomEntityQuery)
public
function SelectOneByCaption(const aCaption: RawUTF8): TCQRSResult;
function SelectAllByCaption(const aCaption: RawUTF8): TCQRSResult;
function SelectAll: TCQRSResult;
function Get(out aAggregate: TSomeEntity): TCQRSResult;
function GetAll(out aAggregates: TSomeEntityObjArray): TCQRSResult;
function GetNext(out aAggregate: TSomeEntity): TCQRSResult;
function Add(const aAggregate: TSomeEntity): TCQRSResult;
function Update(const aUpdatedAggregate: TSomeEntity): TCQRSResult;
end;
TInfraRepoEntityFactory = class(TDDDRepositoryRestFactory)
public
constructor Create(aRest: TSQLRest; aOwner: TDDDRepositoryRestManager=nil); reintroduce;
class procedure RegressionTests(test: TSynTestCase);
end;
TTestRepoEntity = class(TSynTestCase)
published
procedure TestSelf;
end;
TTestSuit = class(TSynTests)
published
procedure TestAll;
end;
procedure RunTestProject;
implementation
procedure RunTestProject;
begin
with TTestSuit.Create() do
try
Run;
ReadLn;
finally
Free;
end;
end;
{ TInfraRepoEntity }
function TInfraRepoEntity.Add(const aAggregate: TSomeEntity): TCQRSResult;
begin
Result := ORMAdd(aAggregate);
end;
function TInfraRepoEntity.Get(out aAggregate: TSomeEntity): TCQRSResult;
begin
Result := ORMGetAggregate(aAggregate);
end;
function TInfraRepoEntity.GetAll(out aAggregates: TSomeEntityObjArray): TCQRSResult;
begin
Result := ORMGetAllAggregates(aAggregates);
end;
function TInfraRepoEntity.GetNext(out aAggregate: TSomeEntity): TCQRSResult;
begin
Result := ORMGetNextAggregate(aAggregate);
end;
function TInfraRepoEntity.SelectAll: TCQRSResult;
begin
Result := ORMSelectAll('', []);
end;
function TInfraRepoEntity.SelectAllByCaption(const aCaption: RawUTF8): TCQRSResult;
begin
Result := ORMSelectAll('Caption=?', [aCaption], (''=aCaption));
end;
function TInfraRepoEntity.SelectOneByCaption(
const aCaption: RawUTF8): TCQRSResult;
begin
Result := ORMSelectOne('Caption=?', [aCaption], (''=aCaption));
end;
function TInfraRepoEntity.Update(
const aUpdatedAggregate: TSomeEntity): TCQRSResult;
begin
Result := ORMUpdate(aUpdatedAggregate);
end;
{ TInfraRepoEntityFactory }
constructor TInfraRepoEntityFactory.Create(aRest: TSQLRest;
aOwner: TDDDRepositoryRestManager);
begin
inherited Create(IDomEntityCommand,TInfraRepoEntity,TSomeEntity,aRest,TSQLRecordSomeEntity,aOwner);
AddFilterOrValidate(['*'], TSynFilterTrim.Create);
AddFilterOrValidate(['Caption'],TSynValidateNonVoidText.Create);
end;
class procedure TInfraRepoEntityFactory.RegressionTests(test: TSynTestCase);
procedure TestOne(Rest: TSQLRest);
const
PreFix = 'Modified';
MAX = 1000;
var
cmd: IDomEntityCommand;
qry: IDomEntityQuery;
entity: TSomeEntity;
entitys: TSomeEntityObjArray;
i,entityCount: Integer;
iText: RawUTF8;
begin
with test do
begin
entity := TSomeEntity.Create;
Check(Rest.Services.Resolve(IDomEntityCommand, cmd));
try
// test Add
for i := 1 to MAX do
begin
UInt32ToUtf8(i,iText);
entity.Caption := ' ' + iText;
Check(cqrsSuccess = cmd.Add(entity));
end;
Check(cqrsSuccess = cmd.Commit);
// test select
for i := 1 to MAX do
begin
UInt32ToUtf8(i, iText);
// testing SelectAllByCaption
Check(cqrsSuccess = cmd.SelectAllByCaption(iText));
Check(1 = cmd.GetCount);
Check(cqrsSuccess = cmd.GetNext(entity));
Check(iText = entity.Caption);
// testing SelectOneByCaption
Check(cqrsSuccess = cmd.SelectOneByCaption(iText));
Check(1 = cmd.GetCount);
Check(cqrsSuccess = cmd.Get(entity));
Check(iText = entity.Caption);
end;
// test update
for i := 1 to MAX do
begin
UInt32ToUtf8(i, iText);
Check(cqrsSuccess = cmd.SelectOneByCaption(iText));
Check(1 = cmd.GetCount);
Check(cqrsSuccess = cmd.Get(entity));
Check(iText = entity.Caption);
iText := PreFix + iText;
entity.Caption := iText;
Check(cqrsSuccess = cmd.Update(entity));
end;
Check(cqrsSuccess = cmd.Commit);
// check effect of update
for i := 1 to MAX do
begin
UInt32ToUtf8(i, iText);
iText := PreFix + iText;
Check(cqrsSuccess = cmd.SelectOneByCaption(iText)); // error occurs here
Check(1 = cmd.GetCount);
Check(cqrsSuccess = cmd.Get(entity));
Check(iText = entity.Caption);
end;
// test delete
Check(cqrsSuccess = cmd.SelectAll);
Check(cqrsSuccess = cmd.DeleteAll);
Check(cqrsSuccess = cmd.Commit);
Check(cqrsSuccess = cmd.SelectAll);
Check(0 = cmd.GetCount);
finally
entity.Free;
end;
end;
end;
var
RestServer: TSQLRestServerFullMemory;
RestClient: TSQLRestClientURI;
begin
RestServer := TSQLRestServerFullMemory.CreateWithOwnModel([TSQLRecordSomeEntity]);
try // first try directly on server side
RestServer.ServiceContainer.InjectResolver([TInfraRepoEntityFactory.Create(RestServer)],true);
TestOne(RestServer); // sub function will ensure that all I*Command are released
finally
RestServer.Free;
end;
RestServer := TSQLRestServerFullMemory.CreateWithOwnModel([TSQLRecordSomeEntity]);
try // then try from a client-server process
RestServer.ServiceContainer.InjectResolver([TInfraRepoEntityFactory.Create(RestServer)],true);
RestServer.ServiceDefine(TInfraRepoEntity,[IDomEntityCommand,IDomEntityQuery],sicClientDriven);
test.Check(RestServer.ExportServer);
RestClient := TSQLRestClientURIDll.Create(TSQLModel.Create(RestServer.Model),@URIRequest);
try
RestClient.Model.Owner := RestClient;
RestClient.ServiceDefine([IDomEntityCommand],sicClientDriven);
TestOne(RestServer);
RestServer.DropDatabase;
USEFASTMM4ALLOC := true; // for slightly faster process
TestOne(RestClient);
finally
RestClient.Free;
end;
finally
RestServer.Free;
end;
end;
{ TTestRepoEntity }
procedure TTestRepoEntity.TestSelf;
begin
TInfraRepoEntityFactory.RegressionTests(Self);
end;
{ TTestSuit }
procedure TTestSuit.TestAll;
begin
AddCase([TTestRepoEntity]);
end;
initialization
TJSONSerializer.RegisterObjArrayForJSON([
TypeInfo(TSomeEntityObjArray), TSomeEntity]);
TInterfaceFactory.RegisterInterfaces([
TypeInfo(IDomEntityQuery), TypeInfo(IDomEntityCommand)]);
end.
Is there any chance we can fix this issue?
Bookmark disappeared in current download of document (pdf version), which is present when it is in 2015.09.14 .
Is this a feature or a bug?