2
0

sqldbrestdataset.pp 13 KB

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