You are not logged in.
Pages: 1
In Ukraine, we now have to fight for our lives and the lives of our families. I ask everyone to bring the truth to the Russians. Because their lies have already crossed all boundaries.
Stop the war!
And yet I would rather use RecordReference.
In my opinion, this is a great solution - to store the table and ID in one field, which in fact has only one limitation - the number of tables. And it would be cool to be able to bypass this limitation. Of course, it would be even cooler to have its own function for forming RecordReference, perhaps some kind of virtual method of the model .. But this will require serious refactoring. Adding a constant or model property is much cheaper in terms of time.
Arnaud, this is very critical for me. I'm getting to the point where 64 tables are not enough.
I was thinking about the model. But there are functions that work without it. For example:
function RecordRef.ID: TID;
begin
result := Value shr 6; // 64=1 shl 6
end;
Maybe should modify them:
function RecordRef.ID(Model: TSQLModel): TID;
If you need another bit mask, you could encode your own value and use it in your code.
I didn't quite understand how it helps me, because I need all the power of the mORMot's TRecordReference and TRecordReferenceToBeDeleted
I suggest to add a constant than defines the RecordReference calculation rules (not specifying the number 64 in different parts of the code). And make it available for modification by the developer
const
REC_REF_MAX_TABLES: Word = 64;
function RecordReference(Model: TSQLModel; aTable: TSQLRecordClass; aID: TID): TRecordReference;
begin
if aID=0 then
result := 0 else begin
result := Model.GetTableIndexExisting(aTable);
if result>=REC_REF_MAX_TABLES then
result := 0 else
inc(result,aID * REC_REF_MAX_TABLES);
end;
end;
Is it possible or am I missing something?
Wonderful job! Congratulations. One small question, is there any hope for Delphi support for Android?
Thanks @ab, you are the best!
Three lines of code will make me the happiest person
@ab, please
TSQLModelRecordReferenceArray = array of TSQLModelRecordReference;
...
fRecordReferences: TSQLModelRecordReferenceArray;
public
property RecordReferences: TSQLModelRecordReferenceArray read fRecordReferences;
Thanks
I ran into a similar problem.
The code is definitely missing:
fEchoRemoteClient: = nil;
in procedure TSynLogFamily.EchoRemoteStop;
@ab could you fix the code?
I need to find all the references to the record, just like the TSQLRestServer.AfterDeleteForceCoherency method does. All the information I need is stored in fRecordReferences, but I can’t get to it.
/// this array contain all TRecordReference and TSQLRecord properties
// existing in the database model
// - used in TSQLRestServer.Delete() to enforce relational database coherency
// after deletion of a record: all other records pointing to it will be
// reset to 0 or deleted (if CascadeDelete is true) by
// TSQLRestServer.AfterDeleteForceCoherency
fRecordReferences: array of TSQLModelRecordReference;
Can I access this array?
Well I overridden InitializeFields and got extra fields in the table. Works great.
But what about compression? How to save these fields during compression? Maybe it makes sense to make HistoryAdd virtual in order to be able to add additional fields? Would that be enough?
Thanks, ab. It is ok now
I have compare old and new code and found out a key difference:
function PropIsIDTypeCastedField(Prop: PPropInfo; IsObj: TJSONObject;
Value: TObject): boolean;
begin // see [22ce911c715]
if (Value<>nil) and (Prop^.PropType^.ClassSQLFieldType=sftID) then // <-- deleted in new code
case IsObj of
oSQLMany:
if IdemPropName(Prop^.Name,'source') or IdemPropName(Prop^.Name,'dest') then
result := true else
result := not TSQLRecord(Value).fFill.JoinedFields;
oSQLRecord:
result := not TSQLRecord(Value).fFill.JoinedFields;
else result := false;
end else
result := false; // assume true instance by default // <-- deleted in new code
end;
ab, could you check my investigate?
https://drive.google.com/open?id=1Fe43Q … n-AMgHhte9
It is the simple project to reproduce the problem.
After upgrade to last NightlyBuild I get an error like "ITest.GetUser failed on aUser:TSQLMyUser [returned object]."
The error appears when I try to get an object that contains a field of type TSQLRecordMany.
Test Model:
TSQLMyUser = class;
TSQLMyRole = class(TSQLRecord)
end;
TSQLMyUserRole = class(TSQLRecordMany)
private
fSource: TSQLMyUser;
fDest: TSQLMyRole;
published
property Source: TSQLMyUser read fSource; // map Source column
property Dest: TSQLMyRole read fDest; // map Dest column
end;
TSQLMyUser = class(TSQLRecord)
private
FRoles: TSQLMyUserRole;
published
property Roles: TSQLMyUserRole read FRoles;
end;
And a simple "Interface based services":
ITest = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
function GetUser(out aUser: TSQLMyUser): boolean;
end;
Ok. Thanks.
I suspect it won't work.
I didn't notice any Inc(i) in the infinite loop:
i := 1;
repeat
L := Lens[i-1];
if L<>0 then begin
{$ifdef FPC}Move{$else}MoveFast{$endif}(T.fResults[i]^,P^,L);
inc(P,L);
end;
if i=T.fRowCount then
break;
{$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Separator)^,P^,SepLen);
inc(P,SepLen);
until false;
The problem in
function OneFieldValues(Table: TSQLRecordClass; const FieldName: RawUTF8;
const WhereClause: RawUTF8=''; const Separator: RawUTF8=','): RawUTF8; overload;
It returns '1'#0',3,4,5,8,1,14,15' instead of '1,3,4,5,8,1,14,15' if I request FieldName = 'ID' (2 symbol length).
for i := 0 to T.fRowCount-1 do begin // ignore fResults[0] i.e. field name
//Lens[i] := StrLen(T.fResults[i]); // not correct: fResults[0] is field name
Lens[i] := StrLen(T.fResults[i+1]); // <--- correct
inc(Len,Lens[i]+SepLen);
end;
Please fix.
Thanks, ab
I meant something like this:
TMyParams = class
private
FP1: Boolean;
published
property P1: Boolean read FP1 write FP1;
end;
TMyRec = class(TSQLRecord)
private
FTitle: string;
FParams: TMyParams;
public
destructor Destroy; override; // to destroy FParams
class function NewInstance: TObject {$IFDEF AUTOREFCOUNT} unsafe {$ENDIF}; override; // need override NewInstance to create FParams (constructor TObjects isn't virtual)
published
property Title: string read FTitle write FTitle;
property Params: TMyParams read FParams write FParams;
end;
const
cStoreClassName = True;
...
r := TMyRec.Create;
try
r.Title := 'Hello';
r.Params.P1 := True;
aJSON := ObjectToJSON(r, cStoreClassName);
finally
r.Free;
end;
r := TMyRec(JSONToNewObject(aJSON))
I made some tests.
1. The function ObjectToJSON have parameter StoreClassName: boolean=false. So we don't have ClassName to create nested classes in SynCrossPlatformREST by default. Is it possible to make it customizable?
2. The code works on Win32. On Android there is a problem with
function JSONToNewObject(const JSON: string): pointer;
should be
function JSONToNewObject(const JSON: string): TObject;
And a question. Is it possible to somehow teach the mORMotWrappers to generate nested classes? Or should I put them in the template? Or I should use nested records and arrays?
Are there any plans to add support for nested classes in the Cross-Platform?
OK. Thanks for reply.
I have an external DB and class
TSQLSKU = class(TSQLRecord)
private
FTitleLocal: RawUTF8;
published
property TitleLocal: RawUTF8 index 100 read FTitleLocal write FTitleLocal;
end;
Line
aSKU := TSQLSKU.CreateAndFillPrepare(Server, 'TitleLocal is null OR TitleLocal = ''''');
work correctly.
But line
aSKU := TSQLSKU.CreateAndFillPrepare(Server, '(TitleLocal is null) OR (TitleLocal = '''')');
does not work.
I think constructor TSynTableStatement.Create does not work properly.
Ok. If I use TWinHTTP and properly fill ExtendedOptions I get what I need. But I have to care about proxy.
Yes, I read documentation about import the current IE settings. But to do it I need to have admin rights and it does not work when IE "Automatically detect proxy settings" is checked.
So we have to write proxy by hand.
What do you say about this piece of code? WinHTTP AutoProxy Support: http://markmail.org/download.xqy?id=4lc … 4&number=1
May be we could forget about TWinINet if we implement it.
I see. Is it planned to do?
I wand to GET http://user:password@host:port/api/command by TWinINet.Get.
InternetConnectA has paramters lpszUsername and lpszPassword, but they always are nil
InternetConnectA(fSession, pointer(fServer), fPort, nil, nil, INTERNET_SERVICE_HTTP, 0, 0)
Is it possible to extract Username and Password from URI and use they in InternetConnect? Or I have to write my descendant?
Sometimes it is usefull to show a common edit dialog for any TSQLRecord.
TRecordEditForm allows to do it easily. But it doesn't work with TRecordReferenceToBeDeleted properrties. I propose to fix it.
procedure TRecordEditForm.SetRecord
....
if ((P.SQLFieldType in [ // must match "case SQLFieldType of" below
// -- sftTID
sftRecord, {sftTID, }sftBlob, sftBlobDynArray, sftBlobCustom, sftUTF8Custom,
//
....
// ++ sftTID
sftTID:
if aClient<>nil then begin
// ID field (TSQLRecord descendant) is handled by a TComboBox component
// with all possible values of the corresponding TSQLRecord descendant
IDClass := TSQLRecordClass((P as TSQLPropInfoRTTITID).RecordClass);
CB := TComboBox.Create(Scroll);
CB.Parent := Scroll; // need parent now for CB.Items access
CB.Style := csDropDownList;
aID := GetInt64(pointer(aValue));
with IDClass.RecordProps do
if MainField[true]>=0 then begin
aClient.OneFieldValues(IDClass,Fields.List[MainField[true]].Name,
'',CB.Items,@aID);
CB.ItemIndex := aID; // @aID now contains the found index of aID
end;
end;
//
procedure TRecordEditForm.BtnSaveClick(Sender: TObject);
....
if C.InheritsFrom(TComboBox) then begin
SetIndex := CB.ItemIndex;
case P.SQLFieldType of
sftEnumerate:
if SetIndex>=0 then begin
P.SetValue(Rec,pointer(Int32ToUTF8(SetIndex)),false);
Include(ModifiedFields,FieldIndex);
end;
// + sftTID
sftID, sftTID: begin
//
if SetIndex<0 then
aID := 0 else
aID := PtrInt(CB.Items.Objects[SetIndex]);
P.SetValue(Rec,pointer(Int64ToUTF8(aID)),false);
Include(ModifiedFields,FieldIndex);
end;
end;
end else
SynCommons.SameValue(386.0, 720, 2) = true
System.Math.SameValue(386.0, 720, 2) = false
I am a little bit confused. Is it OK?
Ok. It seems like I have to follow your proposition to use text definition.
Thanks a lot.
Thanks for answer, but I tried to dig by myself and found something interesting:
SynCommons (line:41326)
fElemType := PTypeInfo(aTypeInfo)^.elType;
I inspected PTypeInfo(aTypeInfo)^ , it has properties
elType = nil
and
elType2 = $62CA90
When I modified
fElemType := PTypeInfo(aTypeInfo)^.elType2;
I have got the result = [{"Lat":0,"Lon":0},{"Lat":1,"Lon":1},{"Lat":2,"Lon":2},{"Lat":3,"Lon":3},{"Lat":4,"Lon":4}]
SynCommons (line:18870)
// also unmanaged field
elType2: PTypeInfoStored;
May be should be used elType2 for unmanaged fields, or at least in the case when elType = nil. Will it be correct?
OK.
Good explanation, but why SaveJSON(a[0], TypeInfo(TLatLon)) returns {"Lat":0,"Lon":0} ?
And why does not custom serialization work?
I apologize for boring, but is it the final diagnosis? It is tempting to use the arrays of records without string fields.
Just curious.
There is a code:
type
TLatLon = packed record
Lat, Lon: Double;
end;
TLatLonArr = array of TLatLon;
TLatLon1 = packed record
Lat, Lon: Double;
s: RawUTF8;
end;
TLatLonArr1 = array of TLatLon1;
...
procedure TForm1.Button1Click(Sender: TObject);
var
a: TLatLonArr;
a1: TLatLonArr1;
i: Integer;
begin
SetLength(a, 5);
SetLength(a1, 5);
for i := 0 to 4 do
begin
a[i].Lat := i;
a[i].Lon := i;
a1[i].Lat := i;
a1[i].Lon := i;
end;
Memo1.Lines.Add(SaveJSON(a, TypeInfo(TLatLonArr)));
Memo1.Lines.Add(SaveJSON(a1, TypeInfo(TLatLonArr1)));
end;
Tell me why
SaveJSON(a, TypeInfo(TLatLonArr)) returns something like ["EAAFAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAPA/AAAAAAAAAEAAAAAAAAAAQAAAAAAAAAhAAAAAAAAACEAAAAAAAAAQQAAAAAAAABBA"]
But
SaveJSON(a1, TypeInfo(TLatLonArr1)) returns [{"Lat":0,"Lon":0,"s":""},{"Lat":1,"Lon":1,"s":""},{"Lat":2,"Lon":2,"s":""},{"Lat":3,"Lon":3,"s":""},{"Lat":4,"Lon":4,"s":""}]
The difference is
s: RawUTF8;
Tested in Delphi XE2
By the way custom serialization doesn't work too:
TTLatLonArrSerializer = class
class function ShortReader(P: PUTF8Char; var aValue; out aValid: Boolean): PUTF8Char;
class procedure ShortWriter(const aWriter: TTextWriter; const aValue);
end;
implementation
{ TTLatLonArrSerializer }
class function TTLatLonArrSerializer.ShortReader(P: PUTF8Char; var aValue; out aValid: Boolean): PUTF8Char;
var
V: TLatLon absolute aValue;
begin
aValid := false;
result := nil;
if (P=nil) or (P^<>'[') then
exit;
inc(P);
V.Lat := GetNextItemDouble(P);
V.Lon := GetNextItemDouble(P);
if P=nil then
exit;
aValid := true;
result := P;
end;
class procedure TTLatLonArrSerializer.ShortWriter(const aWriter: TTextWriter; const aValue);
var
V: TLatLon absolute aValue;
begin
aWriter.Add('["%","%"]', [V.Lat, V.Lon], twJSONEscape);
end;
initialization
TTextWriter.RegisterCustomJSONSerializer(TypeInfo(TLatLon),
TTLatLonArrSerializer.ShortReader, TTLatLonArrSerializer.ShortWriter);
end.
I have a problem. When I retrive big data from server I don't see any progress. Yes, I can handle OnIdle event but it is not enough!
So we have
function TWinHttpAPI.InternalRetrieveAnswer(var Header, Encoding, AcceptEncoding,
Data: SockString): integer;
var Bytes, ContentLength, Read: DWORD;
begin // HTTP_QUERY* and WINHTTP_QUERY* do match -> common to TWinINet + TWinHTTP
result := InternalGetInfo32(HTTP_QUERY_STATUS_CODE);
Header := InternalGetInfo(HTTP_QUERY_RAW_HEADERS_CRLF);
Encoding := InternalGetInfo(HTTP_QUERY_CONTENT_ENCODING);
AcceptEncoding := InternalGetInfo(HTTP_QUERY_ACCEPT_ENCODING);
// retrieve received content (if any)
Read := 0;
ContentLength := InternalGetInfo32(HTTP_QUERY_CONTENT_LENGTH);
if ContentLength<>0 then begin
SetLength(Data,ContentLength);
repeat
Bytes := InternalReadData(Data,Read);
// ////
// ---> we can notify
if Assigned(OnProgress) then
OnProgress(Read+Bytes, ContentLength);
// ////
if Bytes=0 then begin
SetLength(Data,Read); // truncated content
break;
end else
inc(Read,Bytes);
until Read=ContentLength;
end else begin
// Content-Length not set: read response in blocks of HTTP_RESP_BLOCK_SIZE
repeat
SetLength(Data,Read+HTTP_RESP_BLOCK_SIZE);
Bytes := InternalReadData(Data,Read);
if Bytes=0 then
break;
inc(Read,Bytes);
until false;
SetLength(Data,Read);
end;
end;
Next problem is InternalReadData
function TWinHTTP.InternalReadData(var Data: SockString; Read: integer): cardinal;
begin
if not WinHttpReadData(fRequest, @PByteArray(Data)[Read], length(Data)-Read, result) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
end;
No possibility to request a predetermined amount of data. OnProgress will fire only once.
I want
function TWinHTTP.InternalReadData(var Data: SockString; Read: integer; Count: Integer): cardinal;
begin
if not WinHttpReadData(fRequest, @PByteArray(Data)[Read], Min(length(Data)-Read, Count), result) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
end;
and our code
if ContentLength<>0 then begin
SetLength(Data,ContentLength);
repeat
// ////
// read chunk HTTP_RESP_BLOCK_SIZE
Bytes := InternalReadData(Data,Read,HTTP_RESP_BLOCK_SIZE);
if Assigned(OnProgress) then
OnProgress(Read+Bytes, ContentLength);
// ////
if Bytes=0 then begin
SetLength(Data,Read); // truncated content
break;
end else
inc(Read,Bytes);
until Read=ContentLength;
end else begin
And I am not sure about HTTP_RESP_BLOCK_SIZE...
What do you think? Of course we need access to OnProgress...
Is it implementable?
Is it possible to update mORMoti18n.pas to allow translating DisplayLabel?
if (LastPropName='Caption') or (LastPropName='EditLabel.Caption') or
(LastPropName='Hint') or (LastPropName='EditLabel.Hint') or
(LastPropName='Title') or (LastPropName='Items')
or (LastPropName='DisplayLabel') // <--- line to extract DisplayLabel
// standard properties
if (P^.Name='Caption') or (P^.Name='Hint') or
(P^.Name='Title')
or (P^.Name='DisplayLabel') // <--- line to translate DisplayLabel
then
CB_EnumDFMProc has the condition
if (LastPropName='Caption') or (LastPropName='EditLabel.Caption') or
(LastPropName='Hint') or (LastPropName='EditLabel.Hint') or
(LastPropName='Title') or (LastPropName='Items') then begin
Writeln(F^,PropName,'=_',Hash32(CB_EnumStrings[AddOnceDynArray(Value)]),
' ',Value); // add original caption for custom form translation
TField has property DisplayLabel. Can we include it in the condition?
Should I use ServiceContext.Request.SessionUser?
How can I check user rights inside a SOA method?
Is it possible?
Now everything is OK.
Thank you for the excellent work!
I have a class
TSQLRegisterCUTE = class(TSQLRecord)
private
FDoc: TRecordReferenceToBeDeleted;
published
property Doc: TRecordReferenceToBeDeleted read FDoc write FDoc;
end;
I try to set Doc:
aRec.Doc := aModel.RecordReference(TSQLDocPasterShiftReport, ID);
aClient.Add(aRec, True);
In my code aRec.Doc = 22738
But in Database Doc = -46.
This value I got from TPropInfo.GetOrdProp.
In this function
PropType^.Kind = tkInt64
But
TOrdType(PByte(AlignToPtr(@PropType^.Name[ord(PropType^.Name[0])+1]))^) = otSByte
so 22738 converts to SingleByte = -46
What is wrong?
It works now!
Thanks!
I am sorry to bother you again.
But how can i fix the problem?
No.
Target: Win32.
Yes, of course!
if FHTTPServer.HttpServer is THttpApiServer then
begin
if THttpApiServer(FHTTPServer.HttpServer).HasAPI2 then
THttpApiServer(FHTTPServer.HttpServer).SetTimeOutLimits(
2*SecsPerMin, 2*SecsPerMin, 2*SecsPerMin, 2*SecsPerMin, 2*SecsPerMin, 2*SecsPerMin);
end
else if FHTTPServer.HttpServer is THttpServer then
begin
THttpServer(FHTTPServer.HttpServer).ServerKeepAliveTimeOut := 20 * MSecsPerSec * SecsPerMin;
end;
Windows 8.1 / Windows 7
Code
THttpApiServer(FHTTPServer.HttpServer).SetTimeOutLimits(
2*SecsPerMin, 2*SecsPerMin, 2*SecsPerMin, 20*SecsPerMin, 2*SecsPerMin, 2*SecsPerMin);
raises exception
EHttpApiServer ("HttpSetUrlGroupProperty failed: The data area passed to a system call is too small (122)") at 0070299B stack trace API 005B3E10 005B3E38 00407E44 776FB5DF 776B0133 0070299B 0070532F 00807412 008078DE 00807B0B 00808022 004971E1 0049B5E8 004B7129 0049B738 0049B5E8 0054523E 0049AC3B 0045A92A 76C262FA 76C26D3A 76C2965E 76C296C5 73EC45A1 73EC4603 73EC448D 76C262FA 76C26D3A 76C30D27 76C30D4D
In i18nAddLanguageItems function LanguageAbrToIndex gets filename with extention (en.msg, ru.msg)
function LanguageAbrToIndex(const value: RawUTF8): TLanguages;
// LanguageAbrToIndex('GR')=1
begin
if length(value)=2 then
result := LanguageAbrToIndex(pointer(Value)) else
result := LANGUAGE_NONE;
end;
So we always get LANGUAGE_NONE
For example, I want to add into model
TBlogApplication
procedure MakeReportAndDownload
, which makes report.pdf or report.xls.
How can I return this file instead of the web page and user can download it?
Or I have to override
TSQLHttpServerWeb.Request(
?
Incredibly fast. Thanks.
I'm trying to get data from multiple tables RestModel.RetrieveDocVariantArray. The result of the query, some fields do not contain any of the values on which it would be possible to determine their type. So
ValueVarToVariant
raise ESQLTableException because
fFieldType[f].ContentType = sftUnknown
Is it right?
Can we fix it?
Thank you. it's enough
mORMot.pas
function SQLFromWhere(const Where: RawUTF8): RawUTF8;
Could you add a check for LEFT, RIGHT, INNER, OUTER JOIN?
And could you exclude spaces before this and other keywords.
For example, ' where 1 = 1' - doesn't work correctly.
Pages: 1