2
0

dadataset.pas 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2018 by Michael Van Canneyt, member of the
  4. Free Pascal development team
  5. Dataset which talks to Remobjects Data Abstract server.
  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 dadataset;
  13. {$mode objfpc}
  14. {$modeswitch externalclass}
  15. interface
  16. uses Types, Classes, DB, jsonDataset, JS, rosdk, da, dasdk;
  17. Type
  18. EDADataset = Class(EDatabaseError);
  19. TDAConnection = Class;
  20. { TDAWhereClauseBuilder }
  21. TDAWhereClauseBuilder = class
  22. public
  23. class function NewBinaryExpression(aLeft, aRight: TDAExpression; anOp: TDABinaryOperator): TDAExpression;overload;
  24. class function NewBinaryExpression(aLeft: TDAExpression; anOp: TDABinaryOperator;const aValue: JSValue): TDAExpression;overload;
  25. class function NewBinaryExpression(aLeft: TDAExpression; anOp: TDABinaryOperator;const aValue: JSValue; aType: TDADataType): TDAExpression;overload;
  26. class function NewBinaryExpression(const aTableName,aFieldName: string; anOp: TDABinaryOperator; const aJSValue: JSValue; aType: TDADataType): TDAExpression; overload;
  27. class function NewBinaryExpression(const aTableName,aFieldName: string; anOp: TDABinaryOperator; const aJSValue: JSValue): TDAExpression; overload;
  28. class function NewBinaryExpression(const aTableName,aFieldName: string; const aParameterName: string; aParameterType: TDADataType; anOp: TDABinaryOperator): TDAExpression; overload;
  29. class function NewBinaryExpressionList(const aExpressions: array of TDAExpression; anOp: TDABinaryOperator): TDAExpression;
  30. class function NewUnaryExpression(anExpression: TDAExpression; anOp: TDAUnaryOperator): TDAExpression;
  31. class function NewConstant(const aValue: jsValue): TDAExpression; overload;
  32. class function NewConstant(const aValue: jsValue; aType: TDADataType): TDAExpression; overload;
  33. class function NewDateTimeConstant(const aValue: TJSDate): TDAExpression; overload;
  34. class function NewDateTimeConstant(const aValue: TDateTime): TDAExpression; overload;
  35. class function NewList(const aValues: array of TDAExpression): TDAExpression;
  36. class function NewParameter(const aParameterName: string; aParameterType: TDADataType = datUnknown): TDAExpression;
  37. class function NewField(const aTableName,aFieldName: string): TDAExpression;
  38. class function NewNull: TDAExpression;
  39. class function NewIsNotNull: TDAExpression; overload;
  40. class function NewIsNotNull(const aTableName,aFieldName: string): TDAExpression; overload;
  41. class function NewIsNull(const aTableName,aFieldName: string): TDAExpression; overload;
  42. class function NewMacro(const aName: string): TDAExpression; overload;
  43. class function NewMacro(const aName: string; const aValues: array of TDAExpression): TDAExpression; overload;
  44. class function NewBetweenExpression(aExpression, aLower, aUpper: TDAExpression): TDAExpression; overload;
  45. class function NewBetweenExpression(const aExprTableName, aExprFieldName: string; aLower, aUpper: TDAExpression): TDAExpression; overload;
  46. class function NewBetweenExpression(const aExprTableName, aExprFieldName: string; aLowerValue, aUpperValue: JSValue; aValuesDataType: TDADataType): TDAExpression; overload;
  47. class function GetWhereClause (aExpression : TDAExpression) : String;
  48. end;
  49. Type
  50. TDADataRow = Class external name 'Object' (TJSObject)
  51. _new,
  52. _old : TJSValueDynArray;
  53. end;
  54. TDaDataRowArray = Array of TDADataRow;
  55. TResolvedRow = Class external name 'Object' (TJSObject)
  56. changes : TDAChange;
  57. fields : TLogFieldArray;
  58. end;
  59. { TDAArrayFieldMapper }
  60. TDAArrayFieldMapper = Class(TJSONArrayFieldMapper)
  61. Public
  62. Procedure RemoveField(Const FieldName : String; FieldIndex : Integer; Row : JSValue); override;
  63. procedure SetJSONDataForField(Const FieldName{%H-} : String; FieldIndex : Integer; Row,Data : JSValue); override;
  64. Function GetJSONDataForField(Const FieldName{%H-} : String; FieldIndex : Integer; Row : JSValue) : JSValue; override;
  65. Function CreateRow : JSValue; override;
  66. end;
  67. { TDADataset }
  68. TDADatasetOption = (doRefreshAllFields);
  69. TDADatasetOptions = Set of TDADatasetOption;
  70. TDADataset = class(TBaseJSONDataset)
  71. private
  72. FDAOptions: TDADatasetOptions;
  73. FParams: TParams;
  74. FTableName: String;
  75. FDAConnection: TDAConnection;
  76. FWhereClause: String;
  77. FWhereClauseBuilder : TDAWhereClauseBuilder;
  78. function DataTypeToFieldType(s: String): TFieldType;
  79. procedure SetParams(AValue: TParams);
  80. Protected
  81. function DoResolveRecordUpdate(anUpdate : TRecordUpdateDescriptor): Boolean; override;
  82. Procedure MetaDataToFieldDefs; override;
  83. Procedure InternalEdit; override;
  84. Procedure InternalDelete; override;
  85. // These operate on metadata received from DA.
  86. function GetDAFields: TDAFieldArray;
  87. function GetExcludedFields : TNativeIntDynArray;
  88. Function GetLoggedFields : TLogFieldArray;
  89. function GetKeyFields: TStringDynArray;
  90. Public
  91. constructor create(aOwner : TComponent); override;
  92. Destructor Destroy; override;
  93. function ConvertToDateTime(aField : TField; aValue : JSValue; ARaiseException : Boolean) : TDateTime; override;
  94. function ConvertDateTimeToNative(aField : TField; aValue : TDateTime) : JSValue; override;
  95. function DoGetDataProxy: TDataProxy; override;
  96. Function ParamByName(Const aName : string) : TParam;
  97. Function FindParam(Const aName : string) : TParam;
  98. Property WhereClauseBuilder : TDAWhereClauseBuilder Read FWhereClauseBuilder;
  99. // DA is index based. So create array field mapper.
  100. function CreateFieldMapper : TJSONFieldMapper; override;
  101. Procedure CreateFieldDefs(a : TJSArray);
  102. Property TableName : String Read FTableName Write FTableName;
  103. Property DAConnection : TDAConnection Read FDAConnection Write FDAConnection;
  104. Property Params : TParams Read FParams Write SetParams;
  105. Property WhereClause : String Read FWhereClause Write FWhereClause;
  106. Property DAOptions : TDADatasetOptions Read FDAOptions Write FDAOptions;
  107. end;
  108. TDADataRequest = Class(TDataRequest)
  109. Public
  110. Procedure doSuccess(res : JSValue) ;
  111. Procedure DoFail(response: TROMessage; fail: TjsError) ;
  112. End;
  113. { TDADataProxy }
  114. TDADataProxy = class(TDataProxy)
  115. private
  116. FConnection: TDAConnection;
  117. function ConvertParams(DADS: TDADataset): TDADataParameterDataArray;
  118. procedure ProcessUpdateResult(Res: JSValue; aBatch: TRecordUpdateBatch);
  119. Protected
  120. Function GetDataRequestClass : TDataRequestClass; override;
  121. Public
  122. Function DoGetData(aRequest : TDataRequest) : Boolean; override;
  123. Function ProcessUpdateBatch(aBatch : TRecordUpdateBatch): Boolean; override;
  124. Property Connection : TDAConnection Read FConnection Write FConnection;
  125. end;
  126. TDAMessageType = (mtAuto, // autodetect from URL
  127. mtBin, // use BinMessage
  128. mtJSON); // Use JSONMessage.
  129. TDAStreamerType = (stJSON,stBin);
  130. { TDAConnection }
  131. TLoginFailedEvent = Reference to procedure (Msg : TROMessage; Err : String);
  132. TDAConnection = class(TComponent)
  133. private
  134. FDataService: TDADataAbstractService;
  135. FDataserviceName: String;
  136. FLoginService: TDASimpleLoginService;
  137. FLoginServiceName: String;
  138. FMessageType: TDAMessageType;
  139. FMessage : TROmessage;
  140. FChannel : TROHTTPClientChannel;
  141. FOnLoginFailed: TLoginFailedEvent;
  142. FOnLogin: TDALoginSuccessEvent;
  143. FOnLogout: TDASuccessEvent;
  144. FOnLogoutailed: TDAFailedEvent;
  145. FOnLogoutFailed: TDAFailedEvent;
  146. FStreamerType: TDAStreamerType;
  147. FURL: String;
  148. procedure ClearConnection;
  149. function GetChannel: TROHTTPClientChannel;
  150. function GetClientID: String;
  151. Function GetDataService : TDADataAbstractService;
  152. function GetLoginService: TDASimpleLoginService;
  153. function GetMessage: TROMessage;
  154. procedure SetDataserviceName(AValue: String);
  155. procedure SetLoginServiceName(AValue: String);
  156. procedure SetMessageType(AValue: TDAMessageType);
  157. procedure SetURL(AValue: String);
  158. procedure DoLoginFailed(Msg: TROMessage; aErr: TJSError);
  159. Protected
  160. Procedure CreateChannelAndMessage; virtual;
  161. function DetectMessageType(Const aURL: String): TDAMessageType; virtual;
  162. Function CreateDataService : TDADataAbstractService; virtual;
  163. Function CreateLoginService : TDASimpleLoginService; virtual;
  164. Function CreateStreamer : TDADataStreamer;
  165. Function InterpretMessage(aRes : JSValue) : String;
  166. Public
  167. Constructor create(aOwner : TComponent); override;
  168. Destructor Destroy; override;
  169. // Returns a non-auto MessageType, but raises exception if it cannot be determined;
  170. Function EnsureMessageType : TDAMessageType;
  171. // Returns DataService, but raises exception if it is nil;
  172. Function EnsureDataservice : TDADataAbstractService;
  173. // Returns SimpleLoginService, but raises exception if it is nil;
  174. Function EnsureLoginservice : TDASimpleLoginService;
  175. // Call this to login. This is an asynchronous call, check the result using OnLoginOK and OnLoginFailed calls.
  176. Procedure Login(aUserName, aPassword : String);
  177. Procedure LoginEx(aLoginString : String);
  178. Procedure Logout;
  179. // You can set this. If you didn't set this, and URL is filled, an instance will be created.
  180. Property DataService : TDADataAbstractService Read GetDataService Write FDataService;
  181. // You can set this. If you didn't set this, and URL is filled, an instance will be created.
  182. Property LoginService : TDASimpleLoginService Read GetLoginService Write FLoginService;
  183. // You can get this to use in other service constructors
  184. Property Channel : TROHTTPClientChannel Read GetChannel;
  185. Property Message : TROMessage Read GetMessage;
  186. // Get client ID
  187. Property ClientID : String Read GetClientID;
  188. Published
  189. // If set, this is the message type that will be used when auto-creating the service. Setting this while dataservice is Non-Nil will remove the reference
  190. Property MessageType : TDAMessageType Read FMessageType Write SetMessageType;
  191. // if set, URL is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
  192. Property URL : String Read FURL Write SetURL;
  193. // DataServiceName is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
  194. Property DataserviceName : String Read FDataserviceName Write SetDataserviceName;
  195. // LoginServiceName is used to create a login service. Setting this while loginservice is Non-Nil will remove the reference
  196. Property LoginServiceName : String read FLoginServiceName write SetLoginServiceName;
  197. // Called when login call is executed.
  198. Property OnLogin : TDALoginSuccessEvent Read FOnLogin Write FOnLogin;
  199. // Called when login call failed. When call was executed but user is wrong OnLogin is called !
  200. Property OnLoginCallFailed : TLoginFailedEvent Read FOnLoginFailed Write FOnLoginFailed;
  201. // Called when logout call is executed.
  202. Property OnLogout : TDASuccessEvent Read FOnLogout Write FOnLogout;
  203. // Called when logout call failed.
  204. Property OnLogOutCallFailed : TDAFailedEvent Read FOnLogoutailed Write FOnLogoutFailed;
  205. // Streamertype : format of the data package in the message.
  206. Property StreamerType : TDAStreamerType Read FStreamerType Write FStreamerType;
  207. end;
  208. implementation
  209. uses strutils, sysutils;
  210. resourcestring
  211. SErrInvalidDate = '%s is not a valid date value for %s';
  212. { TDAArrayFieldMapper }
  213. procedure TDAArrayFieldMapper.RemoveField(const FieldName: String; FieldIndex: Integer; Row: JSValue);
  214. begin
  215. Inherited RemoveField(FieldName,FieldIndex,TDADataRow(Row)._new);
  216. end;
  217. procedure TDAArrayFieldMapper.SetJSONDataForField(const FieldName: String; FieldIndex: Integer; Row, Data: JSValue);
  218. begin
  219. Inherited SetJSONDataForField(FieldName,FieldIndex,TDADataRow(Row)._new,Data);
  220. end;
  221. function TDAArrayFieldMapper.GetJSONDataForField(const FieldName: String; FieldIndex: Integer; Row: JSValue): JSValue;
  222. begin
  223. Result:=Inherited GetJSONDataForField(FieldName,FieldIndex,TDADataRow(Row)._new);
  224. end;
  225. function TDAArrayFieldMapper.CreateRow: JSValue;
  226. begin
  227. Result:=TDADataRow.New;
  228. With TDADataRow(Result) do
  229. begin
  230. _new:=[];
  231. _old:=[];
  232. end;
  233. end;
  234. { TDAWhereClauseBuilder }
  235. class function TDAWhereClauseBuilder.NewBinaryExpression(aLeft, aRight: TDAExpression; anOp: TDABinaryOperator): TDAExpression;
  236. begin
  237. Result:=TDABinaryExpression.New(aLeft,aRight,BinaryOperatorNames[anOp]);
  238. end;
  239. class function TDAWhereClauseBuilder.NewBinaryExpression(aLeft: TDAExpression; anOp: TDABinaryOperator; const aValue: JSValue
  240. ): TDAExpression;
  241. begin
  242. Result:=TDABinaryExpression.New(aLeft,NewConstant(aValue),BinaryOperatorNames[anOp]);
  243. end;
  244. class function TDAWhereClauseBuilder.NewBinaryExpression(aLeft: TDAExpression; anOp: TDABinaryOperator; const aValue: JSValue;
  245. aType: TDADataType): TDAExpression;
  246. begin
  247. Result:=TDABinaryExpression.New(aLeft,NewConstant(aValue,aType),BinaryOperatorNames[anOp]);
  248. end;
  249. class function TDAWhereClauseBuilder.NewBinaryExpression(const aTableName, aFieldName: string; anOp: TDABinaryOperator;
  250. const aJSValue: JSValue; aType: TDADataType): TDAExpression;
  251. begin
  252. Result:=TDABinaryExpression.New(NewField(aTableName,aFieldName),NewConstant(aJSValue,aType),BinaryOperatorNames[anOp])
  253. end;
  254. class function TDAWhereClauseBuilder.NewBinaryExpression(const aTableName, aFieldName: string; anOp: TDABinaryOperator;
  255. const aJSValue: JSValue): TDAExpression;
  256. begin
  257. Result:=TDABinaryExpression.New(NewField(aTableName,aFieldName),NewConstant(aJSValue),BinaryOperatorNames[anOp])
  258. end;
  259. class function TDAWhereClauseBuilder.NewBinaryExpression(const aTableName, aFieldName: string; const aParameterName: string;
  260. aParameterType: TDADataType; anOp: TDABinaryOperator): TDAExpression;
  261. begin
  262. Result:=TDABinaryExpression.New(NewField(aTableName,aFieldName),NewParameter(aParameterName,aParameterType),BinaryOperatorNames[anOp])
  263. end;
  264. class function TDAWhereClauseBuilder.NewBinaryExpressionList(const aExpressions: array of TDAExpression; anOp: TDABinaryOperator): TDAExpression;
  265. var
  266. i, len: integer;
  267. begin
  268. len:=Length(aExpressions);
  269. Case Len of
  270. 0: Result:=nil;
  271. 1: Result:=aExpressions[0];
  272. else
  273. Result:=NewBinaryExpression(aExpressions[0],aExpressions[1],anOp);
  274. for i := 2 to Len-1 do
  275. Result:=NewBinaryExpression(Result,aExpressions[i],anOp);
  276. end;
  277. end;
  278. class function TDAWhereClauseBuilder.NewUnaryExpression(anExpression: TDAExpression; anOp: TDAUnaryOperator): TDAExpression;
  279. begin
  280. Result:=TDAUnaryExpression.New(anExpression,UnaryOperatorNames[anOp]);
  281. end;
  282. class function TDAWhereClauseBuilder.NewConstant(const aValue: jsValue): TDAExpression;
  283. begin
  284. Result:=TDAConstantExpression.New(JSValueToDataTypeName(aValue),aValue,Ord(IsNull(aValue)));
  285. end;
  286. class function TDAWhereClauseBuilder.NewDateTimeConstant(const aValue: TJSDate): TDAExpression; overload;
  287. begin
  288. Result:=TDAConstantExpression.New(DataTypeNames[datDateTime],Trunc(aValue.Time/1000),0);
  289. end;
  290. class function TDAWhereClauseBuilder.NewDateTimeConstant(const aValue: TDateTime): TDAExpression; overload;
  291. begin
  292. Result:=NewDateTimeConstant(DateTimeToJSDate(aValue));
  293. // Result:=TDAConstantExpression.New(DataTypeNames[datDateTime],DateTimeToJSDate(('yyyy"-"mm"-"dd"T"hh":"nn":"ss',aValue),0);
  294. end;
  295. class function TDAWhereClauseBuilder.NewConstant(const aValue: jsValue; aType: TDADataType): TDAExpression;
  296. begin
  297. Result:=TDAConstantExpression.New(JSValueToDataTypeName(aValue),aValue,Ord(IsNull(aValue)));
  298. end;
  299. class function TDAWhereClauseBuilder.NewList(const aValues: array of TDAExpression): TDAExpression;
  300. begin
  301. Result:=TDAListExpression.New(aValues);
  302. end;
  303. class function TDAWhereClauseBuilder.NewParameter(const aParameterName: string; aParameterType: TDADataType): TDAExpression;
  304. begin
  305. Result:=TDAParameterExpression.New(aParameterName,DataTypeNames[aParameterType],0);
  306. end;
  307. class function TDAWhereClauseBuilder.NewField(const aTableName, aFieldName: string): TDAExpression;
  308. var
  309. aName : String;
  310. begin
  311. aName:=aFieldName;
  312. if aTableName<>'' then
  313. aName:=aTableName+'.'+aName;
  314. Result:=TDAFieldExpression.New(aName);
  315. end;
  316. class function TDAWhereClauseBuilder.NewNull: TDAExpression;
  317. begin
  318. Result:=TDANullExpression.new;
  319. end;
  320. class function TDAWhereClauseBuilder.NewIsNotNull: TDAExpression;
  321. begin
  322. Result:=NewUnaryExpression(TDANullExpression.new,duoNot);
  323. end;
  324. class function TDAWhereClauseBuilder.NewIsNotNull(const aTableName, aFieldName: string): TDAExpression;
  325. begin
  326. Result:=NewBinaryExpression(NewField(aTableName,aFieldName),NewIsNotNull,dboEqual);
  327. end;
  328. class function TDAWhereClauseBuilder.NewIsNull(const aTableName,
  329. aFieldName: string): TDAExpression;
  330. begin
  331. Result:=NewBinaryExpression(NewField(aTableName,aFieldName),TDANullExpression.new,dboEqual);
  332. end;
  333. class function TDAWhereClauseBuilder.NewMacro(const aName: string): TDAExpression;
  334. begin
  335. Result:=TDAMacroExpression.New(aName);
  336. end;
  337. class function TDAWhereClauseBuilder.NewMacro(const aName: string; const aValues: array of TDAExpression): TDAExpression;
  338. begin
  339. Result:=TDAMacroExpression.New(aName); // ??
  340. end;
  341. class function TDAWhereClauseBuilder.NewBetweenExpression(aExpression, aLower, aUpper: TDAExpression): TDAExpression;
  342. begin
  343. Result:=TDABetweenExpression.New(aExpression,aLower,aUpper);
  344. end;
  345. class function TDAWhereClauseBuilder.NewBetweenExpression(const aExprTableName, aExprFieldName: string; aLower,
  346. aUpper: TDAExpression): TDAExpression;
  347. begin
  348. Result:=NewBetweenExpression(NewField(aExprTableName,aExprFieldName),aLower,aUpper);
  349. end;
  350. class function TDAWhereClauseBuilder.NewBetweenExpression(const aExprTableName, aExprFieldName: string; aLowerValue,
  351. aUpperValue: JSValue; aValuesDataType: TDADataType): TDAExpression;
  352. begin
  353. Result:=NewBetweenExpression(NewField(aExprTableName,aExprFieldName),
  354. NewConstant(aLowerValue,aValuesDataType),
  355. NewConstant(aUpperValue,aValuesDataType));
  356. end;
  357. class function TDAWhereClauseBuilder.GetWhereClause(aExpression: TDAExpression): String;
  358. Var
  359. DW : TDADynamicWhere;
  360. begin
  361. DW:=TDADynamicWhere.New(aExpression);
  362. try
  363. Result:=dw.toXml
  364. Finally
  365. DW:=Nil;
  366. end;
  367. end;
  368. { TDAConnection }
  369. function TDAConnection.GetDataService: TDADataAbstractService;
  370. begin
  371. if (FDataservice=Nil) then
  372. FDataservice:=CreateDataService;
  373. Result:=FDataService;
  374. end;
  375. function TDAConnection.GetLoginService: TDASimpleLoginService;
  376. begin
  377. if (FLoginService=Nil) then
  378. FLoginService:=CreateLoginService;
  379. Result:=FLoginService;
  380. end;
  381. function TDAConnection.GetMessage: TROMessage;
  382. begin
  383. CreateChannelAndMessage;
  384. Result:=FMessage;
  385. end;
  386. procedure TDAConnection.SetDataserviceName(AValue: String);
  387. begin
  388. if FDataserviceName=AValue then Exit;
  389. ClearConnection;
  390. FDataserviceName:=AValue;
  391. end;
  392. procedure TDAConnection.SetLoginServiceName(AValue: String);
  393. begin
  394. if FLoginServiceName=AValue then Exit;
  395. FLoginServiceName:=AValue;
  396. end;
  397. procedure TDAConnection.SetMessageType(AValue: TDAMessageType);
  398. begin
  399. if FMessageType=AValue then Exit;
  400. ClearConnection;
  401. FMessageType:=AValue;
  402. end;
  403. procedure TDAConnection.ClearConnection;
  404. begin
  405. FDataservice:=Nil;
  406. FChannel:=Nil;
  407. FMessage:=Nil;
  408. end;
  409. function TDAConnection.GetChannel: TROHTTPClientChannel;
  410. begin
  411. CreateChannelAndMessage;
  412. Result:=FChannel;
  413. end;
  414. function TDAConnection.GetClientID: String;
  415. begin
  416. if Assigned(FMessage) then
  417. Result:=FMessage.ClientID
  418. else
  419. Result:='';
  420. end;
  421. procedure TDAConnection.SetURL(AValue: String);
  422. begin
  423. if FURL=AValue then Exit;
  424. ClearConnection;
  425. FURL:=AValue;
  426. end;
  427. procedure TDAConnection.CreateChannelAndMessage;
  428. begin
  429. if (FChannel=Nil) then
  430. FChannel:=TROHTTPClientChannel.New(URL);
  431. if (FMessage=Nil) then
  432. Case EnsureMessageType of
  433. mtBin : fMessage:=TROBINMessage.New;
  434. mtJSON : fMessage:=TROJSONMessage.New;
  435. end;
  436. end;
  437. function TDAConnection.DetectMessageType(const aURL: String): TDAMessageType;
  438. Var
  439. S : String;
  440. begin
  441. S:=aURL;
  442. Delete(S,1,RPos('/',S));
  443. case lowercase(S) of
  444. 'bin' : Result:=mtBin;
  445. 'json' : Result:=mtJSON;
  446. else
  447. Raise EDADataset.Create(Name+': Could not determine message type from URL: '+aURL);
  448. end;
  449. end;
  450. function TDAConnection.CreateDataService: TDADataAbstractService;
  451. begin
  452. Result:=Nil;
  453. if URL='' then exit;
  454. CreateChannelAndMessage;
  455. Result:=TDADataAbstractService.New(FChannel,FMessage,DataServiceName);
  456. end;
  457. function TDAConnection.CreateLoginService: TDASimpleLoginService;
  458. begin
  459. Result:=Nil;
  460. if URL='' then exit;
  461. CreateChannelAndMessage;
  462. Result:=TDASimpleLoginService.New(FChannel,FMessage,LoginServiceName);
  463. end;
  464. function TDAConnection.CreateStreamer: TDADataStreamer;
  465. begin
  466. Case StreamerType of
  467. stJSON : Result:=TDAJSONDataStreamer.new;
  468. stBIN: Result:=TDABIN2DataStreamer.new;
  469. end;
  470. end;
  471. function TDAConnection.InterpretMessage(aRes: JSValue): String;
  472. begin
  473. Result:=String(aRes);
  474. if (EnsureMessageType=mtJSON) then
  475. Result:=TROUtil.Frombase64(Result);
  476. end;
  477. constructor TDAConnection.create(aOwner: TComponent);
  478. begin
  479. inherited create(aOwner);
  480. FDataServiceName:='DataService';
  481. FLoginServiceName:='LoginService';
  482. end;
  483. destructor TDAConnection.Destroy;
  484. begin
  485. ClearConnection;
  486. inherited Destroy;
  487. end;
  488. function TDAConnection.EnsureMessageType: TDAMessageType;
  489. begin
  490. Result:=MessageType;
  491. if Result=mtAuto then
  492. Result:=DetectMessageType(URL);
  493. end;
  494. function TDAConnection.EnsureDataservice: TDADataAbstractService;
  495. begin
  496. Result:=Dataservice;
  497. if (Result=Nil) then
  498. Raise EDADataset.Create('No data service available. ');
  499. end;
  500. function TDAConnection.EnsureLoginservice: TDASimpleLoginService;
  501. begin
  502. Result:=LoginService;
  503. if (Result=Nil) then
  504. Raise EDADataset.Create('No login service available. ');
  505. end;
  506. procedure TDAConnection.DoLoginFailed(Msg : TROMessage; aErr : TJSError);
  507. Var
  508. ErrMsg : String;
  509. begin
  510. if Assigned(FonLoginFailed) then
  511. begin
  512. if IsObject(aErr) then
  513. if TJSObject(aErr).HasOwnProperty('message') then
  514. ErrMsg:=aErr.Message
  515. else
  516. ErrMsg:='Error object: '+TJSJSON.Stringify(aErr)
  517. else if IsString(aErr) then
  518. ErrMsg:=String(JSValue(aErr))
  519. else
  520. ErrMsg:='Unknown error';
  521. FOnLoginFailed(Msg,errMsg);
  522. end;
  523. end;
  524. procedure TDAConnection.Login(aUserName, aPassword: String);
  525. begin
  526. EnsureLoginService.Login(aUserName,aPassword,FOnLogin,@DoLoginFailed);
  527. end;
  528. procedure TDAConnection.LoginEx(aLoginString: String);
  529. begin
  530. EnsureLoginService.LoginEx(aLoginString,FOnLogin,@DoLoginFailed);
  531. end;
  532. procedure TDAConnection.Logout;
  533. begin
  534. EnsureLoginService.Logout(FOnLogout,FOnLogoutFailed);
  535. end;
  536. { TDADataset }
  537. function TDADataset.DataTypeToFieldType(s : String) : TFieldType;
  538. Const
  539. FieldStrings : Array [TFieldType] of string = (
  540. '','String', 'Integer', 'LargeInt', 'Boolean', 'Float', 'Date',
  541. 'Time', 'DateTime', 'AutoInc', 'Blob', 'Memo', 'FixedChar',
  542. 'Variant','Dataset');
  543. begin
  544. if (Copy(S,1,3)='dat') then
  545. system.Delete(S,1,3);
  546. Result:=High(TFieldType);
  547. While (Result>ftUnknown) and Not SameText(FieldStrings[Result],S) do
  548. Result:=Pred(Result);
  549. if Result=ftUnknown then
  550. case LowerCase(s) of
  551. 'widememo',
  552. 'widestring' : result:=ftString;
  553. 'currency' : result:=ftFloat;
  554. 'decimal' : result:=ftFloat;
  555. 'smallint' : result:=ftInteger;
  556. 'largeautoinc' : result:=ftLargeInt;
  557. else
  558. writeln('Unknown field type:',S)
  559. end;
  560. end;
  561. procedure TDADataset.SetParams(AValue: TParams);
  562. begin
  563. if FParams=AValue then Exit;
  564. FParams.Assign(AValue);
  565. end;
  566. function TDADataset.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
  567. Var
  568. rIdx,I : Integer;
  569. Fld : TField;
  570. ResRow : TResolvedRow;
  571. aRow : JSValue;
  572. begin
  573. Result:=True;
  574. if Assigned(anupDate.ServerData) and (anUpdate.Status<>usDeleted) then
  575. begin
  576. rIdx:=NativeInt(anUpdate.Bookmark.Data);
  577. if Not (rIdx>=0) and (rIdx<Rows.Length) then
  578. exit;
  579. // Apply new values
  580. aRow:=Rows[rIdx];
  581. ResRow:=TResolvedRow(anUpdate.ServerData);
  582. With ResRow do
  583. for I:=0 to Length(Fields)-1 do
  584. if (doRefreshAllFields in DAOptions) or (Changes.old[I]<>Changes.new_[I]) then
  585. begin
  586. Fld:=FieldByName(Fields[i].Name);
  587. FieldMapper.SetJSONDataForField(Fld,aRow,Changes.New_[i]);
  588. end;
  589. TDADataRow(aRow)._old:=[];
  590. end;
  591. end;
  592. function TDADataset.ConvertToDateTime(aField: TField; aValue: JSValue; ARaiseException: Boolean): TDateTime;
  593. begin
  594. Result:=0;
  595. if isDate(aValue) then
  596. Result:=JSDateToDateTime(TJSDate(aValue))
  597. else if isString(aValue) then
  598. Result:=Inherited ConvertToDateTime(afield,aValue,ARaiseException)
  599. else
  600. if aRaiseException then
  601. DatabaseErrorFmt(SErrInvalidDate,[String(aValue),aField.FieldName],Self);
  602. end;
  603. function TDADataset.ConvertDateTimeToNative(aField: TField; aValue: TDateTime): JSValue;
  604. begin
  605. Result:=DateTimeToJSDate(aValue);
  606. end;
  607. procedure TDADataset.MetaDataToFieldDefs;
  608. begin
  609. if Not isArray(Metadata['fields']) then
  610. exit;
  611. CreateFieldDefs(TJSArray(Metadata['fields']));
  612. end;
  613. procedure TDADataset.InternalEdit;
  614. Var
  615. D : TDADataRow;
  616. begin
  617. Inherited;
  618. D:=TDADataRow(ActiveBuffer.Data);
  619. if Length(D._old)=0 then
  620. begin
  621. asm
  622. this.FEditRow._old=this.FEditRow._new.slice();
  623. end;
  624. end;
  625. if Not isDefined(D._new) then
  626. ActiveBuffer.Data:=FieldMapper.CreateRow
  627. end;
  628. procedure TDADataset.InternalDelete;
  629. begin
  630. inherited InternalDelete;
  631. asm
  632. var len=this.FDeletedRows.length-1;
  633. this.FDeletedRows[len]._old=this.FDeletedRows[len]._new.slice();
  634. end;
  635. end;
  636. function TDADataset.GetDAFields: TDAFieldArray;
  637. begin
  638. if Assigned(metadata) and Metadata.HasOwnProperty('fields') and isArray(MetaData['fields']) then
  639. Result:=TDAFieldArray(Metadata['fields'])
  640. else
  641. Result:=nil;
  642. end;
  643. function TDADataset.GetExcludedFields: TNativeIntDynArray;
  644. Var
  645. Flds : TDAFieldArray;
  646. I : Integer;
  647. begin
  648. Result:=[];
  649. Flds:=GetDaFields;
  650. For I:=0 to Length(Flds)-1 do
  651. if not Flds[i].logChanges then
  652. TJSArray(Result).Push(i);
  653. end;
  654. function TDADataset.GetKeyFields: TStringDynArray;
  655. Var
  656. Flds : TDAFieldArray;
  657. I,aLen : Integer;
  658. begin
  659. Result:=[];
  660. Flds:=GetDaFields;
  661. aLen:=0;
  662. SetLength(Result,Length(Flds));
  663. For I:=0 to Length(Flds)-1 do
  664. begin
  665. if Flds[i].HasOwnProperty('inPrimaryKey') and SameText(Flds[i].inPrimaryKey,'True') then
  666. begin
  667. Result[aLen]:=Flds[i].Name;
  668. Inc(aLen);
  669. end;
  670. end;
  671. SetLength(Result,aLen);
  672. end;
  673. function TDADataset.GetLoggedFields: TLogFieldArray;
  674. Var
  675. Flds : TDAFieldArray;
  676. I,aLen : Integer;
  677. begin
  678. Result:=[];
  679. Flds:=GetDaFields;
  680. aLen:=0;
  681. SetLength(Result,Length(Flds));
  682. For I:=0 to Length(Flds)-1 do
  683. begin
  684. if Flds[i].logChanges then
  685. begin
  686. Result[aLen].name:=Flds[i].Name;
  687. Result[aLen].datatype:=Flds[i].type_;
  688. Inc(aLen);
  689. end;
  690. end;
  691. SetLength(Result,aLen);
  692. end;
  693. function TDADataset.DoGetDataProxy: TDataProxy;
  694. begin
  695. Result:=TDADataProxy.Create(Self);
  696. TDADataProxy(Result).Connection:=DAConnection;
  697. end;
  698. function TDADataset.ParamByName(const aName: string): TParam;
  699. begin
  700. Result:=FParams.ParamByname(aName);
  701. end;
  702. function TDADataset.FindParam(const aName: string): TParam;
  703. begin
  704. Result:=FParams.FindParam(aName);
  705. end;
  706. constructor TDADataset.create(aOwner: TComponent);
  707. begin
  708. inherited;
  709. DataProxy:=nil;
  710. FParams:=TParams.Create(Self);
  711. FWhereClauseBuilder:=TDAWhereClauseBuilder.Create;
  712. end;
  713. destructor TDADataset.Destroy;
  714. begin
  715. FreeAndNil(FWhereClauseBuilder);
  716. FreeAndNil(FParams);
  717. Inherited;
  718. end;
  719. procedure TDADataset.CreateFieldDefs(a: TJSArray);
  720. Var
  721. I : Integer;
  722. F : TDAField;
  723. FO : TJSObject absolute F;
  724. fn,dt : string;
  725. fs : Integer;
  726. FT : TFieldType;
  727. req : boolean;
  728. begin
  729. FieldDefs.Clear;
  730. For I:=0 to A.length-1 do
  731. begin
  732. F:=TDAField(A.Elements[i]);
  733. fn:=F.Name;
  734. // The JSON streamer does not create all properties :(
  735. if FO.hasOwnProperty('size') then
  736. begin
  737. if isString(FO['size']) then
  738. fs:=StrToInt(String(FO['size']))
  739. else if isNumber(FO['size']) then
  740. fs:=F.Size
  741. else
  742. fs:=0;
  743. end
  744. else
  745. fs:=0;
  746. if FO.hasOwnProperty('type') then
  747. dt:=F.type_
  748. else
  749. dt:='string';
  750. if FO.hasOwnProperty('required') then
  751. req:=F.Required
  752. else
  753. Req:=false;
  754. Ft:=DataTypeToFieldType(dT);
  755. if (ft=ftBlob) and (fs=0) then
  756. fs:=1;
  757. // Writeln('FieldDef : ',fn,', ',ft,', ',fs);
  758. FieldDefs.Add(fn,ft,fs,Req);
  759. end;
  760. end;
  761. function TDADataset.CreateFieldMapper: TJSONFieldMapper;
  762. begin
  763. Result := TDAArrayFieldMapper.Create;
  764. end;
  765. { TDADataProxy }
  766. function TDADataProxy.ConvertParams(DADS : TDADataset) : TDADataParameterDataArray;
  767. Var
  768. I : integer;
  769. begin
  770. Result:=Nil;
  771. if DADS.Params.Count=0 then
  772. Exit;
  773. SetLength(Result,DADS.Params.Count);
  774. for I:=0 to DADS.Params.Count-1 do
  775. begin
  776. Result[i].Name:=DADS.Params[i].Name;
  777. Result[i].Value:=DADS.Params[i].Value;
  778. // Writeln('Set param ',Result[i].Name,' to ',Result[i].Value);
  779. end;
  780. end;
  781. function TDADataProxy.DoGetData(aRequest: TDataRequest): Boolean;
  782. Var
  783. TN : TDAStringArray;
  784. TIA : TDATableRequestInfoArray;
  785. TID : TDATableRequestInfoV5Data;
  786. TI : TDATableRequestInfoV5;
  787. Srt : TDAColumnSortingData;
  788. R : TDADataRequest;
  789. DADS : TDADataset;
  790. PA : TDADataParameterDataArray;
  791. DS : TDADataAbstractService;
  792. begin
  793. // DA does not support this option...
  794. if loAtEOF in aRequest.LoadOptions then
  795. exit(False);
  796. DADS:=aRequest.Dataset as TDADataset;
  797. R:=aRequest as TDADatarequest;
  798. if (Connection=Nil) then
  799. Raise EDADataset.Create(DADS.Name+': Cannot get data without connection');
  800. if (DADS.TableName='') then
  801. Raise EDADataset.Create(DADS.Name+': Cannot get data without tablename');
  802. DS:=Connection.EnsureDataservice;
  803. TN:=TDAStringArray.New;
  804. TN.fromObject([DADS.TableName]);
  805. TID.maxRecords:=-1;
  806. TID.IncludeSchema:=True;
  807. Srt.FieldName:='';
  808. Srt.SortDirection:='Ascending';
  809. TID.Sorting:=Srt;
  810. TID.UserFilter:='';
  811. if DADS.WhereClause<>'' then
  812. TID.WhereClause:=DADS.WhereClause;
  813. PA:=ConvertParams(DADS);
  814. if Length(PA)>0 then
  815. TID.Parameters:=Pa;
  816. TIA:=TDATableRequestInfoArray.new;
  817. // We need to manually fill the array
  818. TI:=TDATableRequestInfoV5.New;
  819. TI.FromObject(TID);
  820. TJSArray(TIA.items).push(TI);
  821. DS.GetData(TN,TIA,@R.doSuccess,@R.doFail);
  822. Result:=True;
  823. end;
  824. function TDADataProxy.GetDataRequestClass: TDataRequestClass;
  825. begin
  826. Result:=TDADataRequest;
  827. end;
  828. procedure TDADataProxy.ProcessUpdateResult(Res: JSValue;aBatch: TRecordUpdateBatch);
  829. Var
  830. I : Integer;
  831. aDelta : TDADelta;
  832. aDeltas : TDADeltas;
  833. aStreamer : TDADataStreamer;
  834. C : TDaChange;
  835. ResolvedRow : TResolvedRow;
  836. begin
  837. aStreamer:=Connection.CreateStreamer;
  838. aStreamer.Stream:=Connection.InterpretMessage(Res);
  839. aStreamer.initializeRead;
  840. aDeltas:=aStreamer.ReadDelta;
  841. if Length(aDeltas.deltas)>1 then
  842. begin
  843. For I:=0 to aBatch.List.Count-1 do
  844. aBatch.List[i].ResolveFailed('More than 1 delta in result');
  845. Exit;
  846. end;
  847. aDelta:=aDeltas.Deltas[0];
  848. For C in aDelta.data do
  849. begin
  850. Case C.Status of
  851. 'failed' :
  852. aBatch.List[C.recid].ResolveFailed(C.Message);
  853. 'resolved':
  854. begin
  855. ResolvedRow:=TResolvedRow.new;
  856. ResolvedRow.changes:=C;
  857. ResolvedRow.fields:=aDelta.LoggedFields;
  858. aBatch.List[C.recid].Resolve(ResolvedRow);
  859. end;
  860. end;
  861. end;
  862. For I:=0 to aBatch.List.Count-1 do
  863. if aBatch.List[i].ResolveStatus=rsResolving then
  864. aBatch.List[i].Resolve(Null);
  865. If Assigned(aBatch.OnResolve) then
  866. ABatch.OnResolve(Self,aBatch);
  867. end;
  868. function TDADataProxy.ProcessUpdateBatch(aBatch: TRecordUpdateBatch): Boolean;
  869. Procedure UpdateSuccess(res : JSValue);
  870. begin
  871. ProcessUpdateResult(Res,aBatch)
  872. end;
  873. Procedure UpdateFailure(response : TROMessage; Err : TJSError);
  874. var
  875. I : Integer;
  876. aDesc : TRecordUpdateDescriptor;
  877. begin
  878. For I:=0 to aBatch.List.Count-1 do
  879. begin
  880. aDesc:=aBatch.List[i];
  881. aDesc.ResolveFailed(extractErrorMsg(Err));
  882. end;
  883. If Assigned(aBatch.OnResolve) then
  884. ABatch.OnResolve(Self,aBatch);
  885. end;
  886. Procedure ExcludeItems(aList : TNativeIntDynArray; aValue : TJSValueDynArray);
  887. Var
  888. I : Integer;
  889. begin
  890. // Backwards or index will shift with each delete!
  891. for I:=Length(aList)-1 downto 1 do
  892. TJSArray(aValue).Splice(aList[I],1);
  893. end;
  894. Const
  895. ChangeTypes : Array[TUpdateStatus] of String = ('update','insert','delete');
  896. Var
  897. lDataset : TDaDataset;
  898. excludedFields : TNativeIntDynArray;
  899. I : Integer;
  900. aDesc : TRecordUpdateDescriptor;
  901. aDelta : TDADelta;
  902. aDeltas : TDADeltas;
  903. aChange : TDAChange;
  904. DS : TDADataAbstractService;
  905. DStr : TDADataStreamer;
  906. S : String;
  907. begin
  908. lDataset:=TDADataset(aBatch.Dataset);
  909. aDeltas:=TDADeltas.New;
  910. aDelta:=TDADelta.New;
  911. aDelta.Name:=lDataset.TableName;
  912. aDelta.keyFields:=lDataset.GetKeyFields;
  913. aDelta.loggedFields:=lDataset.GetLoggedFields;
  914. excludedFields:=lDataset.GetExcludedFields;
  915. TJSArray(aDeltas.deltas).Push(aDelta);
  916. For I:=0 to aBatch.List.Count-1 do
  917. begin
  918. aDesc:=aBatch.List[i];
  919. aChange:=TDaChange.New;
  920. aChange.Status:='pending';
  921. if aDesc.Status=usInserted then
  922. aChange.old:=TJSValueDynArray(TJSArray(TDADataRow(aDesc.Data)._new).Slice())
  923. else
  924. aChange.old:=TJSValueDynArray(TJSArray(TDADataRow(aDesc.Data)._old).Slice());
  925. aChange.new_:=TJSValueDynArray(TJSArray(TDADataRow(aDesc.Data)._new).Slice());
  926. excludeItems(ExcludedFields,aChange.new_);
  927. excludeItems(ExcludedFields,aChange.old);
  928. aChange.changeType:=ChangeTypes[aDesc.Status];
  929. aChange.recid:=I;
  930. TJSArray(aDelta.data).push(aChange);
  931. end;
  932. DStr:=Connection.CreateStreamer;
  933. DStr.initializeWrite;
  934. DStr.writeDelta(aDeltas);
  935. DStr.finalizeWrite;
  936. S:=DStr.Stream;
  937. DS:=Connection.EnsureDataservice;
  938. DS.UpdateData(S,@UpdateSuccess,@UpdateFailure);
  939. Result:=True;
  940. end;
  941. { TDADataRequest }
  942. procedure TDADataRequest.DoFail(response: TROMessage; fail: TJSError);
  943. Var
  944. O : TJSOBject;
  945. S : TStringDynArray;
  946. Msg : String;
  947. I : Integer;
  948. begin
  949. if isObject(fail) then
  950. begin
  951. O:=TJSOBject(JSValue(fail));
  952. S:=TJSObject.getOwnPropertyNames(O);
  953. for I:=0 to Length(S)-1 do
  954. begin
  955. msg:=Msg+sLineBreak+S[i];
  956. Msg:=Msg+' : '+String(O[S[i]]);
  957. end;
  958. end
  959. else
  960. Msg:=TJSJSON.Stringify(Fail);
  961. Success:=rrFail;
  962. ErrorMsg:=Msg;
  963. DoAfterRequest;
  964. end;
  965. procedure TDADataRequest.doSuccess(res: JSValue);
  966. Var
  967. S : String;
  968. Rows : TDADataRowArray;
  969. aRow : TDADataRow;
  970. DADS : TDADataset;
  971. DStr : TDADataStreamer;
  972. DT : TDADatatable;
  973. I : Integer;
  974. begin
  975. // Writeln('Data loaded, dataset active: ',Dataset.Active);
  976. DADS:=Dataset as TDADataset;
  977. if not Assigned(DADS.DAConnection) then
  978. Raise EDADataset.Create(DADS.Name+': Cannot process response, connection not available');
  979. DStr:=DADS.DAConnection.CreateStreamer;
  980. S:=DADS.DAConnection.InterpretMessage(Res);
  981. DStr.Stream:=S;
  982. DStr.initializeRead;
  983. DT:=TDADataTable.New;
  984. DT.name:=DADS.TableName;
  985. DStr.ReadDataset(DT);
  986. // Writeln('Row count : ',Length(DT.rows));
  987. SetLength(Rows,Length(DT.rows));
  988. for I:=0 to length(DT.rows)-1 do
  989. begin
  990. aRow:=TDADataRow.New;
  991. aRow._new:=TJSValueDynArray(DT.Rows[i].__newValues);
  992. aRow._old:=[];
  993. Rows[i]:=aRow;
  994. end;
  995. (Dataset as TDADataset).Metadata:=New(['fields',TJSArray(DT.Fields)]);
  996. // Data:=aJSON['data'];
  997. (Dataset as TDADataset).Rows:=TJSArray(Rows);
  998. Success:=rrOK;
  999. DoAfterRequest;
  1000. end;
  1001. end.