You are not logged in.
No problem, Ab. Thanks for the quick response.
We're only using it to get access to our config file at this stage, which we don't want to reload and re-parse for every instance of our Interface-based service. As we're not in control of when the implementation is constructed, we can't use constructor based injection to pass the instance of the config file. How are you handling this scenario?
Hi Ab,
Sad to say it doesn't work when calling registerGlobal. Some more digging and looking at Stefan Glienke's advice, we followed the code and looking BeforeDestruction we came to the following conclusion:
In RegisterGlobal the code calls IInterface(aImplementation)._AddRef, but tries to free the implementation in FinalizeGlobalInterfaceResolution. This causes BeforeDestruction to raise an invalid pointer operation because the RefCount is not zero. Changing FinalizeGlobalInterfaceResolution's call from ImplementationInstance.Free to IInterface(ImplementationInstance)._Release, causes the issue to go away and no memory is leaked.
Thanks for the quick reply Ab. I am still curious as to why Delphi is exhibiting this behaviour. It's quite scary that it never called the destructor at all.
Hi Ab,
We have a weird one. We're trying to use the DI feature where we inject an instance of a configuration reader to our service using ServiceContainer.RegisterGlobal and we ran across a very strange issue.
Sample code here (with all Synopse code removed to show a pure Delphi example):
https://stackoverflow.com/questions/457 … rom-memory
Anyone had to deal with something like this before?
Hi guys,
We're trying to stub an interface that contains a method that returns an interface. Mormot complains with:
blah : blah parameter should be declared as const
when we try to register the interface.
Looking at the code mormot.pas(54472) we get:
if Kind=mkFunction then
with Args[n] do begin
ParamName := @CONST_PSEUDO_RESULT_NAME;
ValueDirection := smdResult;
SetFromRTTI(PB);
end;
This effectively asks the method to verify the result type as if it were a parameter. But the verification requires an Interface type to be const.
Is this a bug or by design?
@hnb
TSQLRestHelper = class helper for TSQLRest
is not Delphi 6/7 safe/
Hi AB,
When we recently required a CSV parser, I remembered that mORMot offered such a feature. We plugged in the CSVToRawUTF8DynArray function, but immediately started receiving issues. Some debugging revealed that blank field were ignored and investigating the code we saw:
procedure CSVToRawUTF8DynArray(CSV: PUTF8Char; var Result: TRawUTF8DynArray; Sep: AnsiChar = ',');
var s: RawUTF8;
begin
while CSV<>nil do begin
s := GetNextItem(CSV,Sep);
if s<>'' then begin
SetLength(Result,length(Result)+1);
Result[high(Result)] := s;
end;
end;
end;
The line:
if s<>'' then begin
seems like a bug to us, but I'm pretty sure you had some use case where you needed to ignore blank fields. This is, however, not part of the CSV standard (as much as CSV is a standard). Is there an alternative function to call or can we submit a fix?
Dexter,
I've never tried using it like that. From our use cases it's always been RESTfull, so the method to call is encoded in the URI, while the paramters are either POSTed or encoded as GET parameters (.../calculator/add?n1=1&n2=2)
Hope this helps
Have a look at my article here:
https://tamingthemormot.wordpress.com/2 … word-hash/
Good work Ab!
Let me explain it like this:
type
IDescribable = interface
function getID : integer;
function getDescription : RawUTF8;
end;
TPerson = class(TSynPersistent, IDescribable)
private
fID : integer;
LastName: RawUTF8;
FirstName: RawUTF8;
protected
function getID : integer;
function getDescription : RawUTF8;
published
property ID : integer read fID write fID;
property FirstName : RawUTF8 read fFirstName write fFirstName;
property LastName : RawUTF8 read fLastName write fLastName;
end;
Then on my client side I can have a library function:
procedure populateList( aStrings : TStrings; aData : array of IDescribable )
var I : integer;
begin
for I := low( aData ) to high(aData) do begin
aStrings.AddObject( aData[i].getDescription, aData[i] );
end;
end;
and call it like this:
var
UserNames : TPersonDynArray;
begin
SomeServiceINterface.GetLookupValues( aSomeLookups );
populateList( cbUserDropDown.Items, aSomeLookups );
..
Please note - this is just from the top of my head to trying and explain what I'm trying to do.
Hi AB,
Sure - but we have a function to populate a drop down, for instance. It would be great to pass the array of DTO's directly to it and by using an interface it could extract the correct description to use.
Hi AB,
We're sitting with a situation where we'd like out DTO's to implement certain interfaces. I realize that we should not inherited from TSynPersistent, in these cases, but where do you suggest we inherit from then?
Hi guys,
Been looking at FPC / Lazarus lately, even if it's just server side for now. Lazarus is looking GOOD, and the new FPC features looks awesome. What a downer when I found this thread!
@Ab
1. Which of these changes have been implemented?
2. I notices the SAD(#25.4.4) encourages us to use FPC 3.1.1, but using the ComputeFPCInterfacesUnit function.
Are these still options or is 3.1.1 completely broken?
Hi Ab,
An example that illustrate the issue is here: https://goo.gl/YEGyj7
No. I'll try and put together a small test application to illustrate the behavior.
Ab,
I had some time to play with it now, and it seems that the wrappers are not generating for my DTOs. I can't use the DTO unit, as my DTO's descend from SynCommons' TSynPersistent, and make heavy use of UTF8Raw.
Do I need to register my DTOs somewhere?
Hi Ab,
We've gone live with our first Rest server and lo and behold the TI team bounced the database server without restarting the REST server. This caused some very strange connections issues as the connections previously instantiated returned errorsm, but new ones worked - it took us a while to figure out what happened!
Researching possible solutions I found http://synopse.info/fossil/info/f024266c0839
I understand the ConnectionTimeOutMinutes property, which is something we'll investigate.
I am wondering about the proposed usage of ExceptionIsAboutConnection, though. How do you for see it being used?
Kind regards,
Willo
Oh! I didn't realise that this applies to FMX clients. Thanks Ab!
I can't seem to find anything relating to Interface based services (specifically TSQLHttpClient or other class that implements ServiceDefine) in any of the cross-platform dedicated units. Is this a Work In Progress?
Brilliant! Thanks Ab!
Hi Ab,
Playing with Delphi 10 Seattle, I wanted to bring in mORMot, but quickly realised that there's a big issue. System.Contnrs is not supported in mobile projects! That means no TObjectList.
See mobile white paper:
"Use the generic container classes defined in the unit Generics.Collections. The old-style Contnrs unit is not available on the mobile platform, because it is based on a pointer list (the non-generic TList class) and won’t work properly with the ARC memory management model."
Does that mean no mORMot for mobile projects, or is there a clever work around?
Ab,
Is the FastCGI interface stable?
Hi Ab,
Yes, I am using that. The issue came in with session recovery. Currently mORMot only tries TSQLRestServerAuthenticationSSPI (if SSPIAUTH is defined ) or TSQLRestServerAuthenticationDefault on the client side. I added a new event OnClientSetUser, which would allow the Client Application to take control of how SetUser prioritizes authentication schemes. It also makes it easy to reliably add new schemes and one can do away with the initial [CustomAuthScheme].SetUser() call. There was also a small issue where TSQLRestServerAuthenticationHttpAbstract.ClientSessionSign that would keep on appending the Cookie: header.
The current hash I was referring to is the current legacy hash we need to integrate to. We're looking at upgrading that to an SHA based hashing system to allow us to take advantage of the other more secure and compatible authentication schemes.
For now we're stuck with basic HTTP Auth, sending a plain text password, so that we can calculate the database hash on the server.
My changes work now. How do you prefer I submit the changes for your approval and comment?
Hi Ab,
Digging a bit further, it looks like it has to do with my custom hash. On top of that I'm also relying on HTTP Basic Auth, as the current hash can not be coded in JS without considerable effort. I'm making some changes I'd like to recommend be added to the base mORMot libraries, but will submit a full diff / pull request once I've stabilized the extension.
In our quest for ever more robust software, I'm wondering how mORMot deals with a server restart. My tests didn't have a very elegant result. The only solution I found was to recreate the client connection, which is a problem when the connection reference has been passed down through several layers. To my mind it would be ideal if TSQLHttpClient, or some parent, could try and re-establish it's session on receiving a 403.
Hi Michal,
We're using ZEOSDBO-7.1.4-stable, is 7.2 stable enough to upgrade to?
When fiddling around with transaction handling in Firebird 1.5, using ZEOS, we ran in to an issue where the transaction was not committed and we had to force a commit.
Digging around I found:
fDbConnection.MainConnection.StartTransaction;
Which worked pretty well in the unit tests, but testing it in a service produced an exception:
TSQLDBConnection on xxx should be connected
Forcing the connection before calling StartTransaction resolves the issue, but I'm wondering why TSQLDBConnection behaves this way.
Google is great!
Found the MdDataPack that came with Mastering Delphi 7.
Hi guys,
It seems that all the tools are there, but has anyone tried doing something like this yet?
Thanks Ab!
*bump*
Would it help if I submit a pull request?
Hi Ab,
We have several methods that would like to know who the user was that performed the action. This would be the authenticated session user. What is the best way to get hold of the session user from inside a service implementation?
Yay! Found the problem!
The issue is in TSQLRestServerAuthenticationHttpBasic.RetrieveSession
Ab, can you please change line mORMot.pas[44879]:
with TSQLAuthUser.Create do
to:
with Ctxt.Server.SQLAuthUserClass.Create do
Thanks esmondb, but I'm starting to think that Ab was right and I should abandon the Auth stuff in mORMot. The issue seems to be that when CreateMissingTables is called, it creates the dummy tables, with no/basic/default information in.
This has the side effect that Group information will always be Admin (when forced to ID:1) and all the group information will always be default.
This doesn't play well with trying to force the Authentication credentials, as it keeps overriding group information from the in memory created group.
When I have more time to play, I'll see if it is possible to derive a new HTTP server that allows for a more flexible authentication/authorization model.
That was a good idea, but setting TSQLAuthGroup.SessionTimeout to 999999 has the same effect
Hi Ab,
Yes, that's what I do:
type
TDummySQLUser = class(TSQLAuthUser)
protected
procedure SetPasswordPlain(const Value: RawUTF8); override;
public
constructor Create( aUserName, aPassword : RawUTF8 ); reintroduce;
end;
TDummySQLGroup = class(TSQLAuthGroup)
public
constructor Create; reintroduce;
end;
{ TDummySQLUser }
procedure TDummySQLUser.SetPasswordPlain(const Value: RawUTF8);
begin
PasswordHashHexa := EncryptIBPass( Value );
end;
constructor TDummySQLUser.Create( aUserName, aPassword : RawUTF8 );
begin
inherited Create;
fID := 1;
LogonName := aUserName;
PasswordHashHexa := aPassword;
GroupRights := TDummySQLGroup.Create();
end;
{ TDummySQLGroup }
constructor TDummySQLGroup.Create;
begin
fID := 1;
Ident := 'User';
end;
and then in my server class:
function TAbstractServer.RetrieveRevelightUser(
Sender: TSQLRestServerAuthentication; Ctxt: TSQLRestServerURIContext;
aUserID: TID; const aUserName: RawUTF8): TSQLAuthUser;
var
Res : ISQLDBRows;
begin
Result := nil;
Res := fDbConnection.Execute(
'select USER_ID, USER_PASSWORD '+
'from USERS '+
'where ACTIVE_FLAG=? and '+
'USER_ID=UPPER(?)',
[
cACTIVEFLAG_ACTIVE,
aUserName
]);
if Res.Step then begin
Result := TDummySQLUser.Create(Res['USER_ID'], Res['USER_PASSWORD']);
end;
end;
Which works pretty well, as long as SetPasswordPlain is virtual.
I am experiencing some other issues too. From my test client it looks like my session is disappearing. From the logs I can see the Authentication attempt come through and succeed, but then the next call to [server]/[service]._contract_ I get a
{ "errorCode":403, "errorText":"Forbidden" }
response.
Hi Ab,
I need to authenticate against an existing user database which calculates a proprietary hash. The easiest way I found to do that was to declare my own TSQLAuthUser. If we make TSQLAuthUser.SetPasswordPlain virtual, I can override the hash calculation and everything works.
Will you apply this to the code base?
procedure SetPasswordPlain(const Value: RawUTF8); virtual;
I think this part of the library needs some work though, it's very tightly bound. Perhaps if one can abstract it out to use interfaces instead, that would make extending it really easy.
PS Article forthcoming.
Hi Ab,
Shouldn't the definition of TOnAuthenticationUserRetrieve be of object, like so?
TOnAuthenticationUserRetrieve = function(Sender: TSQLRestServerAuthentication;
Ctxt: TSQLRestServerURIContext; aUserID: TID; const aUserName: RawUTF8): TSQLAuthUser of object;
Thanks for your comments on my DI article, Ab. I fixed it up now.
Thanks Ab, I'll probably go with a Git Pull request.
Next article is trying to uncover the mysteries of DI in mORMot. I might need some assistance in this regard, but will shout when I give up
Ab,
I'm thinking I should take you up on your offer of publishing the sample projects in a dedicated folder under your "third party" source code tree. How would I go about doing that?
I'm still exploring, so I reserve the right to retract anything I've published! I'm sure it won't get to that, but I AM sure I will want to revisit some of my earlier examples later, as I gain experience, to make adjustments.
Thanks guys!
Hi guys,
I've decided to blog my experiences and bits of knowledge I pick up as 'n explore the mORMot landscape here https://tamingthemormot.wordpress.com/ . Hopefully it will help someone else too. Comments welcome!
Willo
Hi guys,
I've been wondering how to implement the multi-legged Accounting Transaction using the ORM as described by Fowler here: http://martinfowler.com/eaaDev/Accounti … ction.html
The ORM might not be the best tool to model this, but it would make for an interesting use case nonetheless.
What I have so far:
TFinancialEvent = class;
TTransaction = class( TSQLRecord )
private
fAmount: TCurrency;
fAccount: TLegacyAccount;
fTransactionDate: TDateTime;
fStockEvent: TStockEvent;
published
property FinancialEvent : TFinancialEvent read fFinancialEvent write fFinancialEvent;
property TransactionDate : TDateTime read fTransactionDate write fTransactionDate;
property Account : TLegacyAccount read fAccount write fAccount;
property Amount : TCurrency read fAmount write fAmount;
end;
TFinancialEvent = class( TSQLRecord )
private
fEventDate: TDateTime;
published
property EventDate : TDateTime read fEventDate;
end;
Now, a lot of guys would now derive a new class from TFinancialEvent to handle specific events. Let's look at a Stock Procurement event, for instance:
TStockProcurementEvent = class(TFinancialEvent)
public
constructor Create( aStockItem : TLegacyStockItem; aQuantity: Double; aAmount : TCurrency;
aSupplier : TLegacySupplier; aReceivingLocation : TLegacyAccount; aDate : TDateTime ); reintroduce;
end;
...
implementation
{ TStockProcurementEvent }
constructor TStockProcurementEvent.Create( aStockItem : TLegacyStockItem; aQuantity: Double; aAmount : TCurrency;
aSupplier : TLegacySupplier; aReceivingLocation : TLegacyAccount; aDate : TDateTime );
var
aTransaction : TTransaction;
begin
inherited Create;
fEventDate := aDate;
//First add the Supplier leg
aTransaction := Self.Add;
aTransaction.Account := aSupplier;
aTransaction.Amount := -aAmount;
aTransaction.TransactionDate := aDate;
//Then add the Receiver leg
aTransaction := Self.Add;
aTransaction.Account := aReceivingLocation;
aTransaction.Amount := Amount;
aTransaction.TransactionDate := aDate;
end;
There are some assumptions and the code is far from complete. The one solution I played with was to make TFinancialEvent a descendant from TCollection of TTransaction, which provides a clean separation from the Framework, but then there is no ORM involved.
So then I started playing with keeping a dynamic array of transactions in the Event object, but I haven't found an easy way to of adding or loading slave records using the ORM.
Any suggestions?
SOA using interface based services seem to be using TServiceMethodArgument.AddJSON specifically. Browsing through the code and these forums I noticed that it can encode Enums as String, if the soWriteHumanReadable option is set, but I can't seem to find a hook to customize the Options.
Is there an easy way to have mORMot encode Enums as String? I see it's part of the standard serializer, but I can't seem to find a place to customize the serializer's options.
Thanks Ab
Reducing and simplifying to:
function TSupplierQuery.GetSuppliers(const aCompanyID: RawUTF8; out Suppliers: TSupplierObjArray): TCQRSResult;
var
Res : ISQLDBRows;
Supplier : TSupplier;
begin
Result := cqrsNotFound;
Res := fDbConnection.Execute( 'select SUPPLIER_ID from SUPPLIERS where COMPANY_ID=?', [aCompanyID] );
while Res.Step do begin
Supplier := TSupplier.Create;
GetSupplier(Res['SUPPLIER_ID'], Supplier);
ObjArrayAdd( Suppliers, Supplier );
end;
end;
Still produces an Access Violation.
Changing mORMot.pas[48685]:
if IsObjArray then
ObjArrayClear(Value^) else
Wrapper.Clear;
to
if false then//IsObjArray then
ObjArrayClear(Value^) else
Wrapper.Clear;
Works perfectly, but I'm pretty sure that will produce a memory leak.