sqldbrestdataset.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by Michael Van Canneyt, member of the
  4. Free Pascal development team
  5. Simple SQLDBRESTBridge JSON dataset component and connection.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit sqldbrestdataset;
  13. {$mode objfpc}
  14. interface
  15. uses
  16. Classes, SysUtils, JS, web, db, JSONDataset, restconnection;
  17. Type
  18. { TSQLDBRestConnection }
  19. TSQLDBRestConnection = Class(TRestConnection)
  20. private
  21. FDataProperty: String;
  22. FmetaDataProperty: String;
  23. FMetaDataResourceName: String;
  24. FonGetResources: TNotifyEvent;
  25. FPassword: String;
  26. FResourceList: TStrings;
  27. FUserName: String;
  28. procedure DoResources(Sender: TObject);
  29. function DoStoreDataProp: Boolean;
  30. function DoStoreMetadata: Boolean;
  31. function DoStoreMetadataProp: Boolean;
  32. Protected
  33. Procedure SetupRequest(aXHR : TJSXMLHttpRequest); override;
  34. Function GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String; override;
  35. Function GetReadBaseURL(aRequest: TDataRequest): String; Override;
  36. Public
  37. Constructor create(aOwner : TComponent); override;
  38. Destructor Destroy; override;
  39. Procedure GetResources(OnResult : TNotifyEvent = Nil);
  40. Property ResourceList : TStrings Read FResourceList;
  41. Published
  42. Property OnGetResources : TNotifyEvent Read FonGetResources Write FOnGetResources;
  43. Property metaDataProperty : String read FmetaDataProperty Write FmetaDataProperty Stored DoStoreMetadataProp;
  44. Property DataProperty : String read FDataProperty Write FDataProperty Stored DoStoreDataProp;
  45. Property MetaDataResourceName : String Read FMetaDataResourceName Write FMetaDataResourceName Stored DoStoreMetadata;
  46. Property UserName : String Read FUserName Write FUserName;
  47. Property Password : String Read FPassword Write FPassword;
  48. end;
  49. { TSQLDBRestDataset }
  50. TSQLDBRestDataset = Class(TJSONDataset)
  51. private
  52. FConnection: TSQLDBRestConnection;
  53. FResourceName: String;
  54. procedure SetConnection(AValue: TSQLDBRestConnection);
  55. procedure SetResourceName(AValue: String);
  56. Protected
  57. function DataPacketReceived(ARequest: TDataRequest): Boolean; override;
  58. function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer;virtual;
  59. function StringToFieldType(S: String): TFieldType; virtual;
  60. Function DoGetDataProxy: TDataProxy; override;
  61. Procedure MetaDataToFieldDefs; override;
  62. Public
  63. Property Connection: TSQLDBRestConnection Read FConnection Write SetConnection;
  64. Property ResourceName : String Read FResourceName Write SetResourceName;
  65. end;
  66. implementation
  67. Type
  68. { TServiceRequest }
  69. TServiceRequest = Class(TObject)
  70. Private
  71. FOnMyDone,
  72. FOnDone : TNotifyEvent;
  73. FXHR: TJSXMLHttpRequest;
  74. function GetResult: String;
  75. function GetResultJSON: TJSObject;
  76. function GetStatusCode: Integer;
  77. function onLoad(Event{%H-}: TEventListenerEvent): boolean;
  78. Public
  79. Constructor Create(Const aMethod,aURL,aUserName,aPassword : String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
  80. Procedure Execute;
  81. Property RequestResult : String read GetResult;
  82. Property ResultJSON : TJSObject read GetResultJSON;
  83. Property OnDone : TNotifyEvent Read FOnDone;
  84. Property StatusCode : Integer Read GetStatusCode;
  85. end;
  86. { TServiceRequest }
  87. constructor TServiceRequest.Create(const aMethod,aURL, aUserName, aPassword: String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
  88. begin
  89. FOnMyDone:=aOnDone1;
  90. FOnDone:=aOnDone2;
  91. FXHR:=TJSXMLHttpRequest.New;
  92. FXHR.AddEventListener('load',@onLoad);
  93. FXHR.open(aMethod,aURL,true);
  94. (* else
  95. begin
  96. // FXHR.withCredentials := true;
  97. FXHR.open(aMethod,aURL,true,aUserName,aPassword);
  98. end;*)
  99. FXHR.setRequestHeader('Content-Type', 'application/json');
  100. FXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(aUserName+':'+aPassword));
  101. end;
  102. procedure TServiceRequest.Execute;
  103. begin
  104. FXHR.send;
  105. end;
  106. function TServiceRequest.GetResult: String;
  107. begin
  108. Result:=FXHR.responseText;
  109. end;
  110. function TServiceRequest.GetResultJSON: TJSObject;
  111. begin
  112. if SameText(FXHR.getResponseHeader('Content-Type'),'application/json') then
  113. Result:=TJSJSON.parseObject(GetResult)
  114. else
  115. Result:=nil;
  116. end;
  117. function TServiceRequest.GetStatusCode: Integer;
  118. begin
  119. Result:=FXHR.Status;
  120. end;
  121. function TServiceRequest.onLoad(Event: TEventListenerEvent): boolean;
  122. begin
  123. if Assigned(FOnMyDone) then
  124. FOnMyDone(Self);
  125. end;
  126. { TSQLDBRestConnection }
  127. function TSQLDBRestConnection.DoStoreMetadata: Boolean;
  128. begin
  129. Result:=(FMetadataResourceName<>'metadata');
  130. end;
  131. function TSQLDBRestConnection.DoStoreMetadataProp: Boolean;
  132. begin
  133. Result:=(FMetaDataProperty<>'metaData');
  134. end;
  135. procedure TSQLDBRestConnection.SetupRequest(aXHR: TJSXMLHttpRequest);
  136. begin
  137. inherited SetupRequest(aXHR);
  138. aXHR.setRequestHeader('Content-Type', 'application/json');
  139. aXHR.setRequestHeader('Accept', 'application/json');
  140. if (UserName<>'') then
  141. aXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(UserName+':'+Password));
  142. end;
  143. function TSQLDBRestConnection.GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String;
  144. begin
  145. Result:=inherited GetUpdateBaseURL(aRequest);
  146. Result:=IncludeTrailingPathDelimiter(Result)+TSQLDBRestDataset(aRequest.Dataset).ResourceName;
  147. end;
  148. function TSQLDBRestConnection.GetReadBaseURL(aRequest: TDataRequest): String;
  149. begin
  150. Result:=inherited GetReadBaseURL(aRequest);
  151. Result:=IncludeTrailingPathDelimiter(Result)+TSQLDBRestDataset(aRequest.Dataset).ResourceName;
  152. end;
  153. procedure TSQLDBRestConnection.DoResources(Sender: TObject);
  154. Var
  155. R : TServiceRequest absolute Sender;
  156. J,Res : TJSObject;
  157. A : TJSArray;
  158. i : Integer;
  159. begin
  160. FResourceList.Clear;
  161. if (R.StatusCode=200) then
  162. begin
  163. J:=R.ResultJSON;
  164. if J=Nil then
  165. exit;
  166. A:=TJSArray(J.Properties['data']);
  167. For I:=0 to A.Length-1 do
  168. begin
  169. Res:=TJSObject(A[i]);
  170. FResourceList.Add(String(Res.Properties['name']));
  171. end;
  172. end;
  173. If Assigned(R.OnDone) then
  174. R.OnDone(Self);
  175. If Assigned(OnGetResources) then
  176. OnGetResources(Self);
  177. end;
  178. function TSQLDBRestConnection.DoStoreDataProp: Boolean;
  179. begin
  180. Result:=(FDataProperty<>'data');
  181. end;
  182. constructor TSQLDBRestConnection.create(aOwner: TComponent);
  183. begin
  184. inherited create(aOwner);
  185. FResourceList:=TStringList.Create;
  186. FMetaDataResourceName:='metadata';
  187. FmetaDataProperty:='metaData';
  188. FDataProperty:='data';
  189. TStringList(FResourceList).Sorted:=true;
  190. end;
  191. destructor TSQLDBRestConnection.Destroy;
  192. begin
  193. FreeAndNil(FResourceList);
  194. inherited Destroy;
  195. end;
  196. procedure TSQLDBRestConnection.GetResources(OnResult: TNotifyEvent);
  197. Var
  198. aURL : String;
  199. R : TServiceRequest;
  200. begin
  201. aURL:=IncludeTrailingPathDelimiter(BaseURL)+MetaDataResourceName+'?fmt=json';
  202. R:=TServiceRequest.Create('GET',aURL,Self.UserName,Self.Password,@DoResources,OnResult);
  203. R.Execute;
  204. end;
  205. { TSQLDBRestDataset }
  206. procedure TSQLDBRestDataset.SetConnection(AValue: TSQLDBRestConnection);
  207. begin
  208. if FConnection=AValue then Exit;
  209. if Assigned(FConnection) then
  210. FConnection.RemoveFreeNotification(Self);
  211. FConnection:=AValue;
  212. if Assigned(FConnection) then
  213. FConnection.FreeNotification(Self);
  214. end;
  215. procedure TSQLDBRestDataset.SetResourceName(AValue: String);
  216. begin
  217. if FResourceName=AValue then Exit;
  218. CheckInactive;
  219. FResourceName:=AValue;
  220. end;
  221. function TSQLDBRestDataset.DoGetDataProxy: TDataProxy;
  222. begin
  223. Result:=Connection.DataProxy;
  224. end;
  225. function TSQLDBRestDataset.StringToFieldType(S: String): TFieldType;
  226. begin
  227. if (s='int') then
  228. Result:=ftInteger
  229. else if (s='bigint') then
  230. Result:=ftLargeInt
  231. else if (s='float') then
  232. Result:=ftFloat
  233. else if (s='bool') then
  234. Result:=ftBoolean
  235. else if (s='date') then
  236. Result:=ftDate
  237. else if (s='datetime') then
  238. Result:=ftDateTime
  239. else if (s='time') then
  240. Result:=ftTime
  241. else if (s='blob') then
  242. Result:=ftBlob
  243. else if (s='string') then
  244. Result:=ftString
  245. else
  246. if MapUnknownToStringType then
  247. Result:=ftString
  248. else
  249. Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
  250. end;
  251. function TSQLDBRestDataset.GetStringFieldLength(F: TJSObject; AName: String;
  252. AIndex: Integer): integer;
  253. Var
  254. I,L : Integer;
  255. D : JSValue;
  256. begin
  257. Result:=0;
  258. D:=F.Properties['maxLen'];
  259. if Not jsIsNan(toNumber(D)) then
  260. begin
  261. Result:=Trunc(toNumber(D));
  262. if (Result<=0) then
  263. Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
  264. end
  265. else
  266. begin
  267. For I:=0 to Rows.Length-1 do
  268. begin
  269. D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
  270. if isString(D) then
  271. begin
  272. l:=Length(String(D));
  273. if L>Result then
  274. Result:=L;
  275. end;
  276. end;
  277. end;
  278. if (Result=0) then
  279. Result:=20;
  280. end;
  281. procedure TSQLDBRestDataset.MetaDataToFieldDefs;
  282. Var
  283. A : TJSArray;
  284. F : TJSObject;
  285. I,FS : Integer;
  286. N: String;
  287. ft: TFieldType;
  288. D : JSValue;
  289. begin
  290. FieldDefs.Clear;
  291. D:=Metadata.Properties['fields'];
  292. if Not IsArray(D) then
  293. Raise EJSONDataset.Create('Invalid metadata object');
  294. A:=TJSArray(D);
  295. For I:=0 to A.Length-1 do
  296. begin
  297. If Not isObject(A[i]) then
  298. Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
  299. F:=TJSObject(A[i]);
  300. D:=F.Properties['name'];
  301. If Not isString(D) then
  302. Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
  303. N:=String(D);
  304. D:=F.Properties['type'];
  305. If IsNull(D) or isUndefined(D) then
  306. ft:=ftstring
  307. else If Not isString(D) then
  308. begin
  309. Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
  310. end
  311. else
  312. begin
  313. ft:=StringToFieldType(String(D));
  314. end;
  315. if (ft=ftString) then
  316. fs:=GetStringFieldLength(F,N,I)
  317. else
  318. fs:=0;
  319. FieldDefs.Add(N,ft,fs);
  320. end;
  321. end;
  322. function TSQLDBRestDataset.DataPacketReceived(ARequest: TDataRequest): Boolean;
  323. Var
  324. O : TJSObject;
  325. A : TJSArray;
  326. smetadata,sroot : String;
  327. begin
  328. Result:=False;
  329. If isNull(aRequest.Data) then
  330. exit;
  331. If isString(aRequest.Data) then
  332. O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
  333. else if isObject(aRequest.Data) then
  334. O:=TJSOBject(aRequest.Data)
  335. else
  336. DatabaseError('Cannot handle data packet');
  337. sRoot:=Connection.DataProperty;
  338. sMetaData:=Connection.metaDataProperty;
  339. if (sroot='') then
  340. sroot:='data';
  341. if (smetadata='') then
  342. smetadata:='metaData';
  343. { if (IDField='') then
  344. idField:='id';}
  345. if O.hasOwnProperty(sMetaData) and isObject(o[sMetaData]) then
  346. begin
  347. if not Active then // Load fields from metadata
  348. metaData:=TJSObject(o[SMetaData]);
  349. { if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
  350. IDField:=string(metaData['idField']);}
  351. end;
  352. if O.hasOwnProperty(sRoot) and isArray(o[sRoot]) then
  353. begin
  354. A:=TJSArray(o[sRoot]);
  355. Result:=A.Length>0;
  356. AddToRows(A);
  357. end;
  358. end;
  359. end.