sqldbrestdataset.pp 13 KB

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