sqldbrestdata.pp 36 KB

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