mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-04-05 22:57:49 +02:00
417 lines
11 KiB
ObjectPascal
417 lines
11 KiB
ObjectPascal
{
|
|
This file is part of the Free Pascal run time library.
|
|
Copyright (c) 2019 by Michael Van Canneyt, member of the
|
|
Free Pascal development team
|
|
|
|
Simple SQLDBRESTBridge JSON dataset component and connection.
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
**********************************************************************}
|
|
unit sqldbrestdataset;
|
|
|
|
{$mode objfpc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, JS, web, db, JSONDataset, restconnection;
|
|
|
|
Type
|
|
|
|
{ TSQLDBRestConnection }
|
|
|
|
TSQLDBRestConnection = Class(TRestConnection)
|
|
private
|
|
FDataProperty: String;
|
|
FmetaDataProperty: String;
|
|
FMetaDataResourceName: String;
|
|
FonGetResources: TNotifyEvent;
|
|
FPassword: String;
|
|
FResourceList: TStrings;
|
|
FUserName: String;
|
|
procedure DoResources(Sender: TObject);
|
|
function DoStoreDataProp: Boolean;
|
|
function DoStoreMetadata: Boolean;
|
|
function DoStoreMetadataProp: Boolean;
|
|
Protected
|
|
Procedure SetupRequest(aXHR : TJSXMLHttpRequest); override;
|
|
Function GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String; override;
|
|
Function GetReadBaseURL(aRequest: TDataRequest): String; Override;
|
|
Public
|
|
Constructor create(aOwner : TComponent); override;
|
|
Destructor Destroy; override;
|
|
Procedure GetResources(OnResult : TNotifyEvent = Nil);
|
|
Property ResourceList : TStrings Read FResourceList;
|
|
Published
|
|
Property OnGetResources : TNotifyEvent Read FonGetResources Write FOnGetResources;
|
|
Property metaDataProperty : String read FmetaDataProperty Write FmetaDataProperty Stored DoStoreMetadataProp;
|
|
Property DataProperty : String read FDataProperty Write FDataProperty Stored DoStoreDataProp;
|
|
Property MetaDataResourceName : String Read FMetaDataResourceName Write FMetaDataResourceName Stored DoStoreMetadata;
|
|
Property UserName : String Read FUserName Write FUserName;
|
|
Property Password : String Read FPassword Write FPassword;
|
|
end;
|
|
|
|
{ TSQLDBRestDataset }
|
|
|
|
TSQLDBRestDataset = Class(TJSONDataset)
|
|
private
|
|
FConnection: TSQLDBRestConnection;
|
|
FResourceName: String;
|
|
procedure SetConnection(AValue: TSQLDBRestConnection);
|
|
procedure SetResourceName(AValue: String);
|
|
Protected
|
|
function DataPacketReceived(ARequest: TDataRequest): Boolean; override;
|
|
function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer;virtual;
|
|
function StringToFieldType(S: String): TFieldType; virtual;
|
|
Function DoGetDataProxy: TDataProxy; override;
|
|
Procedure MetaDataToFieldDefs; override;
|
|
Public
|
|
Property Connection: TSQLDBRestConnection Read FConnection Write SetConnection;
|
|
Property ResourceName : String Read FResourceName Write SetResourceName;
|
|
end;
|
|
|
|
implementation
|
|
|
|
Type
|
|
|
|
{ TServiceRequest }
|
|
|
|
TServiceRequest = Class(TObject)
|
|
Private
|
|
FOnMyDone,
|
|
FOnDone : TNotifyEvent;
|
|
FXHR: TJSXMLHttpRequest;
|
|
function GetResult: String;
|
|
function GetResultJSON: TJSObject;
|
|
function GetStatusCode: Integer;
|
|
function onLoad(Event{%H-}: TEventListenerEvent): boolean;
|
|
Public
|
|
Constructor Create(Const aMethod,aURL,aUserName,aPassword : String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
|
|
Procedure Execute;
|
|
Property RequestResult : String read GetResult;
|
|
Property ResultJSON : TJSObject read GetResultJSON;
|
|
Property OnDone : TNotifyEvent Read FOnDone;
|
|
Property StatusCode : Integer Read GetStatusCode;
|
|
end;
|
|
|
|
{ TServiceRequest }
|
|
|
|
constructor TServiceRequest.Create(const aMethod,aURL, aUserName, aPassword: String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
|
|
begin
|
|
FOnMyDone:=aOnDone1;
|
|
FOnDone:=aOnDone2;
|
|
FXHR:=TJSXMLHttpRequest.New;
|
|
FXHR.AddEventListener('load',@onLoad);
|
|
FXHR.open(aMethod,aURL,true);
|
|
(* else
|
|
begin
|
|
// FXHR.withCredentials := true;
|
|
FXHR.open(aMethod,aURL,true,aUserName,aPassword);
|
|
end;*)
|
|
FXHR.setRequestHeader('Content-Type', 'application/json');
|
|
FXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(aUserName+':'+aPassword));
|
|
end;
|
|
|
|
procedure TServiceRequest.Execute;
|
|
begin
|
|
FXHR.send;
|
|
end;
|
|
|
|
function TServiceRequest.GetResult: String;
|
|
begin
|
|
Result:=FXHR.responseText;
|
|
end;
|
|
|
|
function TServiceRequest.GetResultJSON: TJSObject;
|
|
begin
|
|
if SameText(FXHR.getResponseHeader('Content-Type'),'application/json') then
|
|
Result:=TJSJSON.parseObject(GetResult)
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TServiceRequest.GetStatusCode: Integer;
|
|
begin
|
|
Result:=FXHR.Status;
|
|
end;
|
|
|
|
function TServiceRequest.onLoad(Event: TEventListenerEvent): boolean;
|
|
begin
|
|
if Assigned(FOnMyDone) then
|
|
FOnMyDone(Self);
|
|
end;
|
|
|
|
|
|
|
|
|
|
{ TSQLDBRestConnection }
|
|
|
|
function TSQLDBRestConnection.DoStoreMetadata: Boolean;
|
|
begin
|
|
Result:=(FMetadataResourceName<>'metadata');
|
|
end;
|
|
|
|
function TSQLDBRestConnection.DoStoreMetadataProp: Boolean;
|
|
begin
|
|
Result:=(FMetaDataProperty<>'metaData');
|
|
end;
|
|
|
|
procedure TSQLDBRestConnection.SetupRequest(aXHR: TJSXMLHttpRequest);
|
|
begin
|
|
inherited SetupRequest(aXHR);
|
|
aXHR.setRequestHeader('Content-Type', 'application/json');
|
|
aXHR.setRequestHeader('Accept', 'application/json');
|
|
if (UserName<>'') then
|
|
aXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(UserName+':'+Password));
|
|
end;
|
|
|
|
function TSQLDBRestConnection.GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String;
|
|
begin
|
|
Result:=inherited GetUpdateBaseURL(aRequest);
|
|
Result:=IncludeTrailingPathDelimiter(Result)+TSQLDBRestDataset(aRequest.Dataset).ResourceName;
|
|
end;
|
|
|
|
function TSQLDBRestConnection.GetReadBaseURL(aRequest: TDataRequest): String;
|
|
begin
|
|
Result:=inherited GetReadBaseURL(aRequest);
|
|
Result:=IncludeTrailingPathDelimiter(Result)+TSQLDBRestDataset(aRequest.Dataset).ResourceName;
|
|
end;
|
|
|
|
procedure TSQLDBRestConnection.DoResources(Sender: TObject);
|
|
|
|
Var
|
|
R : TServiceRequest absolute Sender;
|
|
J,Res : TJSObject;
|
|
A : TJSArray;
|
|
i : Integer;
|
|
|
|
begin
|
|
FResourceList.Clear;
|
|
if (R.StatusCode=200) then
|
|
begin
|
|
J:=R.ResultJSON;
|
|
if J=Nil then
|
|
exit;
|
|
A:=TJSArray(J.Properties['data']);
|
|
For I:=0 to A.Length-1 do
|
|
begin
|
|
Res:=TJSObject(A[i]);
|
|
FResourceList.Add(String(Res.Properties['name']));
|
|
end;
|
|
end;
|
|
If Assigned(R.OnDone) then
|
|
R.OnDone(Self);
|
|
If Assigned(OnGetResources) then
|
|
OnGetResources(Self);
|
|
end;
|
|
|
|
function TSQLDBRestConnection.DoStoreDataProp: Boolean;
|
|
begin
|
|
Result:=(FDataProperty<>'data');
|
|
end;
|
|
|
|
constructor TSQLDBRestConnection.create(aOwner: TComponent);
|
|
begin
|
|
inherited create(aOwner);
|
|
FResourceList:=TStringList.Create;
|
|
FMetaDataResourceName:='metadata';
|
|
FmetaDataProperty:='metaData';
|
|
FDataProperty:='data';
|
|
TStringList(FResourceList).Sorted:=true;
|
|
end;
|
|
|
|
destructor TSQLDBRestConnection.Destroy;
|
|
begin
|
|
FreeAndNil(FResourceList);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSQLDBRestConnection.GetResources(OnResult: TNotifyEvent);
|
|
|
|
Var
|
|
aURL : String;
|
|
R : TServiceRequest;
|
|
|
|
begin
|
|
aURL:=IncludeTrailingPathDelimiter(BaseURL)+MetaDataResourceName+'?fmt=json';
|
|
R:=TServiceRequest.Create('GET',aURL,Self.UserName,Self.Password,@DoResources,OnResult);
|
|
R.Execute;
|
|
end;
|
|
|
|
{ TSQLDBRestDataset }
|
|
|
|
procedure TSQLDBRestDataset.SetConnection(AValue: TSQLDBRestConnection);
|
|
begin
|
|
if FConnection=AValue then Exit;
|
|
if Assigned(FConnection) then
|
|
FConnection.RemoveFreeNotification(Self);
|
|
FConnection:=AValue;
|
|
if Assigned(FConnection) then
|
|
FConnection.FreeNotification(Self);
|
|
end;
|
|
|
|
procedure TSQLDBRestDataset.SetResourceName(AValue: String);
|
|
begin
|
|
if FResourceName=AValue then Exit;
|
|
CheckInactive;
|
|
FResourceName:=AValue;
|
|
end;
|
|
|
|
function TSQLDBRestDataset.DoGetDataProxy: TDataProxy;
|
|
begin
|
|
Result:=Connection.DataProxy;
|
|
end;
|
|
|
|
function TSQLDBRestDataset.StringToFieldType(S: String): TFieldType;
|
|
|
|
begin
|
|
if (s='int') then
|
|
Result:=ftInteger
|
|
else if (s='bigint') then
|
|
Result:=ftLargeInt
|
|
else if (s='float') then
|
|
Result:=ftFloat
|
|
else if (s='bool') then
|
|
Result:=ftBoolean
|
|
else if (s='date') then
|
|
Result:=ftDate
|
|
else if (s='datetime') then
|
|
Result:=ftDateTime
|
|
else if (s='time') then
|
|
Result:=ftTime
|
|
else if (s='blob') then
|
|
Result:=ftBlob
|
|
else if (s='string') then
|
|
Result:=ftString
|
|
else
|
|
if MapUnknownToStringType then
|
|
Result:=ftString
|
|
else
|
|
Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
|
|
end;
|
|
|
|
function TSQLDBRestDataset.GetStringFieldLength(F: TJSObject; AName: String;
|
|
AIndex: Integer): integer;
|
|
|
|
Var
|
|
I,L : Integer;
|
|
D : JSValue;
|
|
|
|
begin
|
|
Result:=0;
|
|
D:=F.Properties['maxLen'];
|
|
if Not jsIsNan(toNumber(D)) then
|
|
begin
|
|
Result:=Trunc(toNumber(D));
|
|
if (Result<=0) then
|
|
Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
|
|
end
|
|
else
|
|
begin
|
|
For I:=0 to Rows.Length-1 do
|
|
begin
|
|
D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
|
|
if isString(D) then
|
|
begin
|
|
l:=Length(String(D));
|
|
if L>Result then
|
|
Result:=L;
|
|
end;
|
|
end;
|
|
end;
|
|
if (Result=0) then
|
|
Result:=20;
|
|
end;
|
|
|
|
procedure TSQLDBRestDataset.MetaDataToFieldDefs;
|
|
Var
|
|
A : TJSArray;
|
|
F : TJSObject;
|
|
I,FS : Integer;
|
|
N: String;
|
|
ft: TFieldType;
|
|
D : JSValue;
|
|
|
|
begin
|
|
FieldDefs.Clear;
|
|
D:=Metadata.Properties['fields'];
|
|
if Not IsArray(D) then
|
|
Raise EJSONDataset.Create('Invalid metadata object');
|
|
A:=TJSArray(D);
|
|
For I:=0 to A.Length-1 do
|
|
begin
|
|
If Not isObject(A[i]) then
|
|
Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
|
|
F:=TJSObject(A[i]);
|
|
D:=F.Properties['name'];
|
|
If Not isString(D) then
|
|
Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
|
|
N:=String(D);
|
|
D:=F.Properties['type'];
|
|
If IsNull(D) or isUndefined(D) then
|
|
ft:=ftstring
|
|
else If Not isString(D) then
|
|
begin
|
|
Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
|
|
end
|
|
else
|
|
begin
|
|
ft:=StringToFieldType(String(D));
|
|
end;
|
|
if (ft=ftString) then
|
|
fs:=GetStringFieldLength(F,N,I)
|
|
else
|
|
fs:=0;
|
|
FieldDefs.Add(N,ft,fs);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBRestDataset.DataPacketReceived(ARequest: TDataRequest): Boolean;
|
|
|
|
Var
|
|
O : TJSObject;
|
|
A : TJSArray;
|
|
smetadata,sroot : String;
|
|
begin
|
|
Result:=False;
|
|
If isNull(aRequest.Data) then
|
|
exit;
|
|
If isString(aRequest.Data) then
|
|
O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
|
|
else if isObject(aRequest.Data) then
|
|
O:=TJSOBject(aRequest.Data)
|
|
else
|
|
DatabaseError('Cannot handle data packet');
|
|
sRoot:=Connection.DataProperty;
|
|
sMetaData:=Connection.metaDataProperty;
|
|
if (sroot='') then
|
|
sroot:='data';
|
|
if (smetadata='') then
|
|
smetadata:='metaData';
|
|
{ if (IDField='') then
|
|
idField:='id';}
|
|
if O.hasOwnProperty(sMetaData) and isObject(o[sMetaData]) then
|
|
begin
|
|
if not Active then // Load fields from metadata
|
|
metaData:=TJSObject(o[SMetaData]);
|
|
{ if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
|
|
IDField:=string(metaData['idField']);}
|
|
end;
|
|
if O.hasOwnProperty(sRoot) and isArray(o[sRoot]) then
|
|
begin
|
|
A:=TJSArray(o[sRoot]);
|
|
Result:=A.Length>0;
|
|
AddToRows(A);
|
|
end;
|
|
end;
|
|
|
|
|
|
end.
|
|
|