sqldbrestdata.pp 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by the Free Pascal development team
  4. SQLDB REST data manipulation routines.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit sqldbrestdata;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, sqldb, db, fpjson, sqldbrestio, sqldbrestschema;
  16. Type
  17. TSQLQueryClass = Class of TSQLQuery;
  18. TRestFilterPair = Record
  19. Field : TSQLDBRestField;
  20. Operation : TRestFieldFilter;
  21. ValueParam : TParam;
  22. Value : String;
  23. end;
  24. TRestFilterPairArray = Array of TRestFilterPair;
  25. { TSQLDBRestDBHandler }
  26. TSQLDBRestDBHandlerOption = (rhoLegacyPut,rhoCheckupdateCount,rhoAllowMultiUpdate);
  27. TSQLDBRestDBHandlerOptions = set of TSQLDBRestDBHandlerOption;
  28. TSQLDBRestDBHandler = Class(TComponent)
  29. private
  30. FDeriveResourceFromDataset: Boolean;
  31. FEmulateOffsetLimit: Boolean;
  32. FEnforceLimit: Int64;
  33. FExternalDataset: TDataset;
  34. FOptions: TSQLDBRestDBHandlerOptions;
  35. FPostParams: TParams;
  36. FQueryClass: TSQLQueryClass;
  37. FRestIO: TRestIO;
  38. FStrings : TRestStringsConfig;
  39. FResource : TSQLDBRestResource;
  40. FOwnsResource : Boolean;
  41. procedure CheckAllRequiredFieldsPresent;
  42. function GetAllowMultiUpdate: Boolean;
  43. function GetCheckUpdateCount: Boolean;
  44. function GetUseLegacyPUT: Boolean;
  45. procedure SetExternalDataset(AValue: TDataset);
  46. Protected
  47. function StreamRecord(O: TRestOutputStreamer; D: TDataset; FieldList: TRestFieldPairArray): Boolean; virtual;
  48. function FindExistingRecord(D: TDataset): Boolean;
  49. function GetRequestFields: TSQLDBRestFieldArray;
  50. procedure CreateResourceFromDataset(D: TDataset); virtual;
  51. procedure DoNotFound; virtual;
  52. procedure SetPostParams(aParams: TParams; Old : TFields = Nil);virtual;
  53. procedure SetPostFields(aFields: TFields);virtual;
  54. procedure SetFieldFromData(DataField: TField; ResField: TSQLDBRestField; D: TJSONData); virtual;
  55. procedure InsertNewRecord; virtual;
  56. procedure UpdateExistingRecord(OldData: TDataset; IsPatch : Boolean); virtual;
  57. Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  58. function SpecialResource: Boolean; virtual;
  59. function GetGeneratorValue(const aGeneratorName: String): Int64; virtual;
  60. function GetSpecialDatasetForResource(aFieldList: TRestFieldPairArray): TDataset; virtual;
  61. function FindFieldForParam(aOperation: TRestOperation; P: TParam): TSQLDBRestField; virtual;
  62. function BuildFieldList(ForceAll : Boolean): TRestFieldPairArray; virtual;
  63. function CreateQuery(aSQL: String): TSQLQuery; virtual;
  64. procedure FillParams(aOperation: TRestOperation; aParams: TParams;
  65. FilteredFields: TRestFilterPairArray); virtual;
  66. function GetDatasetForResource(aFieldList: TRestFieldPairArray; Singleton : Boolean): TDataset; virtual;
  67. function GetOrderByFieldArray: TRestFieldOrderPairArray;
  68. function GetOrderBy: UTF8String;virtual;
  69. function GetIDWhere(Out FilteredFields : TRestFilterPairArray): UTF8String; virtual;
  70. function GetWhere(Out FilteredFields : TRestFilterPairArray): UTF8String; virtual;
  71. function GetLimit: UTF8String;
  72. // Handle 4 basic operations
  73. procedure DoHandleGet;virtual;
  74. procedure DoHandleDelete;virtual;
  75. procedure DoHandlePost;virtual;
  76. procedure DoHandlePutPatch(IsPatch : Boolean); virtual;
  77. procedure DoHandlePut; virtual;
  78. procedure DoHandlePatch; virtual;
  79. // Parameters used when executing update SQLs. Used to get values for return dataset params.
  80. Property PostParams : TParams Read FPostParams;
  81. Property UseLegacyPUT : Boolean Read GetUseLegacyPUT;
  82. Property CheckUpdateCount : Boolean Read GetCheckUpdateCount;
  83. Property AllowMultiUpdate : Boolean Read GetAllowMultiUpdate;
  84. Public
  85. Destructor Destroy; override;
  86. // Get limi
  87. Function GetLimitOffset(out aLimit, aOffset: Int64) : Boolean; virtual;
  88. Procedure Init(aIO: TRestIO; aStrings : TRestStringsConfig;AQueryClass : TSQLQueryClass); virtual;
  89. Procedure ExecuteOperation;
  90. Function StreamDataset(O: TRestOutputStreamer; D: TDataset; FieldList: TRestFieldPairArray; CurrentOnly : Boolean = False) : Int64;
  91. procedure SetParamFromData(P: TParam; F: TSQLDBRestField; D: TJSONData); virtual;
  92. function GetDataForParam(P: TParam; F: TSQLDBRestField; Sources : TVariableSources = AllVariableSources): TJSONData; virtual;
  93. Function GetString(aString : TRestStringProperty) : UTF8String;
  94. Property IO : TRestIO Read FRestIO;
  95. Property Strings : TRestStringsConfig Read FStrings;
  96. Property QueryClass : TSQLQueryClass Read FQueryClass;
  97. Property EnforceLimit : Int64 Read FEnforceLimit Write FEnforceLimit;
  98. Property ExternalDataset : TDataset Read FExternalDataset Write SetExternalDataset;
  99. Property EmulateOffsetLimit : Boolean Read FEmulateOffsetLimit Write FEmulateOffsetLimit;
  100. Property DeriveResourceFromDataset : Boolean Read FDeriveResourceFromDataset Write FDeriveResourceFromDataset;
  101. Property Options : TSQLDBRestDBHandlerOptions Read FOptions Write FOptions;
  102. end;
  103. TSQLDBRestDBHandlerClass = class of TSQLDBRestDBHandler;
  104. implementation
  105. uses strutils, variants, dateutils, base64, sqldbrestconst;
  106. Const
  107. FilterParamPrefix : Array [TRestFieldFilter] of string = ('eq_','lt_','gt_','lte_','gte_','');
  108. FilterOps : Array [TRestFieldFilter] of string = ('=','<','>','<=','>=','IS NULL');
  109. { TSQLDBRestDBHandler }
  110. procedure TSQLDBRestDBHandler.Init(aIO: TRestIO; aStrings: TRestStringsConfig; AQueryClass: TSQLQueryClass);
  111. begin
  112. FRestIO:=aIO;
  113. FQueryClass:=aQueryClass;
  114. FStrings:=aStrings;
  115. end;
  116. procedure TSQLDBRestDBHandler.ExecuteOperation;
  117. begin
  118. if Not DeriveResourceFromDataset then
  119. FResource:=IO.Resource;
  120. Case IO.Operation of
  121. roGet : DoHandleGet;
  122. roPut : DoHandlePut;
  123. roPatch : DoHandlePatch;
  124. roPost : DoHandlePost;
  125. roDelete : DoHandleDelete;
  126. else
  127. ;
  128. end;
  129. end;
  130. function TSQLDBRestDBHandler.GetString(aString: TRestStringProperty): UTF8String;
  131. begin
  132. if Assigned(FStrings) then
  133. Result:=FStrings.GetRestString(aString)
  134. else
  135. Result:=TRestStringsConfig.GetDefaultString(aString);
  136. end;
  137. function TSQLDBRestDBHandler.GetIDWhere(out FilteredFields: TRestFilterPairArray): UTF8String;
  138. Var
  139. Qry : UTF8String;
  140. L : TSQLDBRestFieldArray;
  141. F: TSQLDBRestField;
  142. I : Integer;
  143. begin
  144. FilteredFields:=Default(TRestFilterPairArray);
  145. Result:='';
  146. if (IO.GetVariable('ID',Qry,[vsQuery,vsRoute,vsHeader])=vsNone) or (Qry='') then
  147. if not Assigned(PostParams) then
  148. raise ESQLDBRest.Create(IO.RestStatuses.GetStatusCode(rsInvalidParam),SErrNoKeyParam);
  149. L:=FResource.GetFieldArray(flWhereKey);
  150. SetLength(FilteredFields,Length(L));
  151. for I:=0 to Length(L)-1 do
  152. begin
  153. F:=L[i];
  154. FilteredFields[I].Field:=F;
  155. FilteredFields[I].Operation:=rfEqual;
  156. // If we have postparams, it means we're building a dataset for return data.
  157. // So check for actual DB value there
  158. if Assigned(PostParams) then
  159. FilteredFields[I].ValueParam:=PostParams.FindParam(F.FieldName);
  160. if (FilteredFields[I].ValueParam=nil) then
  161. FilteredFields[I].Value:=ExtractWord(1,Qry,['|']);
  162. If (Result<>'') then
  163. Result:=Result+' and ';
  164. Result:='('+F.FieldName+' = :'+FilterParamPrefix[rfEqual]+F.FieldName+')';
  165. end;
  166. end;
  167. function TSQLDBRestDBHandler.GetWhere(out FilteredFields: TRestFilterPairArray
  168. ): UTF8String;
  169. Const
  170. MaxFilterCount = 1+ Ord(High(TRestFieldFilter)) - Ord(Low(TRestFieldFilter));
  171. Var
  172. Qry : UTF8String;
  173. L : TSQLDBRestFieldArray;
  174. RF : TSQLDBRestField;
  175. fo : TRestFieldFilter;
  176. aLen : integer;
  177. begin
  178. FilteredFields:=Default(TRestFilterPairArray);
  179. Result:='';
  180. L:=FResource.GetFieldArray(flFilter);
  181. SetLength(FilteredFields,Length(L)*MaxFilterCount);
  182. aLen:=0;
  183. for RF in L do
  184. for FO in RF.Filters do
  185. if IO.GetFilterVariable(RF.PublicName,FO,Qry)<>vsNone then
  186. begin
  187. FilteredFields[aLen].Field:=RF;
  188. FilteredFields[aLen].Operation:=FO;
  189. FilteredFields[aLen].Value:=Qry;
  190. Inc(aLen);
  191. If (Result<>'') then Result:=Result+' AND ';
  192. if FO<>rfNull then
  193. Result:=Result+Format('(%s %s :%s%s)',[RF.FieldName,FilterOps[FO],FilterParamPrefix[FO],RF.FieldName])
  194. else
  195. Case IO.StrToNullBoolean(Qry,True) of
  196. nbTrue : Result:=Result+Format('(%s IS NULL)',[RF.FieldName]);
  197. nbFalse : Result:=Result+Format('(%s IS NOT NULL)',[RF.FieldName]);
  198. nbNone : Raise ESQLDBRest.CreateFmt(IO.RestStatuses.GetStatusCode(rsInvalidParam),SErrInvalidBooleanForField,[RF.PublicName])
  199. end;
  200. end;
  201. SetLength(FilteredFields,aLen);
  202. end;
  203. function TSQLDBRestDBHandler.GetOrderByFieldArray : TRestFieldOrderPairArray;
  204. Procedure AddField(Idx : Integer; F : TSQLDBRestField; aDesc : boolean);
  205. begin
  206. Result[Idx].RestField:=F;
  207. Result[Idx].Desc:=aDesc;
  208. end;
  209. Var
  210. L : TSQLDBRestFieldArray;
  211. I,J,aLen : Integer;
  212. F : TSQLDBRestField;
  213. V,FN : UTF8String;
  214. Desc : Boolean;
  215. begin
  216. Result:=Default(TRestFieldOrderPairArray);
  217. if IO.GetVariable(GetString(rpOrderBy),V,[vsQuery])=vsNone then
  218. begin
  219. L:=FResource.GetFieldArray(flWhereKey);
  220. SetLength(Result,Length(L));
  221. I:=0;
  222. For F in L do
  223. begin
  224. AddField(I,F,False);
  225. Inc(I);
  226. end
  227. end
  228. else
  229. begin
  230. L:=FResource.GetFieldArray(flOrderBy);
  231. aLen:=WordCount(V,[',']);
  232. SetLength(Result,aLen);
  233. For I:=1 to WordCount(V,[',']) do
  234. begin
  235. FN:=ExtractWord(I,V,[',']);
  236. Desc:=SameText(ExtractWord(2,FN,[' ']),'desc');
  237. FN:=ExtractWord(1,FN,[' ']);
  238. J:=Length(L)-1;
  239. While (J>=0) and Not SameText(L[J].PublicName,FN) do
  240. Dec(J);
  241. if J<0 then
  242. Raise ESQLDBRest.CreateFmt(IO.RestStatuses.GetStatusCode(rsInvalidParam),SErrInvalidSortField,[FN]);
  243. F:=L[J];
  244. if Desc then
  245. if not (foOrderByDesc in F.Options) then
  246. Raise ESQLDBRest.CreateFmt(IO.RestStatuses.GetStatusCode(rsInvalidParam),SErrInvalidSortDescField,[FN]);
  247. AddField(I-1,F,Desc)
  248. end;
  249. end;
  250. end;
  251. function TSQLDBRestDBHandler.GetOrderBy: UTF8String;
  252. Const
  253. AscDesc : Array[Boolean] of string = ('ASC','DESC');
  254. Var
  255. L : TRestFieldOrderPairArray;
  256. P : TRestFieldOrderPair;
  257. begin
  258. Result:='';
  259. L:=GetOrderByFieldArray;
  260. For P in L do
  261. begin
  262. if Result<>'' then
  263. Result:=Result+', ';
  264. Result:=Result+P.RestField.FieldName+' '+AscDesc[P.Desc];
  265. end;
  266. end;
  267. function TSQLDBRestDBHandler.CreateQuery(aSQL: String): TSQLQuery;
  268. begin
  269. Result:=FQueryClass.Create(Self);
  270. Result.DataBase:=IO.Connection;
  271. Result.Transaction:=IO.Transaction;
  272. Result.SQL.Text:=aSQL;
  273. end;
  274. function TSQLDBRestDBHandler.BuildFieldList(ForceAll : Boolean): TRestFieldPairArray;
  275. Var
  276. L : TSQLDBRestFieldArray;
  277. F : TSQLDBRestField;
  278. aCount : Integer;
  279. Fi,Fe : TStrings;
  280. Function ML(N : String) : TStrings;
  281. Var
  282. V : UTF8String;
  283. begin
  284. Result:=Nil;
  285. if ForceAll then
  286. exit;
  287. IO.GetVariable(N,V);
  288. if (V<>'') then
  289. begin
  290. Result:=TStringList.Create;
  291. Result.StrictDelimiter:=True;
  292. Result.CommaText:=V;
  293. end;
  294. end;
  295. Function IsIncluded(F : TSQLDBRestField) : Boolean;
  296. begin
  297. Result:=(FI=Nil) or (FI.IndexOf(F.PublicName)<>-1)
  298. end;
  299. Function IsExcluded(F : TSQLDBRestField) : Boolean;
  300. begin
  301. Result:=(FE<>Nil) and (FE.IndexOf(F.PublicName)<>-1)
  302. end;
  303. begin
  304. Result:=Default(TRestFieldPairArray);
  305. if Not Assigned(FResource) then
  306. exit;
  307. FE:=Nil;
  308. FI:=ML(GetString(rpFieldList));
  309. try
  310. FE:=ML(GetString(rpExcludeFieldList));
  311. L:=FResource.GetFieldArray(flSelect);
  312. SetLength(Result,Length(L));
  313. aCount:=0;
  314. For F in L do
  315. if IsIncluded(F) and not IsExcluded(F) then
  316. begin
  317. Result[aCount].RestField:=F;
  318. Result[aCount].DBField:=Nil;
  319. Inc(aCount);
  320. end;
  321. SetLength(Result,aCount);
  322. finally
  323. FI.Free;
  324. FE.Free;
  325. end;
  326. end;
  327. function TSQLDBRestDBHandler.GetDataForParam(P: TParam; F: TSQLDBRestField;
  328. Sources: TVariableSources): TJSONData;
  329. Var
  330. vs : TVariableSource;
  331. S,N : UTF8String;
  332. begin
  333. Result:=Nil;
  334. if Assigned(F) then
  335. begin
  336. N:=F.PublicName;
  337. vs:=IO.GetVariable(N,S,Sources);
  338. if (vs<>vsNone) then
  339. Result:=TJSONString.Create(S)
  340. else if (vsContent in Sources) then
  341. Result:=IO.RESTInput.GetContentField(N);
  342. end;
  343. If (Result=Nil) then
  344. begin
  345. N:=P.Name;
  346. if N='ID_' then
  347. N:='ID';
  348. vs:=IO.GetVariable(N,S);
  349. if (vs<>vsNone) then
  350. Result:=TJSONString.Create(S)
  351. else if (vsContent in Sources) then
  352. Result:=IO.RESTInput.GetContentField(N)
  353. end;
  354. end;
  355. procedure TSQLDBRestDBHandler.SetParamFromData(P: TParam; F: TSQLDBRestField;
  356. D: TJSONData);
  357. Var
  358. S : String;
  359. begin
  360. if Assigned(D) then
  361. S:=D.AsString;
  362. if not Assigned(D) then
  363. P.Clear
  364. else if Assigned(F) then
  365. Case F.FieldType of
  366. rftInteger : P.AsInteger:=D.AsInteger;
  367. rftLargeInt : P.AsLargeInt:=D.AsInt64;
  368. rftFloat : P.AsFloat:=D.AsFloat;
  369. rftDate : P.AsDateTime:=ScanDateTime(GetString(rpDateFormat),S);
  370. rftTime : P.AsDateTime:=ScanDateTime(GetString(rpTimeFormat),S);
  371. rftDateTime : P.AsDateTime:=ScanDateTime(GetString(rpDateTimeFormat),S);
  372. rftString : P.AsString:=S;
  373. rftBoolean : P.AsBoolean:=D.AsBoolean;
  374. rftBlob :
  375. {$IFNDEF VER3_0}
  376. P.AsBlob:=BytesOf(DecodeStringBase64(S));
  377. {$ELSE}
  378. P.AsBlob:=DecodeStringBase64(S);
  379. {$ENDIF}
  380. else
  381. P.AsString:=S;
  382. end
  383. else
  384. P.AsString:=S;
  385. end;
  386. function TSQLDBRestDBHandler.FindFieldForParam(aOperation: TRestOperation;
  387. P: TParam): TSQLDBRestField;
  388. Var
  389. N : UTF8String;
  390. A : TSQLDBRestFieldArray;
  391. begin
  392. Result:=Nil;
  393. N:=P.Name;
  394. if (N='ID_') then
  395. begin
  396. A:=FResource.GetFieldArray(flWhereKey);
  397. if (Length(A)=1) then
  398. Result:=A[0];
  399. end
  400. else
  401. Result:=FResource.Fields.FindByFieldName(N);
  402. end;
  403. procedure TSQLDBRestDBHandler.FillParams(aOperation : TRestOperation; aParams: TParams;FilteredFields : TRestFilterPairArray);
  404. Var
  405. I : Integer;
  406. P : TParam;
  407. D : TJSONData;
  408. F : TSQLDBRestField;
  409. FF : TRestFilterPair;
  410. Sources : TVariableSources;
  411. begin
  412. // Fill known params
  413. for FF in FilteredFields do
  414. begin
  415. F:=FF.Field;
  416. if FF.Operation<>rfNull then
  417. begin
  418. P:=aParams.FindParam(FilterParamPrefix[FF.Operation]+F.FieldName);
  419. // If there is no %where% macro, the parameter can be absent
  420. if Assigned(P) then
  421. begin
  422. if Assigned(FF.ValueParam) then
  423. P.Value:=FF.ValueParam.Value
  424. else
  425. begin
  426. D:=TJSONString.Create(FF.Value);
  427. try
  428. SetParamFromData(P,F,D)
  429. finally
  430. D.Free;
  431. end;
  432. end;
  433. end;
  434. end;
  435. end;
  436. // Fill in remaining params. Determine source
  437. case aOperation of
  438. roGet : Sources:=[vsQuery,vsRoute];
  439. roPost,
  440. roPatch,
  441. roPut : Sources:=[vsQuery,vsContent,vsRoute];
  442. roDelete : Sources:=[vsQuery,vsRoute];
  443. else
  444. Sources:=AllVariableSources;
  445. end;
  446. For I:=0 to aParams.Count-1 do
  447. begin
  448. P:=aParams[i];
  449. if P.IsNull then
  450. try
  451. D:=Nil;
  452. F:=FindFieldForParam(aOperation,P);
  453. D:=GetDataForParam(P,F,Sources);
  454. if (D<>Nil) then
  455. SetParamFromData(P,F,D)
  456. else if (aOperation in [roDelete]) then
  457. Raise ESQLDBRest.CreateFmt(IO.RestStatuses.GetStatusCode(rsInvalidParam),SErrMissingParameter,[P.Name])
  458. else
  459. P.Clear;
  460. finally
  461. FreeAndNil(D);
  462. end;
  463. end;
  464. end;
  465. function TSQLDBRestDBHandler.GetLimitOffset(out aLimit, aOffset: Int64
  466. ): Boolean;
  467. begin
  468. Result:=IO.GetLimitOffset(EnforceLimit,aLimit,aoffset);
  469. end;
  470. function TSQLDBRestDBHandler.GetLimit: UTF8String;
  471. var
  472. aOffset, aLimit : Int64;
  473. CT : String;
  474. begin
  475. Result:='';
  476. GetLimitOffset(aLimit,aOffset);
  477. if aLimit=0 then
  478. exit;
  479. if Not (IO.Connection is TSQLConnector) then
  480. Raise ESQLDBRest.Create(IO.RestStatuses.GetStatusCode(rsError),SErrLimitNotSupported);
  481. CT:=lowerCase(TSQLConnector(IO.Connection).ConnectorType);
  482. if Copy(CT,1,5)='mysql' then
  483. CT:='mysql';
  484. case CT of
  485. 'mysql' : Result:=Format('LIMIT %d, %d',[aOffset,aLimit]);
  486. 'postgresql',
  487. 'sqlite3' : Result:=Format('LIMIT %d offset %d',[aLimit,aOffset]);
  488. 'interbase',
  489. 'firebird' : Result:=Format('ROWS %d TO %d',[aOffset,aOffset+aLimit-1]);
  490. 'oracle',
  491. 'sybase',
  492. 'odbc',
  493. 'MSSQLServer' : Result:=Format('OFFSET %d ROWS FETCH NEXT %d ROWS ONLY',[aOffset,aLimit]);
  494. end;
  495. end;
  496. function TSQLDBRestDBHandler.StreamRecord(O: TRestOutputStreamer; D: TDataset;
  497. FieldList: TRestFieldPairArray): Boolean;
  498. Var
  499. i : Integer;
  500. begin
  501. Result:=IO.Resource.AllowRecord(IO.RestContext,D);
  502. if not Result then
  503. exit;
  504. O.StartRow;
  505. For I:=0 to Length(FieldList)-1 do
  506. O.WriteField(FieldList[i]);
  507. O.EndRow;
  508. end;
  509. function TSQLDBRestDBHandler.StreamDataset(O: TRestOutputStreamer; D: TDataset;
  510. FieldList: TRestFieldPairArray; CurrentOnly : Boolean = False): Int64;
  511. Var
  512. aLimit,aOffset : Int64;
  513. Function LimitReached : boolean;
  514. begin
  515. Result:=EmulateOffsetLimit and (aLimit<=0);
  516. end;
  517. Var
  518. I : Integer;
  519. begin
  520. Result:=0;
  521. if EmulateOffsetLimit then
  522. GetLimitOffset(aLimit,aOffset)
  523. else
  524. begin
  525. aLimit:=0;
  526. aOffset:=0;
  527. end;
  528. For I:=0 to Length(FieldList)-1 do
  529. FieldList[i].DBField:=D.FieldByName(FieldList[i].RestField.FieldName);
  530. if O.HasOption(ooMetadata) then
  531. O.WriteMetadata(FieldList);
  532. O.StartData;
  533. if CurrentOnly then
  534. StreamRecord(O,D,FieldList)
  535. else
  536. begin
  537. if EmulateOffsetLimit then
  538. While (aOffset>0) and not D.EOF do
  539. begin
  540. D.Next;
  541. Dec(aOffset);
  542. end;
  543. While not (D.EOF or LimitReached) do
  544. begin
  545. If StreamRecord(O,D,FieldList) then
  546. begin
  547. Dec(aLimit);
  548. inc(Result);
  549. end;
  550. D.Next;
  551. end;
  552. end;
  553. O.EndData;
  554. end;
  555. function TSQLDBRestDBHandler.GetSpecialDatasetForResource(
  556. aFieldList: TRestFieldPairArray): TDataset;
  557. Var
  558. aLimit,aOffset : Int64;
  559. begin
  560. Result:=ExternalDataset;
  561. if (Result=Nil) then
  562. begin
  563. GetLimitOffset(aLimit,aOffset);
  564. Result:=FResource.GetDataset(IO.RestContext,aFieldList,GetOrderByFieldArray,aLimit,aOffset);
  565. end;
  566. end;
  567. procedure TSQLDBRestDBHandler.SetExternalDataset(AValue: TDataset);
  568. begin
  569. if FExternalDataset=AValue then Exit;
  570. if Assigned(FExternalDataset) then
  571. FExternalDataset.RemoveFreeNotification(Self);
  572. FExternalDataset:=AValue;
  573. if Assigned(FExternalDataset) then
  574. FExternalDataset.FreeNotification(Self);
  575. end;
  576. function TSQLDBRestDBHandler.SpecialResource: Boolean;
  577. begin
  578. Result:=(ExternalDataset<>Nil) or Assigned(FResource.OnGetDataset);
  579. end;
  580. function TSQLDBRestDBHandler.GetDatasetForResource(aFieldList: TRestFieldPairArray; Singleton : Boolean): TDataset;
  581. Var
  582. aWhere,aOrderby,aLimit,SQL : UTF8String;
  583. Q : TSQLQuery;
  584. WhereFilterList : TRestFilterPairArray;
  585. begin
  586. if SpecialResource then
  587. Exit(GetSpecialDatasetForResource(aFieldList));
  588. if Singleton then
  589. aWhere:=GetIDWhere(WhereFilterList)
  590. else
  591. aWhere:=GetWhere(WhereFilterList);
  592. aOrderBy:=GetOrderBy;
  593. aLimit:=GetLimit;
  594. SQL:=FResource.GetResolvedSQl(skSelect,aWhere,aOrderBy,aLimit);
  595. Q:=CreateQuery(SQL);
  596. Try
  597. Q.UsePrimaryKeyAsKey:=False;
  598. FillParams(roGet,Q.Params,WhereFilterList);
  599. if Not SpecialResource then
  600. IO.Resource.CheckParams(IO.RestContext,roPost,Q.Params);
  601. Result:=Q;
  602. except
  603. Q.Free;
  604. raise;
  605. end;
  606. end;
  607. procedure TSQLDBRestDBHandler.CreateResourceFromDataset(D : TDataset);
  608. begin
  609. FOwnsResource:=True;
  610. FResource:=TCustomViewResource.Create(Nil);
  611. FResource.PopulateFieldsFromFieldDefs(D.FieldDefs,Nil,Nil,[]);
  612. end;
  613. procedure TSQLDBRestDBHandler.DoNotFound;
  614. begin
  615. IO.Response.Code:=IO.RestStatuses.GetStatusCode(rsRecordNotFound);
  616. IO.Response.CodeText:='NOT FOUND'; // Do not localize
  617. IO.CreateErrorResponse;
  618. end;
  619. procedure TSQLDBRestDBHandler.DoHandleGet;
  620. Var
  621. D : TDataset;
  622. FieldList : TRestFieldPairArray;
  623. qID : UTF8string;
  624. Single : Boolean;
  625. begin
  626. FieldList:=BuildFieldList(False);
  627. Single:=(IO.GetVariable('ID',qId,[vsRoute,vsQuery])<>vsNone);
  628. D:=GetDatasetForResource(FieldList,Single);
  629. try
  630. D.Open;
  631. if DeriveResourceFromDataset then
  632. begin
  633. CreateResourceFromDataset(D);
  634. FieldList:=BuildFieldList(False);
  635. end;
  636. if not (D.EOF and D.BOF) then
  637. StreamDataset(IO.RESTOutput,D,FieldList)
  638. else
  639. begin
  640. if Single then
  641. DoNotFound
  642. else
  643. StreamDataset(IO.RESTOutput,D,FieldList)
  644. end;
  645. finally
  646. D.Free;
  647. end;
  648. end;
  649. function TSQLDBRestDBHandler.GetGeneratorValue(const aGeneratorName: String
  650. ): Int64;
  651. begin
  652. {$IFDEF VER3_0_4}
  653. // The 'get next value' SQL in 3.0.4 is wrong, so we need to do this sep
  654. if (IO.Connection is TSQLConnector) and SameText((IO.Connection as TSQLConnector).ConnectorType,'Sqlite3') then
  655. begin
  656. With CreateQuery('SELECT seq+1 FROM sqlite_sequence WHERE name=:aName') do
  657. Try
  658. ParamByName('aName').AsString:=aGeneratorName;
  659. Open;
  660. if (EOF and BOF) then
  661. DatabaseErrorFmt('Generator %s does not exist',[aGeneratorName]);
  662. Result:=Fields[0].asLargeint;
  663. Finally
  664. Free;
  665. end;
  666. end
  667. else
  668. {$ENDIF}
  669. Result:=IO.Connection.GetNextValue(aGeneratorName,1);
  670. end;
  671. procedure TSQLDBRestDBHandler.SetPostFields(aFields : TFields);
  672. Var
  673. I : Integer;
  674. FData : TField;
  675. D : TJSONData;
  676. RF : TSQLDBRestField;
  677. V : UTF8string;
  678. begin
  679. // Another approach would be to create params for all fields,
  680. // call setPostParams, and copy field data from all set params
  681. // That would allow the use of checkparams...
  682. For I:=0 to aFields.Count-1 do
  683. try
  684. D:=Nil;
  685. FData:=aFields[i];
  686. RF:=FResource.Fields.FindByFieldName(FData.FieldName);
  687. if (RF<>Nil) then
  688. begin
  689. if (RF.GeneratorName<>'') then // Only when doing POST
  690. D:=TJSONInt64Number.Create(GetGeneratorValue(RF.GeneratorName))
  691. else
  692. D:=IO.RESTInput.GetContentField(RF.PublicName);
  693. end
  694. else if IO.GetVariable(FData.Name,V,[vsContent,vsQuery])<>vsNone then
  695. D:=TJSONString.Create(V);
  696. if (D<>Nil) then
  697. SetFieldFromData(FData,RF,D); // Use new value, if any
  698. finally
  699. D.Free;
  700. end;
  701. end;
  702. procedure TSQLDBRestDBHandler.SetFieldFromData(DataField: TField; ResField: TSQLDBRestField; D: TJSONData);
  703. begin
  704. if not Assigned(D) then
  705. DataField.Clear
  706. else if Assigned(ResField) then
  707. Case ResField.FieldType of
  708. rftInteger : DataField.AsInteger:=D.AsInteger;
  709. rftLargeInt : DataField.AsLargeInt:=D.AsInt64;
  710. rftFloat : DataField.AsFloat:=D.AsFloat;
  711. rftDate : DataField.AsDateTime:=ScanDateTime(GetString(rpDateFormat),D.AsString);
  712. rftTime : DataField.AsDateTime:=ScanDateTime(GetString(rpTimeFormat),D.AsString);
  713. rftDateTime : DataField.AsDateTime:=ScanDateTime(GetString(rpDateTimeFormat),D.AsString);
  714. rftString : DataField.AsString:=D.AsString;
  715. rftBoolean : DataField.AsBoolean:=D.AsBoolean;
  716. rftBlob :
  717. {$IFNDEF VER3_0}
  718. DataField.AsBytes:=BytesOf(DecodeStringBase64(D.AsString));
  719. {$ELSE}
  720. DataField.AsString:=DecodeStringBase64(D.AsString);
  721. {$ENDIF}
  722. else
  723. DataField.AsString:=D.AsString;
  724. end
  725. else
  726. DataField.AsString:=D.AsString;
  727. end;
  728. procedure TSQLDBRestDBHandler.SetPostParams(aParams : TParams; Old : TFields = Nil);
  729. Var
  730. I : Integer;
  731. P : TParam;
  732. D : TJSONData;
  733. F : TSQLDBRestField;
  734. FOld : TField;
  735. V : UTF8string;
  736. begin
  737. For I:=0 to aParams.Count-1 do
  738. try
  739. D:=Nil;
  740. FOld:=Nil;
  741. P:=aParams[i];
  742. F:=FResource.Fields.FindByFieldName(P.Name);
  743. If Assigned(Old) then
  744. Fold:=Old.FindField(P.Name);
  745. if (F<>Nil) then
  746. begin
  747. if (F.GeneratorName<>'') and (Old=Nil) then // Only when doing POST
  748. D:=TJSONInt64Number.Create(GetGeneratorValue(F.GeneratorName))
  749. else
  750. D:=IO.RESTInput.GetContentField(F.PublicName);
  751. end
  752. else if IO.GetVariable(P.Name,V,[vsContent,vsQuery])<>vsNone then
  753. D:=TJSONString.Create(V);
  754. if (D=Nil) and Assigned(Fold) then
  755. begin
  756. {$IFDEF VER3_2_2}
  757. // ftLargeInt is missing
  758. if Fold.DataType=ftLargeInt then
  759. P.AsLargeInt:=FOld.AsLargeInt
  760. else
  761. {$ENDIF}
  762. P.AssignFromField(Fold) // use old value
  763. end
  764. else
  765. SetParamFromData(P,F,D); // Use new value, if any
  766. finally
  767. D.Free;
  768. end;
  769. // Give user a chance to look at it.
  770. FResource.CheckParams(io.RestContext,roPost,aParams);
  771. // Save so it can be used in GetWHereID for return
  772. FPostParams:=TParams.Create(TParam);
  773. FPostParams.Assign(aParams);
  774. end;
  775. procedure TSQLDBRestDBHandler.InsertNewRecord;
  776. Var
  777. S : TSQLStatement;
  778. SQL : UTF8String;
  779. begin
  780. if Assigned(ExternalDataset) then
  781. begin
  782. ExternalDataset.Append;
  783. SetPostFields(ExternalDataset.Fields);
  784. try
  785. ExternalDataset.Post;
  786. except
  787. ExternalDataset.Cancel;
  788. Raise;
  789. end
  790. end
  791. else
  792. begin
  793. SQL:=FResource.GetResolvedSQl(skInsert,'','','');
  794. S:=TSQLStatement.Create(Self);
  795. try
  796. S.Database:=IO.Connection;
  797. S.Transaction:=IO.Transaction;
  798. S.SQL.Text:=SQL;
  799. SetPostParams(S.Params);
  800. S.Execute;
  801. PostParams.Assign(S.Params);
  802. S.Transaction.Commit;
  803. Finally
  804. S.Free;
  805. end;
  806. end;
  807. end;
  808. procedure TSQLDBRestDBHandler.DoHandlePost;
  809. Var
  810. D : TDataset;
  811. FieldList : TRestFieldPairArray;
  812. begin
  813. // We do this first, so we don't run any unnecessary queries
  814. if not IO.RESTInput.SelectObject(0) then
  815. raise ESQLDBRest.Create(IO.RestStatuses.GetStatusCode(rsInvalidParam), SErrNoResourceDataFound);
  816. InsertNewRecord;
  817. // Now build response. We can imagine not doing a select again, and simply supply back the fields as sent...
  818. FieldList:=BuildFieldList(False);
  819. D:=GetDatasetForResource(FieldList,True);
  820. try
  821. D.Open;
  822. IO.RESTOutput.OutputOptions:=IO.RESTOutput.OutputOptions-[ooMetadata];
  823. StreamDataset(IO.RESTOutput,D,FieldList);
  824. finally
  825. D.Free;
  826. end;
  827. end;
  828. procedure TSQLDBRestDBHandler.DoHandlePutPatch(IsPatch: Boolean);
  829. Var
  830. D : TDataset;
  831. FieldList : TRestFieldPairArray;
  832. begin
  833. // We do this first, so we don't run any unnecessary queries
  834. if not IO.RESTInput.SelectObject(0) then
  835. Raise ESQLDBRest.Create(IO.RestStatuses.GetStatusCode(rsInvalidParam),SErrNoResourceDataFound);
  836. // Get the original record.
  837. FieldList:=BuildFieldList(True);
  838. D:=GetDatasetForResource(FieldList,True);
  839. try
  840. if not FindExistingRecord(D) then
  841. begin
  842. DoNotFound;
  843. exit;
  844. end;
  845. UpdateExistingRecord(D,IsPatch);
  846. // Now build response
  847. if D<>ExternalDataset then
  848. begin;
  849. // Now build response. We can imagine not doing a select again, and simply supply back the fields as sent...
  850. FreeAndNil(D);
  851. D:=GetDatasetForResource(FieldList,True);
  852. FieldList:=BuildFieldList(False);
  853. D.Open;
  854. end;
  855. IO.RESTOutput.OutputOptions:=IO.RESTOutput.OutputOptions-[ooMetadata];
  856. StreamDataset(IO.RESTOutput,D,FieldList);
  857. finally
  858. D.Free;
  859. end;
  860. end;
  861. function TSQLDBRestDBHandler.GetRequestFields : TSQLDBRestFieldArray;
  862. Var
  863. F : TSQLDBRestField;
  864. aSize : Integer;
  865. begin
  866. Result:=[];
  867. SetLength(Result,FResource.Fields.Count);
  868. aSize:=0;
  869. For F in FResource.Fields do
  870. if FRestIO.RESTInput.HaveInputData(F.PublicName) then
  871. begin
  872. Result[aSize]:=F;
  873. Inc(aSize);
  874. end;
  875. SetLength(Result,aSize);
  876. end;
  877. procedure TSQLDBRestDBHandler.CheckAllRequiredFieldsPresent;
  878. Var
  879. F : TSQLDBRestField;
  880. Missing : UTF8String;
  881. begin
  882. Missing:='';
  883. For F in FResource.Fields do
  884. if (foRequired in F.Options) and (F.GeneratorName='') then
  885. if not IO.RESTInput.HaveInputData(F.PublicName) then
  886. begin
  887. if Missing<>'' then
  888. Missing:=Missing+', ';
  889. Missing:=Missing+F.PublicName;
  890. end;
  891. if Missing<>'' then
  892. Raise ESQLDBRest.CreateFmt(500,SErrMissingInputFields,[Missing]);
  893. end;
  894. function TSQLDBRestDBHandler.GetAllowMultiUpdate: Boolean;
  895. begin
  896. Result:=rhoAllowMultiUpdate in Options;
  897. end;
  898. function TSQLDBRestDBHandler.GetCheckUpdateCount: Boolean;
  899. begin
  900. Result:=rhoCheckupdateCount in Options;
  901. end;
  902. function TSQLDBRestDBHandler.GetUseLegacyPUT: Boolean;
  903. begin
  904. Result:=rhoLegacyPut in Options;
  905. end;
  906. procedure TSQLDBRestDBHandler.UpdateExistingRecord(OldData: TDataset;
  907. IsPatch: Boolean);
  908. const
  909. putpatch : Array [Boolean] of TRestOperation = (roPut,roPatch);
  910. Var
  911. S : TSQLQuery;
  912. aRowsAffected: Integer;
  913. SQl : String;
  914. WhereFilterList : TRestFilterPairArray;
  915. RequestFields : TSQLDBRestFieldArray;
  916. begin
  917. if (OldData=ExternalDataset) then
  918. begin
  919. ExternalDataset.Edit;
  920. try
  921. SetPostFields(ExternalDataset.Fields);
  922. ExternalDataset.Post;
  923. except
  924. ExternalDataset.Cancel;
  925. Raise;
  926. end
  927. end
  928. else
  929. begin
  930. if isPatch then
  931. RequestFields:=GetRequestFields
  932. else if not (isPatch or UseLegacyPUT) then
  933. begin
  934. CheckAllRequiredFieldsPresent;
  935. RequestFields:=[];
  936. end;
  937. S:=TSQLQuery.Create(Self);
  938. try
  939. SQL:=FResource.GetResolvedSQl(skUpdate,GetIDWhere(WhereFilterList) ,'','',RequestFields);
  940. S.Database:=IO.Connection;
  941. S.Transaction:=IO.Transaction;
  942. S.SQL.Text:=SQL;
  943. if (not isPatch) and UseLegacyPUT then
  944. SetPostParams(S.Params,OldData.Fields);
  945. FillParams(PutPatch[isPatch],S.Params,WhereFilterList);
  946. // Give user a chance to look at it.
  947. FResource.CheckParams(io.RestContext,PutPatch[IsPatch],S.Params);
  948. S.ExecSQL;
  949. if CheckUpdateCount then
  950. begin
  951. aRowsAffected:=S.RowsAffected;
  952. if (aRowsAffected<1) then
  953. Raise ESQLDBRest.Create(500,SErrNoRecordsUpdated);
  954. if (aRowsAffected>1) and not AllowMultiUpdate then
  955. Raise ESQLDBRest.CreateFmt(500,SErrTooManyRecordsUpdated,[aRowsAffected]);
  956. end;
  957. S.SQLTransaction.Commit;
  958. finally
  959. S.Free;
  960. end;
  961. end;
  962. end;
  963. function TSQLDBRestDBHandler.FindExistingRecord(D: TDataset): Boolean;
  964. Var
  965. KeyFields : String;
  966. FieldList : TRestFilterPairArray;
  967. FP : TRestFilterPair;
  968. V : Variant;
  969. I : Integer;
  970. begin
  971. D.Open;
  972. if D<>ExternalDataset then
  973. Result:=Not (D.BOF and D.EOF)
  974. else
  975. begin
  976. GetIDWhere(FieldList);
  977. V:=VarArrayCreate([0,Length(FieldList)-1],varVariant);
  978. KeyFields:='';
  979. I:=0;
  980. For FP in FieldList do
  981. begin
  982. if KeyFields<>'' then
  983. KeyFields:=KeyFields+';';
  984. KeyFields:=KeyFields+FP.Field.FieldName;
  985. if Assigned(FP.ValueParam) then
  986. V[i]:=FP.ValueParam.Value
  987. else
  988. V[i]:=FP.Value;
  989. Inc(i);
  990. end;
  991. Result:=D.Locate(KeyFields,V,[loCaseInsensitive]);
  992. end;
  993. end;
  994. procedure TSQLDBRestDBHandler.DoHandlePut;
  995. begin
  996. DoHandlePutPatch(False);
  997. end;
  998. procedure TSQLDBRestDBHandler.DoHandlePatch;
  999. begin
  1000. DoHandlePutPatch(True);
  1001. end;
  1002. destructor TSQLDBRestDBHandler.Destroy;
  1003. begin
  1004. FreeAndNil(FPostParams);
  1005. If FOwnsResource then
  1006. FreeAndNil(FResource);
  1007. inherited Destroy;
  1008. end;
  1009. procedure TSQLDBRestDBHandler.Notification(AComponent: TComponent; Operation: TOperation);
  1010. begin
  1011. If Operation=opRemove then
  1012. begin
  1013. if (aComponent=FExternalDataset) then
  1014. FExternalDataset:=Nil;
  1015. end;
  1016. end;
  1017. procedure TSQLDBRestDBHandler.DoHandleDelete;
  1018. Var
  1019. aWhere,SQL : UTF8String;
  1020. Q : TSQLQuery;
  1021. FilteredFields : TRestFilterPairArray;
  1022. begin
  1023. if Assigned(ExternalDataset) then
  1024. begin
  1025. If FindExistingRecord(ExternalDataset) then
  1026. ExternalDataset.Delete
  1027. else
  1028. DoNotFound;
  1029. end
  1030. else
  1031. begin
  1032. aWhere:=GetIDWhere(FilteredFields);
  1033. SQL:=FResource.GetResolvedSQl(skDelete,aWhere,'');
  1034. Q:=CreateQuery(SQL);
  1035. try
  1036. FillParams(roDelete,Q.Params,FilteredFields);
  1037. Q.ExecSQL;
  1038. if Q.RowsAffected<>1 then
  1039. DoNotFound;
  1040. finally
  1041. Q.Free;
  1042. end;
  1043. end;
  1044. end;
  1045. end.