sqldbrestdataset.pp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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. FConnectionsResourceName: String;
  22. FCustomViewResourceName: String;
  23. FDataProperty: String;
  24. FmetaDataProperty: String;
  25. FMetaDataResourceName: String;
  26. FonGetResources: TNotifyEvent;
  27. FPassword: String;
  28. FResourceList: TStrings;
  29. FUserName: String;
  30. procedure DoResources(Sender: TObject);
  31. function DoStoreDataProp: Boolean;
  32. function DoStoreMetadata: Boolean;
  33. function DoStoreMetadataProp: Boolean;
  34. Protected
  35. Procedure SetupRequest(aXHR : TJSXMLHttpRequest); override;
  36. Function GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String; override;
  37. Function GetReadBaseURL(aRequest: TDataRequest): String; Override;
  38. Public
  39. Constructor create(aOwner : TComponent); override;
  40. Destructor Destroy; override;
  41. Procedure GetResources(OnResult : TNotifyEvent = Nil);
  42. Property ResourceList : TStrings Read FResourceList;
  43. Published
  44. Property OnGetResources : TNotifyEvent Read FonGetResources Write FOnGetResources;
  45. Property metaDataProperty : String read FmetaDataProperty Write FmetaDataProperty Stored DoStoreMetadataProp;
  46. Property DataProperty : String read FDataProperty Write FDataProperty Stored DoStoreDataProp;
  47. Property MetaDataResourceName : String Read FMetaDataResourceName Write FMetaDataResourceName Stored DoStoreMetadata;
  48. Property UserName : String Read FUserName Write FUserName;
  49. Property Password : String Read FPassword Write FPassword;
  50. Property ConnectionsResourceName : String Read FConnectionsResourceName Write FConnectionsResourceName;
  51. Property CustomViewResourceName : String Read FCustomViewResourceName Write FCustomViewResourceName;
  52. // Published from TRESTCOnnection
  53. Property BaseURL;
  54. Property PageParam;
  55. Property OnGetURL;
  56. Property OnUpdateURL;
  57. end;
  58. { TSQLDBRestDataset }
  59. TSQLDBRestDataset = Class(TJSONDataset)
  60. private
  61. FConnection: TSQLDBRestConnection;
  62. FDatabaseConnection: String;
  63. FResourceName: String;
  64. FSQL: TStrings;
  65. function CleanSQL: String;
  66. function CustomViewResourceName: String;
  67. procedure DoSQLChange(Sender: TObject);
  68. function MyURL: String;
  69. procedure SetConnection(AValue: TSQLDBRestConnection);
  70. procedure SetResourceName(AValue: String);
  71. procedure SetSQL(AValue: TStrings);
  72. Protected
  73. Class Function DefaultBlobDataToBytes(aValue : JSValue) : TBytes; override;
  74. function DataPacketReceived(ARequest: TDataRequest): Boolean; override;
  75. function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer;virtual;
  76. function StringToFieldType(S: String): TFieldType; virtual;
  77. Function DoGetDataProxy: TDataProxy; override;
  78. Procedure MetaDataToFieldDefs; override;
  79. Public
  80. Constructor Create(aOwner : TComponent); override;
  81. Destructor Destroy; override;
  82. Published
  83. Property Connection: TSQLDBRestConnection Read FConnection Write SetConnection;
  84. Property ResourceName : String Read FResourceName Write SetResourceName;
  85. Property SQL : TStrings Read FSQL Write SetSQL;
  86. property DatabaseConnection : String Read FDatabaseConnection Write FDatabaseConnection;
  87. end;
  88. implementation
  89. Type
  90. { TServiceRequest }
  91. TServiceRequest = Class(TObject)
  92. Private
  93. FOnMyDone,
  94. FOnDone : TNotifyEvent;
  95. FXHR: TJSXMLHttpRequest;
  96. function GetResult: String;
  97. function GetResultJSON: TJSObject;
  98. function GetStatusCode: Integer;
  99. function onLoad(Event{%H-}: TEventListenerEvent): boolean;
  100. Public
  101. Constructor Create(Const aMethod,aURL,aUserName,aPassword : String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
  102. Procedure Execute;
  103. Property RequestResult : String read GetResult;
  104. Property ResultJSON : TJSObject read GetResultJSON;
  105. Property OnDone : TNotifyEvent Read FOnDone;
  106. Property StatusCode : Integer Read GetStatusCode;
  107. end;
  108. { TServiceRequest }
  109. constructor TServiceRequest.Create(const aMethod,aURL, aUserName, aPassword: String; aOnDone1 : TNotifyEvent; aOnDone2 : TNotifyEvent = Nil);
  110. begin
  111. FOnMyDone:=aOnDone1;
  112. FOnDone:=aOnDone2;
  113. FXHR:=TJSXMLHttpRequest.New;
  114. FXHR.AddEventListener('load',@onLoad);
  115. FXHR.open(aMethod,aURL,true);
  116. (* else
  117. begin
  118. // FXHR.withCredentials := true;
  119. FXHR.open(aMethod,aURL,true,aUserName,aPassword);
  120. end;*)
  121. FXHR.setRequestHeader('Content-Type', 'application/json');
  122. FXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(aUserName+':'+aPassword));
  123. end;
  124. procedure TServiceRequest.Execute;
  125. begin
  126. FXHR.send;
  127. end;
  128. function TServiceRequest.GetResult: String;
  129. begin
  130. Result:=FXHR.responseText;
  131. end;
  132. function TServiceRequest.GetResultJSON: TJSObject;
  133. begin
  134. if SameText(FXHR.getResponseHeader('Content-Type'),'application/json') then
  135. Result:=TJSJSON.parseObject(GetResult)
  136. else
  137. Result:=nil;
  138. end;
  139. function TServiceRequest.GetStatusCode: Integer;
  140. begin
  141. Result:=FXHR.Status;
  142. end;
  143. function TServiceRequest.onLoad(Event: TEventListenerEvent): boolean;
  144. begin
  145. if Assigned(FOnMyDone) then
  146. FOnMyDone(Self);
  147. end;
  148. { TSQLDBRestConnection }
  149. function TSQLDBRestConnection.DoStoreMetadata: Boolean;
  150. begin
  151. Result:=(FMetadataResourceName<>'metadata');
  152. end;
  153. function TSQLDBRestConnection.DoStoreMetadataProp: Boolean;
  154. begin
  155. Result:=(FMetaDataProperty<>'metaData');
  156. end;
  157. procedure TSQLDBRestConnection.SetupRequest(aXHR: TJSXMLHttpRequest);
  158. begin
  159. inherited SetupRequest(aXHR);
  160. aXHR.setRequestHeader('Content-Type', 'application/json');
  161. aXHR.setRequestHeader('Accept', 'application/json');
  162. if (UserName<>'') then
  163. aXHR.setRequestHeader('Authorization', 'Basic '+window.btoa(UserName+':'+Password));
  164. end;
  165. function TSQLDBRestConnection.GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String;
  166. begin
  167. Result:=inherited GetUpdateBaseURL(aRequest);
  168. Result:=IncludeTrailingPathDelimiter(Result)+TSQLDBRestDataset(aRequest.Dataset).ResourceName;
  169. end;
  170. function TSQLDBRestConnection.GetReadBaseURL(aRequest: TDataRequest): String;
  171. Var
  172. DS : TSQLDBRestDataset;
  173. begin
  174. Result:=inherited GetReadBaseURL(aRequest);
  175. DS:=TSQLDBRestDataset(aRequest.Dataset);
  176. Result:=IncludeTrailingPathDelimiter(Result)+DS.MyURL;
  177. end;
  178. procedure TSQLDBRestConnection.DoResources(Sender: TObject);
  179. Var
  180. R : TServiceRequest absolute Sender;
  181. J,Res : TJSObject;
  182. A : TJSArray;
  183. i : Integer;
  184. begin
  185. FResourceList.Clear;
  186. if (R.StatusCode=200) then
  187. begin
  188. J:=R.ResultJSON;
  189. if J=Nil then
  190. exit;
  191. A:=TJSArray(J.Properties['data']);
  192. For I:=0 to A.Length-1 do
  193. begin
  194. Res:=TJSObject(A[i]);
  195. FResourceList.Add(String(Res.Properties['name']));
  196. end;
  197. end;
  198. If Assigned(R.OnDone) then
  199. R.OnDone(Self);
  200. If Assigned(OnGetResources) then
  201. OnGetResources(Self);
  202. end;
  203. function TSQLDBRestConnection.DoStoreDataProp: Boolean;
  204. begin
  205. Result:=(FDataProperty<>'data');
  206. end;
  207. constructor TSQLDBRestConnection.create(aOwner: TComponent);
  208. begin
  209. inherited create(aOwner);
  210. FResourceList:=TStringList.Create;
  211. FMetaDataResourceName:='metadata';
  212. FmetaDataProperty:='metaData';
  213. FDataProperty:='data';
  214. TStringList(FResourceList).Sorted:=true;
  215. end;
  216. destructor TSQLDBRestConnection.Destroy;
  217. begin
  218. FreeAndNil(FResourceList);
  219. inherited Destroy;
  220. end;
  221. procedure TSQLDBRestConnection.GetResources(OnResult: TNotifyEvent);
  222. Var
  223. aURL : String;
  224. R : TServiceRequest;
  225. begin
  226. aURL:=IncludeTrailingPathDelimiter(BaseURL)+MetaDataResourceName+'?fmt=json';
  227. R:=TServiceRequest.Create('GET',aURL,Self.UserName,Self.Password,@DoResources,OnResult);
  228. R.Execute;
  229. end;
  230. { TSQLDBRestDataset }
  231. procedure TSQLDBRestDataset.SetConnection(AValue: TSQLDBRestConnection);
  232. begin
  233. if FConnection=AValue then Exit;
  234. if Assigned(FConnection) then
  235. FConnection.RemoveFreeNotification(Self);
  236. FConnection:=AValue;
  237. if Assigned(FConnection) then
  238. FConnection.FreeNotification(Self);
  239. end;
  240. function TSQLDBRestDataset.MyURL: String;
  241. begin
  242. Result:=DatabaseConnection;
  243. if (Result<>'') and (Result[Length(Result)]<>'/') then
  244. Result:=Result+'/';
  245. Result:=Result+ResourceName;
  246. if SameText(ResourceName,CustomViewResourceName) then
  247. Result:=Result+'?SQL='+ EncodeURIComponent(CleanSQL);
  248. end;
  249. procedure TSQLDBRestDataset.DoSQLChange(Sender: TObject);
  250. begin
  251. if Trim(FSQL.Text)<>'' then
  252. FResourceName:=CustomViewResourceName;
  253. end;
  254. procedure TSQLDBRestDataset.SetResourceName(AValue: String);
  255. begin
  256. if FResourceName=AValue then Exit;
  257. CheckInactive;
  258. if Not SameText(aValue,CustomViewResourceName) then
  259. FSQL.Clear;
  260. FResourceName:=AValue;
  261. end;
  262. function TSQLDBRestDataset.CustomViewResourceName : String;
  263. begin
  264. if Assigned(Connection) then
  265. Result:=Connection.CustomViewResourceName
  266. else
  267. Result:='customView';
  268. end;
  269. function TSQLDBRestDataset.CleanSQL: String;
  270. begin
  271. Result:=StringReplace(SQL.Text,#13#10,' ',[rfReplaceAll]);
  272. Result:=StringReplace(Result,#10,' ',[rfReplaceAll]);
  273. Result:=StringReplace(Result,#10,' ',[rfReplaceAll]);
  274. end;
  275. procedure TSQLDBRestDataset.SetSQL(AValue: TStrings);
  276. begin
  277. if FSQL=AValue then Exit;
  278. FSQL.Assign(AValue);
  279. end;
  280. class function TSQLDBRestDataset.DefaultBlobDataToBytes(aValue: JSValue): TBytes;
  281. begin
  282. Result:=BytesOf(Window.atob(String(aValue)));
  283. end;
  284. function TSQLDBRestDataset.DoGetDataProxy: TDataProxy;
  285. begin
  286. Result:=Connection.DataProxy;
  287. end;
  288. function TSQLDBRestDataset.StringToFieldType(S: String): TFieldType;
  289. begin
  290. if (s='int') then
  291. Result:=ftInteger
  292. else if (s='bigint') then
  293. Result:=ftLargeInt
  294. else if (s='float') then
  295. Result:=ftFloat
  296. else if (s='bool') then
  297. Result:=ftBoolean
  298. else if (s='date') then
  299. Result:=ftDate
  300. else if (s='datetime') then
  301. Result:=ftDateTime
  302. else if (s='time') then
  303. Result:=ftTime
  304. else if (s='blob') then
  305. Result:=ftBlob
  306. else if (s='string') then
  307. Result:=ftString
  308. else
  309. if MapUnknownToStringType then
  310. Result:=ftString
  311. else
  312. Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
  313. end;
  314. function TSQLDBRestDataset.GetStringFieldLength(F: TJSObject; AName: String;
  315. AIndex: Integer): integer;
  316. Var
  317. I,L : Integer;
  318. D : JSValue;
  319. begin
  320. Result:=0;
  321. D:=F.Properties['maxLen'];
  322. if Not jsIsNan(toNumber(D)) then
  323. begin
  324. Result:=Trunc(toNumber(D));
  325. if (Result<=0) then
  326. Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
  327. end
  328. else
  329. begin
  330. For I:=0 to Rows.Length-1 do
  331. begin
  332. D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
  333. if isString(D) then
  334. begin
  335. l:=Length(String(D));
  336. if L>Result then
  337. Result:=L;
  338. end;
  339. end;
  340. end;
  341. if (Result=0) then
  342. Result:=20;
  343. end;
  344. procedure TSQLDBRestDataset.MetaDataToFieldDefs;
  345. Var
  346. A : TJSArray;
  347. F : TJSObject;
  348. I,FS : Integer;
  349. N: String;
  350. ft: TFieldType;
  351. D : JSValue;
  352. begin
  353. FieldDefs.Clear;
  354. D:=Metadata.Properties['fields'];
  355. if Not IsArray(D) then
  356. Raise EJSONDataset.Create('Invalid metadata object');
  357. A:=TJSArray(D);
  358. For I:=0 to A.Length-1 do
  359. begin
  360. If Not isObject(A[i]) then
  361. Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
  362. F:=TJSObject(A[i]);
  363. D:=F.Properties['name'];
  364. If Not isString(D) then
  365. Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
  366. N:=String(D);
  367. D:=F.Properties['type'];
  368. If IsNull(D) or isUndefined(D) then
  369. ft:=ftstring
  370. else If Not isString(D) then
  371. begin
  372. Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
  373. end
  374. else
  375. begin
  376. ft:=StringToFieldType(String(D));
  377. end;
  378. if (ft=ftString) then
  379. fs:=GetStringFieldLength(F,N,I)
  380. else
  381. fs:=0;
  382. FieldDefs.Add(N,ft,fs);
  383. end;
  384. end;
  385. constructor TSQLDBRestDataset.Create(aOwner: TComponent);
  386. begin
  387. inherited Create(aOwner);
  388. FSQL:=TStringList.Create;
  389. TStringList(FSQL).OnChange:=@DoSQLChange;
  390. end;
  391. destructor TSQLDBRestDataset.Destroy;
  392. begin
  393. FreeAndNil(FSQL);
  394. inherited Destroy;
  395. end;
  396. function TSQLDBRestDataset.DataPacketReceived(ARequest: TDataRequest): Boolean;
  397. Var
  398. O : TJSObject;
  399. A : TJSArray;
  400. smetadata,sroot : String;
  401. begin
  402. Result:=False;
  403. If isNull(aRequest.Data) then
  404. exit;
  405. If isString(aRequest.Data) then
  406. O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
  407. else if isObject(aRequest.Data) then
  408. O:=TJSOBject(aRequest.Data)
  409. else
  410. DatabaseError('Cannot handle data packet');
  411. sRoot:=Connection.DataProperty;
  412. sMetaData:=Connection.metaDataProperty;
  413. if (sroot='') then
  414. sroot:='data';
  415. if (smetadata='') then
  416. smetadata:='metaData';
  417. { if (IDField='') then
  418. idField:='id';}
  419. if O.hasOwnProperty(sMetaData) and isObject(o[sMetaData]) then
  420. begin
  421. if not Active then // Load fields from metadata
  422. metaData:=TJSObject(o[SMetaData]);
  423. { if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
  424. IDField:=string(metaData['idField']);}
  425. end;
  426. if O.hasOwnProperty(sRoot) and isArray(o[sRoot]) then
  427. begin
  428. A:=TJSArray(o[sRoot]);
  429. Result:=A.Length>0;
  430. AddToRows(A);
  431. end;
  432. end;
  433. end.