You are not logged in.
How to read from database feeding a filtering list without memory leakage?
I have a windows-service that reads data from a table into a TObjectList
which is customized to filter out some data i.e. duplicate records.
That's because I couldn't find out how to do that with SQL and mORMot.
I have created a solution that works but I think it might be a
smarter solution.
Anyone that can tell?
Here is my solution:
Alt 1.
Alt 1. Just need one list and the finally is simpler.
procedure TEvNServer.readPkgPrefix(out aPrefixList: TPrefixList);
var
SQL: RawUTF8;
ppf: TSQLPkgPrefix;
newPkgPrefix: TSQLPkgPrefix;
begin
SQL := 'usedBySync = ? ORDER BY DefaultPrefix desc ';
ppf := TSQLPkgPrefix.CreateAndFillPrepare(fDBServer, SQL, [1]);
aPrefixList := TPrefixList.create;
try
while ppf.FillOne do begin
newPkgPrefix := TSQLPkgPrefix.create; // Clone object before adding to list
newPkgPrefix.assign(ppf); //
if aPrefixList.add(newPkgPrefix) = -1 then // if object not accepted by the list,
newPkgPrefix.free; // it must be free:d, otherwise mem-leakage
end;
finally
ppf.free; // must be free:d otherwise mem-leakage
end;
end;
Alt 2.
Alt 2. Dont need to clone and add the assign method to TSQLPkgPrefix class.
procedure TEvNServer.readPkgPrefix(out aPrefixList: TPrefixList);
var
SQL: RawUTF8;
ppf: TSQLPkgPrefix;
tmpList: TObjectList<TSQLPkgPrefix>;
begin
SQL := 'usedBySync = ? ORDER BY DefaultPrefix desc ';
tmpList := fDBServer.RetrieveList<TSQLPkgPrefix>(SQL, [1], '');
aPrefixList := TPrefixList.create;
try
for ppf in tmpList do begin
if aPrefixList.add(ppf) = -1 then begin
ppf.free;
end;
end;
finally
if assigned(tmpList) then begin
tmpList.OwnsObjects := false;
freeAndNil(tmpList);
end;
end;
end;
Don't know where I should set these options/flags and what choices I have?
I skimmed through the Logviewers code but didn't found anything useful.
If I open the logfile with the log viewer, while the program is running and produces lines to the log file, and watching it over time the log go nuts!
It seems that the log viewer holds the logfile so that log rotation can not rename the file but still copies and compresses the filecontents into the rotate-list of files.
As it can't begin with an empty file it continues to run the rotate cycle for each line that adds to the log. This will cause huge cpu and memory load.
Is there a way to avoid this and still view the log? It's too easy to forget to close the viewer and unwatched it can cause a mess.
Otherwise I really like the viewer.
Thank you, that solved it!
I tried this:
var
body: variant;
i: integer;
files: array of RawUTF8;
atm: array of variant;
t: variant;
begin
setLength(files, 2);
files[0] := 'C:\temp\Faktura_se.pdf';
files[1] := 'C:\temp\din.pdf';
TDocVariant.new(body);
if high(files) > -1 then
begin
SetLength(atm, high(files) + 1);
for i := 0 to high(files) do
begin
TDocVariant.new(atm);
t.name := extractFileName(files[i]);
t.value := fileToBase64(files[i]);
atm[i] := t;
end;
body.FileAttachments := _arr([atm]);
end;
but that just set FileAttachments to '{"FileAttachments":[null]}'
Is there a simple correction of this code?
Found the reason... it was a setting called "Use a Proxy" that was on. I never ever used a proxy before... just don't have a clue on how it was set.
Well, that's it.
Thank's a lot.
Ok, but it won't help me as it only works when WIN10 is not detected.
HttpRequest gives error when proxy is set to auto which it is when WIN10 is detected.
What can make it do that? I suppose it normally works with win10?
And when I added "{$R Vista.res}" into the console project it stopped work.
When executing TWinHTTP.Post I get this error when run in a VCL app but if I run an excact copy of the code in a console-app it works.
I have debuged but can't find out what causes this.
Nice if someone have a clue to work on..
I have DELPHI-10.3 in this case and the latest version of WIN10.
I've debugged a bit deeper and found that in the initialization of connection properties there is a selection using Automatic proxy or no proxy. This is controled by OS-Version.
If i run the VCL-app I get major-version=10 and minor=3 but in the console app I get Major=6 and Minor=2 and in that case I get NO_PROXY but in the VCL-app I get Automatic_Proxy.
And if I changed the value from Auto to NO-proxy, using debugger, it works with the VCL-version.
I have no idea why it is like that and why it won't work with automatic_proxy.
I don't want to change anything in SynCrtSock so I still need some help.
Thank's a lot, I've just solved it this way:
TDocVariant.new(body);
body.SendUser := aFrom;
body.Message := aMessage;
body.Subject := aSubject;
body.ToRecipients := _Arr([stringReplace(aMailTo, ';', ',', [rfReplaceAll, rfIgnoreCase])]);
body.CcRecipients := _Arr([stringReplace(aCC, ';', ',', [rfReplaceAll, rfIgnoreCase])]);
if high(aAttachments) > -1 then
begin
for i := 0 to high(aAttachments) do
begin
TDocVariant.new(atm[i]);
atm[i].name := extractFileName(aAttachments[i]);
atm[i].value := fileToBase64(aAttachments[i]);
end;
body.FileAttachments := _Arr([atm]);
end;
messageID := TWinHTTP.Post(fOaSec.getMailURL, body, fOaSec.getMailHeader);
function FileToBase64(const aFileName: RawUTF8): RawUTF8;
var
f: TStream;
bytes: TBytes;
begin
result := '';
f := TFileStream.Create(afilename, fmOpenRead);
try
if f.Size > 0 then
begin
SetLength(bytes, f.Size);
f.Read(bytes[0], f.Size);
end;
result := binToBase64(@bytes[0], f.size); //String(sb);
finally
f.free;
end;
end;
Yes, it's about a http-request.
The service requires a body that contans mailFrom, mailto, message etc. and the attached files with name and contentents encoded in Base64 .
Al is wrapped in a json structure:
{
"SendUser": "senderName@somwhere.on.earth",
"Message": "Hello",
"Subject": "HelloSubject",
"ToRecipients": [
"receiver.theMartian@mons.olympus.on.mars"
],
"CcRecipients": [
"flash@titan.on.saturn"
],
"FileAttachments": [
{
"Name": "spacetraveling.pdf",
"Value": "N7I+GuZ4KclTXP7KXM3YRdh0BfKxnMpOv8s.... } // Filecontents base64-encoded
]
}
Part of the code that calls the sendmail service
var
messageID: sockString;
body: variant;
atm: variant;
begin
if getToken = '' then
result := -1
else begin
TDocVariant.new(body);
body.SendUser := aFrom;
body.Message := aMessage;
body.Subject := aSubject;
body.ToRecipients := _Arr([stringReplace(aMailTo,';',',')]);
body.CcRecipients := _Arr([stringReplace(aCC,';',',')]);
if high(aAttachments) > -1 then begin
TDocVariant.new(atm);
atm.name := aAttachments[0];
.
. read and add file contents here - all attachments should be put into an array and then added to the body.
messageID := TWinHTTP.Post(fOaSec.getMailURL,body,fOaSec.getMailHeader);
Ok, thank's for an answer but I has to send the mail through a service where everything is built around OAuth2. I have everything working and can send the mail but I need to know how I should do with the files that needs to be attached to the mail.
Indy will not help here.
In a new project I need to send mail using OAUTH2. The mail can contain attachments like pdf-files and they need to be Base64 encoded.
I feel lost in all alternatives so I need some help to move forward.
I use TWinHttp.post sending mail. It works good with simple mail.
Ok, I understand.
Well, good to know why it is like that, then it makes it more easy to accept.
In my case, for the moment at least, it won't be much of a problem as it is a fixed string that will not change often if ever so I only have to replace the '~':s with "%7E" manually.
Tries to use OAUTH2 in a project but a secret code contains '~'-characters and they are not replaced by the function.
While debugging I could se that this character belongs to the "unreserved characters" and will be left unchanged.
The OUTH2 service to get a token don't accept this character.
What can I do about it?
I no INDY has that function and that works but would like to make it without adding more libraries.
Ok, from the beginning I did just that but I did not want a complete flat table, I want to split them in One Customer table and one PersonContactable table.
In this simple example it isn't needeed but I want to know how to behave when I really need to split.
In one case I can think of is when you have several contacts for the customer. They are specified by what "role" that contact have. Different customers has different roles One may have only one another has 3 and others may have 6 etc. In that case it feels inconvenient to have a flat table for all this even if its possible.
One contact can possess several roles, the customer employs a new person to take over another contacts role etc.
Having problem finding out how I should handle INSERT of objects that contains linked classes.
ORMAdd does not seems to handle that.
I came up with an own solution but don't really like it.
The main TSQLRecord looks like this:
TSQLRecordCustomer = class(TSQLRecord)
protected
fContact: TSQLRecordPersonContactable; <<<--- The linked class
published
property Contact: TSQLRecordPersonContactable read fContact write fContact;
The repositorys add-function:
function TCustomerRepository.Add(const aAggregate: TCustomer): TCQRSResult;
var
rest: TSQLRest;
SQLpc: TSQLRecordPersonContactable;
pc: TPersonContactable;
SQLcus: TSQLRecordCustomer;
begin
result := cqrsInternalError;
rest := GetRest(self);
SQLpc := TSQLRecordPersonContactable.Create;
SQLcus := TSQLRecordCustomer.Create;
CustomerToRecord(aAggregate,SQLcus,SQLpc); <<<---- Copies all fields from TCustomer into SQLcus and SQLpc
SQLcus.Contact := TSQLRecordPersonContactable(rest.add(SQLpc, true));
rest.Add(SQLcus, true);
result := cqrsSuccess;
This works but I don't know how to handle different errors here and the "CustomerToRecord" function is heavy to type.
What I want is that this should be enough.
Result := ORMAdd(aAggregate);
But this leaves the "Contact"-field equal to zero.
I'm afraid that it isn't possible using ORM this way but hopes for a simpler and better solution than mine.
You are right, it works when running using external tables, at least against SQLite3.
Thank's for the help!
But isn't this a limit to this framework? I've thought that it should be transparent to any storage? Or am I wrong?
I run the regressiontest of TUser in the DDDExample but found that there was no test with the 'LIKE' operator.
The function "SelectByLastName" exists though and it takes an extra boolean parameter "StartsWith" that controls whether you want exact(false) or something that starts with..
Added some code that exercise that function.
test.Check(Rest.Services.Resolve(IDomUserCommand,cmd));
CQRS := cmd.SelectByLastName('Last1', false); // Exact - this one works
CQRS := cmd.SelectByLastName('Last', True); // Using "LIKE" and will not find anything.
Tried with debugging and finally entering: function TSQLRestStorageInMemory.GetJSONValues
where it checked the type of operator used.
Following cleaned code shows the execution flow..
function TSQLRestStorageInMemory.GetJSONValues(Stream: TStream;
case Stmt.Where[0].Operator of
opEqualTo:
opIsNull, opIsNotNull:
else begin
err: W.CancelAll;
result := 0;
exit;
When using the "LIKE" operator, the value of "Stmt.Where[0].Operator" is "opLike" and there's no entry for that and that causes this select to end with "W.CancelAll".
What's wrong here? Shouldn't it be an entry for "opLike".
I tested using the dddUserTypes unit with the classes TPersonFullName, TPerson and TPersonContactable.
Instead of Tuser I created the TCustomer class that inherits from TsynAutoCreateFields and contains a field of type TPersonContactable.
TCustomer = class(TSynAutoCreateFields)
private
fContact: TPersonContactable;
published
property Contact: TPersonContactable read fContact;
end;
Calling –
TDDDRepositoryRestFactory.ComputeSQLRecord([TPersonContactable, TCustomer]);
creates the following classes:
• TSQLRecordPerson
• TSQLRecordPersonContactable
• TSQLRecordCustomer
Calling - .CreateMissingTables creates the following tables:
• PersonContactable
• Customer
PersonContactable contains all the fields from TAddress, TPerson and TPersonFullName and it’s own.
Regression tests on TCustomer confirm access to all fields of these classes.
But testing using ORM doesn’t give access to the fields in the class TPersonFullName. There is no error or warnings when adding records But the fields Name_First, Name_Middle, and Name_Last will not be updated in the table “PersonContactable” but all other fields are updated.
I’ve checked all I can think of but can not make this work. Strange that ORM creates the fields from TPerson-TPersonFullName but does not allow access to the fields from TPersonFullName.
I wonder what I may be missing, I’m sure this should work automatically if I did everything wright.
Hope anyone could spread some light over this.
You are completely right, but it makes "currency" special any way as it works if you do: TPrice = double;
Thank's a lot!
In my little test project running some regression tests, I found that fields that have the type "currency" will not be updated on the database.
I thought it should work with "currency" but in my case apparently not. If I change type "TPrice" to be of type "double" it works.
Am I doing something wrong or should I avoid type "currency"?
---- A crippled piece of the code i use ----
type
TPrice = type currency;
TDiscount = type double;
TWeight = type double;
-------------------------------------------------
TSQLRecordProduct = class(TSQLRecord)
protected
fweight: double; // TWeight
fdiscount: currency; // TDiscount
fPrice: double;//currency; // TPrice
published
property weight: double read fweight write fweight;
property discount: double read fdiscount write fdiscount;
property price: double read fPrice write fPrice;
end;
--------------------------------------------------
TProduct = class(TSynAutoCreateFields)
private
fWeight: TWeight;
fPrice: TPrice;
fDiscount: TDiscount;
public
constructor create(const aName: TProductName; const aWeight: TWeight; const aPrice: TPrice;
const aDiscount: TDiscount); overload;
published
property weight: TWeight read fWeight write fWeight;
property price: TPrice read fPrice write fPrice;
property discount: TDiscount read fDiscount write fDiscount;
end;
TProductObjArray = array of TProduct;
------------- part of server code --------
RestServer := TSQLRestExternalDBCreate( TSQLModel.Create([TSQLRecordProduct]),
RestServer.ServiceContainer.InjectResolver([TProductRepoFactory.Create(RestServer)],true);
---------------------- Test code below --------------------
var p1: TProduct;
p1 := TProduct.Create;
p1.weight := 12.3;
p1.price := 249.99;
p1.discount := 25;
aCQRSRes := cmd.Add(p1);
Check(cqrsSuccess = aCQRSRes);
Check(cqrsSuccess = cmd.Commit);
p1.price is not persisted.
A few problems I've stumbled into:
1. When using "stored AS_UNIQUE" or a calculated property like "discountedprice" below, those properties is lost using ObjectToJSON.
Why? and what can I do about it?
2. Why do I get the new function: "function AS_UNIQUE" when I press (ctrl-shift C)?
Is it possible to avoid?
Below a sample code:
TProduct = class(TSynAutoCreateFields)
private
fName: TProductName;
fPrice: TPrice;
fDiscount: TDiscount;
function getDiscountedPrice: TPrice;
function AS_UNIQUE: Boolean; <<<<----- Will be automatically created on (ctrl-shift C)
public
constructor create(const aName: TProductName; const aPrice: TPrice;
const aDiscount: TDiscount); overload;
class procedure RegressionTests(test: TSynTestCase);
published
property productName: TProductName index 80 read fName stored AS_UNIQUE;
property price: TPrice read fPrice write fPrice;
property discount: TDiscount read fDiscount write fDiscount;
property discountedPrice: TPrice read getDiscountedPrice;
end;
============
procedure T
var b,c: TProduct;
jsonC, jsonB: RawUTF8;
begin
c := TProduct.Create('HardDrive 2TB',12.5,5.0);
with test do
try
jsonC := ObjectToJSON(c)+'*';
---->>>> jsonC will not contain productName nor discountedPrice.
We have a huge system containing several hundreds of tables and I tries to replace pieces of delphi-code by using mORMot and the DDD-concept.
When I do some testing I find that it won't work when the tables don't contain the ID-field. Which is true in most tables.
I can not access these tables without getting the "Invalid column name 'ID'" - error
In the implementation of the service I'm intend not to use ORM on these tables to avoid such problems. But that's not enough.
Even the call of "CreateMissingTables" returns this error. So I wonder if there is a solution for this or if I need to treat these tables in a total different way?
That was a good idea, that worked! Normally I like to use standard ORM-style instead of plain SQL but sometimes it seems to be necessary to breake that rule.
If someone have a better idea using ORM you're welcome, but otherwise I would say this is now solved.
Thank's
Yes I have read that and made all TDateTime fields of type TNullable. But it didn't help.
You see, I'm new to mORMot and not so familiar with variants. I have not so easy to understand how I should use it here.
I doubt that I can write the code like I did in my example above even though it feels natural for me as I'm more used to SQL.
I just test mORMot occasionally when I have time. I'm looking for a way to use it in a larger project and try to find as many pitfalls as possible before I proceeds with a larger project.
I've found a lot of them already and some of them are solved but this problem are still unsolved.
Using ORM I want to check if a field is null or not like this:
function TInfraRepoTicket.SelectUO: TCQRSResult;
begin
result := ORMSelectAll('DateOpened is ?',[null]);
end;
This doesn't seem to work as I never get a hit.
How can I solve this problem?
I have now installed a new version of fpc and lazarus following your instructions.
And now the testSQLite3 and my testapp works as it should.
Easy solved but hard to find
I got the same revision.. very strange when you can't reproduce it. Can it be something with Lazarus?
Free Pascal Compiler version 3.3.1-r41736 [2019/03/19] for x86_64
An immedite answer here, I'm just try to debug the "TestSQL3" app. It fails with access violation and it seems to be in the same code part as in my app.
What's common is that when a call result in a cqrsStatus <> cqrsSuccess and it tries to create the JSON formatted response it crasches.
It is very hard for me to understand what's really happends here and I've been trying to find out by debugging. So far I have not find it.
It should be simple though to reproduce it by running TestSQL3 with the same FPC-compiler 3.3.1 as I use.
The first test that fails is this:
Check(service.StartEmailValidation(template,'toto','toto@toto .com')=cqrsDDDValidationFailed);
This test is made to give an error status and that causes the crasch.
Is it enough to put the code into paste.bin, all code in one file or...?
Ok,
I'm using a Lazarus installation, installed by using fpcupDeluxe choosing the trunk version and got fpc-3.3.1 and Lazarus-2.1.0. I included mORMot in the installation.
I also installed ZEOS (7.2.4-stable build at 2018-03-25 11:08:27).
I also installed the latest WAMP-server to get MySQL together with a convenient environment.
To test that I could use MySQL and ZEOS in future programming I created a simple simple application to test all this.
First I had both server and client in the same application but later I splited it into one client and one server application but the result is the same.
It has no problem adding new records in the database table and it can also read them back without any problem. But,
if I want to read them all by using the function "ORMGetNextAggregate" I get all records delivered but at the end, when this function tries to read beyond the last record,
it gets the cqrsStatuscode=cqrsSuccessWithMoreData. So far so good but then it tries to create the JSON-message to be sent back to the client it goes wrong.
It looks like it doesn't know when to stop the process so it goes to far and get garbage data.
The "PPropInfo^PropType^.Kind the point outside it's buffer and returns a value to the TTextKind variable Kind that is outside its definition.
Maybe it can be in the function listed below:
function InternalClassProp(ClassType: TClass): PClassProp;
{$ifdef FPC}
begin
with GetFPCTypeData(pointer(ClassType.ClassInfo))^ do
result := AlignToPtr(@UnitName[ord(UnitName[0])+1]);
{$else}
{$ifdef PUREPASCAL}
var PTI: PTypeInfo;
begin // code is a bit abstract, but compiles very well
PTI := PPointer(PtrInt(ClassType)+vmtTypeInfo)^;
if PTI<>nil then // avoid GPF if no RTTI available for this class
with PTI^, PClassType(@Name[ord(Name[0])+1])^ do
result := PClassProp(@UnitName[ord(UnitName[0])+1]) else
result := nil;
{$else}
asm // this code is the fastest possible
mov eax, [eax + vmtTypeInfo]
test eax, eax
jz @z // avoid GPF if no RTTI available for this class
movzx edx, byte ptr[eax].TTypeInfo.Name
lea eax, [eax + edx].TTypeInfo.Name[1]
movzx edx, byte ptr[eax].TClassType.UnitName
lea eax, [eax + edx].TClassType.UnitName[1].TClassProp
@z:
{$endif PUREPASCAL}
{$endif FPC}
end;
The delphi version returns NIL when it don't find the classtype but the fpc version just returns the result from: AlignToPtr(@UnitName[ord(UnitName[0])+1]);
I failed to check what this function ( or isn't it a function?) returns.
I'm totally stuck here and if I don't solve this soon, there is nothing more than to leave this project I'm afraid.
I have struggled for days trying to solve this but now I understand I can't get any further without help.
Still not solved, I've no idea how to proceed.. Help needed.
Here it crashes: https://pastebin.com/0fxPPnEB
I found the missing part myself. I had to put all tables in the "VirtualTableExternalRegister" procedure.
I was fooled by the fact that you didn't need to put these tables (TSQLAuthUser, TSQLAuthGroup) into the model as do that automatically in the background, so I never thought about putting these tables into the VirtualTableExternalRegister procedure.
VirtualTableExternalRegister(Model, [TTicket, TSQLAuthUser, TSQLAuthGroup], Props);
As simple as this:
TDTOTicket = class(TSQLRecord)
protected
fticketNo: integer; // TTicketNo
fdescription: RawUTF8; // TDescription
fdateCreated: TDateTime; // TDateTime
fdateOpened: TDateTime; // TDateTime
fdateSolved: TDateTime; // TDateTime
published
/// maps TTicket.ticketNo (TTicketNo)
property ticketNo: integer read fticketNo write fticketNo;
/// maps TTicket.description (TDescription)
property description: RawUTF8 read fdescription write fdescription;
/// maps TTicket.dateCreated
property dateCreated: TDateTime read fdateCreated write fdateCreated;
/// maps TTicket.dateOpened
property dateOpened: TDateTime read fdateOpened write fdateOpened;
/// maps TTicket.dateSolved
property dateSolved: TDateTime read fdateSolved write fdateSolved;
end;
The server is running in the same program as the client, to simplify testing.
The server looks like this:
constructor TTicketServer.create(const aObserver: IErrorObserver);
begin
DBConnect(rseZeosMySQL, 'zdbc:mysql://localhost:3306', 'test_mormot', 'root', 'gT7Wqa96S');
Model := CreateTicketModel;
VirtualTableExternalRegister(Model, [TTicket], Props);
DBServer := TSQLRestServerDB.Create(Model, true); // true -> Authorization used
try
DBServer.CreateMissingTables;
DBServer.ServiceContainer.InjectResolver([TInfraRepoTicketFactory.create(DBServer)], false);
DBServer.ServiceDefine(TInfraRepoTicket, [IDomTicketCommand], sicClientDriven);
httpServer := TSQLHttpServer.Create(PORT_NAME, [DBServer], '+', HTTP_DEFAULT_MODE);
try
httpServer.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
CodeHalted := true;
while CodeHalted do begin
Application.ProcessMessages;
sleep(1000);
end;
finally
httpServer.Free;
end;
finally
DBServer.Free;
end;
end;
TTicket is defined as:
TTicket = class(TSQLRecord)
private
fTicketNo: TTicketNo;
fDescription : TDescription;
fDateCreated: TDateTime;
fDateModified: TDateTime;
fDateOpened: TDateTime;
fDateSolved: TDateTime;
fCategory: TTicketCategory;
function getTicketID: TTicketID;
published
property ticketNo: TTicketNo read fTicketNo write fTicketNo;
property description: TDescription read fDescription write fDescription;
property dateCreated: TDateTime read fDateCreated write fDateCreated;
property dateOpened: TDateTime read fDateOpened write fDateOpened;
property dateSolved: TDateTime read fDateSolved write fDateSolved;
end;
ianxevcd, I tried that before and I tried again - it didn't change anything. The parameter I used is, as what I understand, just a version number. Don't know where it is used but as my ticket table is created with or without this parameter, I can't see that it have something with db modification to do. But what do I know?
I will try to debug that function a bit more later on if no other solution comes up.
In unit mORMot:
procedure TCQRSService.InternalCqrsSetResult(Error: TCQRSResult; var Result: TCQRSResult);
procedure TJSONSerializer.WriteObject(Value: TObject; Options: TTextWriterWriteObjectOptions);
procedure WriteProp(P: PPropInfo);
Kind := P^.PropType^.Kind;
case Kind of <<<< Kind is 28 (TSQLRecordTableDeletedClass)
===== Raises exception External: SIGSEGV
tkInt64{$ifdef FPC}, tkQWord{$endif}: begin
This happends when I call the function ORMGetNextAggregate when there is no data left and when
it sets the cqrsStatus via InternalCqrsSetResult.
The code sequence in my testapp looks like:
ticketServer.DBServer.Services.Resolve(IDomTicketCommand,IDTC);
theTicket := TDTOTicket.Create;
theTicket.ticketNo := 199;
cqrsStatus := IDTC.Add(theTicket);
IDTC.Commit;
try
cqrsStatus := IDTC.SelectAll;
if cqrsStatus in [cqrsSuccess, cqrsSuccessWithMoreData] then begin
while cqrsStatus in [cqrsSuccessWithMoreData, cqrsSuccess] do
begin
cqrsStatus := IDTC.getNext(theTicket);
What can I do here? Is it a system bug or am I the bug?
I have already tested that and I did it again but only the ticket.table is created.
I'm using fpc, zeos and lazarus and I have had a lot of problems making it work can it be something with this configuration? I've tested with delphi in another project and that was easier but then I also used MSSQL.
Setting up the server with authorization doesn't work as expected.
I expected the two tables would be created automatically when I set the parameter of "TSQLRestServerDB.Create" to true.
The table "Ticket" is created
constructor TTicketServer.create;
begin
TDDDRepositoryRestFactory.ComputeSQLRecord([TTicket]);
DBConnect(rseZeosMySQL, 'zdbc:mysql://localhost:3306', 'test_mormot', 'root', '');
Model := CreateTicketModel;
VirtualTableExternalRegister(Model, [TTicket], Props);
DBServer := TSQLRestServerDB.Create(Model, true); // Require authorization
try
DBServer.CreateMissingTables(0);
DBServer.ServiceContainer.InjectResolver([TInfraRepoTicketFactory.create(DBServer)], true);
DBServer.ServiceDefine(TInfraRepoTicket, [IDomTicketCommand], sicClientDriven);
httpServer := TSQLHttpServer.Create(PORT_NAME, [DBServer], '+', HTTP_DEFAULT_MODE);
function createTicketModel: TSQLModel;
begin
result := TSQLModel.Create([TTicket],'root');
end;
procedure TTicketServer.DBConnect(aEngine: TRemoteSQLEngine; const aServerName,
aDatabaseName, aUserID, aPassWord: RawUTF8);
const
TYPES: array[TRemoteSQLEngine] of TSQLDBConnectionPropertiesClass = (TSQLDBZEOSConnectionProperties,TOleDBConnectionProperties, TODBCConnectionProperties, TSQLDBOracleConnectionProperties, TSQLDBSQLite3ConnectionProperties, nil, TOleDBMSSQL2008ConnectionProperties);
begin
if Props <> nil then
raise Exception.Create('Connect called more than once');
if TYPES[aEngine] = nil then
raise Exception.CreateFmt('aEngine=%s is not supported', [GetEnumName(TypeInfo(TRemoteSQLEngine), ord(aEngine))^]);
Props := TYPES[aEngine].Create(aServerName, aDatabaseName, aUserID, aPassWord);
end;
What am I missing?
Ok, I understand. I'll skip that part of the sample to get forward.
I'm new to mORMot and it's not an easy beast to tame so I feel that I'm not ready yet to come with any proposal here. Maybe in the future
Trying to make sample 16 to work with Lazarus and are stopped out by synTaskDialog which are used by mORMotUILogin.
I found that in mORMotUILogin there is a compiler switch that refer to:
{$ifdef FPC}
SynTaskDialog in '.\Samples\ThirdPartyDemos\Ondrej\SynTaskDialog4Lazarus\SynTaskDialog.pas',
{$else}
SynTaskDialog,
But I can see that it is the version of synTaskDialog from the ordinary mORMot library that is used and that one won't compile with FPC.
I find it very odd to have a standard library unit that refer to a sample third party unit. Is that good programming?
Ok, that's that.. but isn't it possible to have FPC work with this fundamental unit?
I thought I made it work but it was only an coincidence:
Baby := TSQLBaby.Create;
if aClient.Retrieve('', Baby, 'min(BirthDate)') then
Memo1.lines.Add(Format('zzzz %s - %s ',[Baby.Name, Baby.Address])+' // '+FormatDateTime('yyyy-mm-dd', Baby.BirthDate)); <<-- Gives an empty Baby
if aClient.Retrieve('', Baby, '') then
Memo1.lines.Add(Format('yyyyy %s - %s ',[Baby.Name, Baby.Address])+' // '+FormatDateTime('yyyy-mm-dd', Baby.BirthDate)); <<-- Gives the first record created (it happened to be the one I searched for)
I'm sure I do something wrong but after I have tested so many different things... it looks impossible to make it work.
Well I did got a hit but the Baby object was empty in return so all strings was empty and the date was '1899-12-30'.
I did like this:
Baby := TSQLBaby.Create;
if aClient.Retrieve('', Baby, 'min(Birthdate)') then
I feel that I'm missing something but what?
Finally I found why I got the Access violation. That's because I didn't created the Baby object first.
I needed to do like this:
if aClient.Retrieve(ID,Baby) then
Memo1.lines.Add(Format('WWWW %s - %s ',[Baby.Name, Baby.Address])+' // '+FormatDateTime('yyyy-mm-dd', Baby.BirthDate));
But I still don't know how I should do to use "Retrieve" to get the post with oldest date.
Of course I can do like this:
if aClient.Retrieve('BirthDate = (SELECT MIN(BirthDate) FROM Baby)',Baby) then
Memo1.lines.Add(Format('WWWW %s - %s ',[Baby.Name, Baby.Address])+' // '+FormatDateTime('yyyy-mm-dd', Baby.BirthDate));
But as I understod from ab, this is not the best way to do it... am I wright/wrong?
Ok, that solved some mystery about this. I have declared BirthDate as TDate and that result as FLOAT in SQLite3. When I changed to TDateTime I got TEXT in SQLite3.
Strange that TDate and TDateTime is not treated the same here.
That solved everything about this and if I had choosen TDateTime from the begining, this topic would not been created
Finally it worked
I could also reduce it to:
Baby := TSQLBaby.CreateAndFillPrepare( aClient, 'BirthDate=?',[double(dt)]);
So you may ask... Why all this about using DateToSQL and DateTimeToSQL when you should cast it to a double?
It has to be some other situations when you need to use them but when?
Thank's !
PS
Is there any Convention how to mark a thread solved here ?
DS
Sorry, but that didn't work either.
Can there be any more solutions to test?
I use SQLIteStudio 3.2.1
which gives the following output:
ID Name Address BirthDate Sex
310 Fredrik Stockholm 43259.5122240741 1
311 Kristina Lund 43294.5122242593 0
312 Kurt Lindesberg 43103 1
The last line is the line I searched for which is 2018-01-03.
I just tested that, but, sorry ... no hit.
Date seems to be a problem in mORMot.
Maybe it could also depend on SQLite3? I never had any such problems in Delphi with firedac and MSSQL.
It doesn't matter if I change to DateTimeToSQL. Still no hit.
You only get hits when using :
Baby := TSQLBaby.CreateAndFillPrepare( aClient, 'BirthDate <> ?',[DateTimeToSQL(dt)]);
or whenever you use '<' or '>' but thats not what I want in this case. I want hits on a certain date.
Maybe this wont work with fpc and lazarus? But I thought it would.
I'm running on Windows10.
Still hope on some help or explanation
I have tried your suggestion but failed, I've probably not understand what you mean so I need more help.
Note that I use Lazarus-2.1 and fpc-3.3.1
I've tried hard to find out how I should use the retrieve-function but totaly failed.
if aClient.Retrieve('BirthDate = ?',[],['Select min(BirthDate)'],Baby,'') then
and even this will not work,
if aClient.Retrieve('BirthDate = ?',[],['2019-01-01'],Baby) then
I get access violation in mormot.pas at the last line in the dump below:
function TSQLModel.GetTableIndex(aTable: TSQLRecordClass): integer;
var i: PtrInt;
Props: TSQLRecordProperties;
c: PSQLRecordClass;
begin
if (self<>nil) and (aTable<>nil) then begin
Props := PPointer(PtrInt(aTable)+vmtAutoTable)^;
if (Props<>nil) and (Props.fModelMax<fTablesMax) then
The following does not work as I expected:
Baby := TSQLBaby.Create;
Baby.name := 'Kurt';
Baby.Sex := sMale;
dt := encodeDate(2018,01,03);
Baby.BirthDate := dt;
Baby.Address := 'Lindesberg';
aClient.Add(Baby, true);
Baby.Free;
Baby := TSQLBaby.CreateAndFillPrepare( aClient, 'BirthDate = ?',[DateToSQL(dt)]);
while Baby.FillOne do
begin
Memo1.lines.Add(Format('>>>>>>>>>> %s - %s ',[Baby.Name, Baby.Address])+' // '+FormatDateTime('yyyy-mm-dd', Baby.BirthDate));
end;
BirthDate and dt is clearly the same date so why didn't I get a hit from the query?