Quick.Linq.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. { ***************************************************************************
  2. Copyright (c) 2016-2019 Kike Pérez
  3. Unit : Quick.Linq
  4. Description : Arrays and Generic Lists Linq functions
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 04/04/2019
  8. Modified : 01/12/2019
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Linq;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. SysUtils,
  26. Generics.Collections,
  27. Generics.Defaults,
  28. RTTI,
  29. Quick.RTTI.Utils,
  30. Quick.Expression,
  31. Quick.Commons,
  32. Quick.Value,
  33. {$IFDEF FPC}
  34. Quick.Value.RTTI,
  35. {$ENDIF}
  36. Quick.Arrays;
  37. type
  38. TOrderDirection = (odAscending, odDescending);
  39. ILinqQuery<T> = interface
  40. ['{16B68C0B-EA38-488A-99D9-BAD1E8560E8E}']
  41. function Where(const aWhereClause : string; aWhereValues : array of const) : ILinqQuery<T>; overload;
  42. function Where(const aWhereClause: string): ILinqQuery<T>; overload;
  43. function OrderBy(const aFieldNames : string) : ILinqQuery<T>;
  44. function OrderByDescending(const aFieldNames : string) : ILinqQuery<T>;
  45. function SelectFirst : T;
  46. function SelectLast : T;
  47. function SelectTop(aLimit : Integer) : TxArray<T>;
  48. function Select : TxArray<T>; overload;
  49. function Select(const aPropertyName : string) : TFlexArray; overload;
  50. function Count : Integer;
  51. function Update(const aFields : array of string; aValues : array of const) : Integer;
  52. function Delete : Integer;
  53. end;
  54. TLinqQuery<T : class> = class(TInterfacedObject,ILinqQuery<T>)
  55. private type
  56. arrayOfT = array of T;
  57. private
  58. fWhereClause : TExpression;
  59. fOrderBy : TArray<string>;
  60. fOrderDirection : TOrderDirection;
  61. //fPList : Pointer;
  62. fList : arrayOfT;
  63. function FormatParams(const aWhereClause : string; aWhereParams : array of const) : string;
  64. procedure DoOrderBy(vArray : ArrayOfT);
  65. function Compare(const aPropertyName : string; L, R : T) : Integer;
  66. procedure Clear;
  67. public
  68. {$IFNDEF FPC}
  69. constructor Create(aObjectList : TObjectList<T>); overload;
  70. {$ENDIF}
  71. constructor Create(aList : TList<T>); overload;
  72. constructor Create(aXArray : TxArray<T>); overload;
  73. constructor Create(aArray : TArray<T>); overload;
  74. destructor Destroy; override;
  75. function Where(const aWhereClause : string; aWhereParams : array of const) : ILinqQuery<T>; overload;
  76. function Where(const aWhereClause: string): ILinqQuery<T>; overload;
  77. function OrderBy(const aFieldNames : string) : ILinqQuery<T>;
  78. function OrderByDescending(const aFieldNames : string) : ILinqQuery<T>;
  79. function SelectFirst : T;
  80. function SelectLast : T;
  81. function SelectTop(aLimit : Integer) : TxArray<T>;
  82. function Select : TxArray<T>; overload;
  83. function Select(const aPropertyName : string) : TFlexArray; overload;
  84. function Count : Integer;
  85. function Update(const aFields : array of string; aValues : array of const) : Integer;
  86. function Delete : Integer;
  87. end;
  88. TLinq<T : class> = class
  89. public
  90. {$IFNDEF FPC}
  91. class function From(aObjectList : TObjectList<T>) : ILinqQuery<T>; overload;
  92. {$ENDIF}
  93. class function From(aArray : TArray<T>) : ILinqQuery<T>; overload;
  94. class function From(aXArray : TXArray<T>) : ILinqQuery<T>; overload;
  95. end;
  96. ELinqNotValidExpression = class(Exception);
  97. ELinqError = class(Exception);
  98. implementation
  99. { TLinqQuery<T> }
  100. procedure TLinqQuery<T>.Clear;
  101. begin
  102. SetLength(fOrderBy,0);
  103. end;
  104. constructor TLinqQuery<T>.Create(aArray: TArray<T>);
  105. begin
  106. Clear;
  107. fList := aArray;
  108. end;
  109. {$IFNDEF FPC}
  110. constructor TLinqQuery<T>.Create(aObjectList: TObjectList<T>);
  111. begin
  112. Clear;
  113. //Create(aObjectList.List);
  114. //fPList := Pointer(aObjectList.List);
  115. //fList := arrayOfT(fPList);
  116. fList := aObjectList.List;
  117. end;
  118. {$ENDIF}
  119. constructor TLinqQuery<T>.Create(aXArray: TxArray<T>);
  120. begin
  121. Clear;
  122. fList := aXArray;
  123. end;
  124. constructor TLinqQuery<T>.Create(aList: TList<T>);
  125. begin
  126. Clear;
  127. fList := aList.ToArray;
  128. end;
  129. function TLinqQuery<T>.Compare(const aPropertyName: string; L, R: T): Integer;
  130. var
  131. valueL : TValue;
  132. valueR : TValue;
  133. begin
  134. Result := 0;
  135. valueL := TRTTI.GetPathValue(L,aPropertyName);
  136. valueR := TRTTI.GetPathValue(R,aPropertyName);
  137. if (valueL.IsEmpty) and (not valueR.IsEmpty) then Exit(1)
  138. else if (not valueL.IsEmpty) and (valueR.IsEmpty) then Exit(-1);
  139. case valueL.Kind of
  140. {$IFNDEF FPC}
  141. tkString,
  142. {$ENDIF}
  143. tkChar, tkWString, tkUString : Result := CompareText(valueL.AsString, valueR.AsString);
  144. tkInteger, tkInt64 :
  145. begin
  146. if valueL.AsInteger > valueR.AsInteger then Result := 1
  147. else if valueL.AsInteger < valueR.AsInteger then Result := -1;
  148. end;
  149. tkFloat :
  150. begin
  151. if valueL.AsExtended > valueR.AsExtended then Result := 1
  152. else if valueL.AsExtended < valueR.AsExtended then Result := -1;
  153. end;
  154. end;
  155. end;
  156. function TLinqQuery<T>.Count: Integer;
  157. var
  158. i : Integer;
  159. begin
  160. Result := 0;
  161. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  162. for i := High(fList) downto Low(fList) do
  163. begin
  164. if fWhereClause.Validate(fList[i]) then
  165. begin
  166. Inc(Result);
  167. end;
  168. end;
  169. end;
  170. function TLinqQuery<T>.Delete: Integer;
  171. var
  172. i : Integer;
  173. begin
  174. Result := 0;
  175. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  176. for i := High(fList) downto Low(fList) do
  177. begin
  178. if fWhereClause.Validate(fList[i]) then
  179. begin
  180. TObject(fList[i]).Free;
  181. //System.Delete(fList,i,1);
  182. Inc(Result);
  183. end;
  184. end;
  185. end;
  186. destructor TLinqQuery<T>.Destroy;
  187. begin
  188. if Assigned(fWhereClause) then fWhereClause.Free;
  189. inherited;
  190. end;
  191. procedure TLinqQuery<T>.DoOrderBy(vArray : ArrayOfT);
  192. begin
  193. if High(fOrderBy) < 0 then Exit;
  194. {$IFNDEF FPC}
  195. TArray.Sort<T>(vArray, TComparer<T>.Construct(
  196. function (const A, B: T): Integer
  197. var
  198. field : string;
  199. begin
  200. for field in fOrderBy do
  201. begin
  202. Result := Compare(field,A,B);
  203. if Result <> 0 then Break;
  204. end;
  205. if fOrderDirection = TOrderDirection.odDescending then Result := Result * -1;
  206. end)
  207. );
  208. {$ENDIF}
  209. end;
  210. function TLinqQuery<T>.OrderBy(const aFieldNames: string): ILinqQuery<T>;
  211. begin
  212. Result := Self;
  213. if aFieldNames = '' then raise ELinqError.Create('No order fields specified!');
  214. fOrderBy := aFieldNames.Split([',']);
  215. fOrderDirection := TOrderDirection.odAscending;
  216. end;
  217. function TLinqQuery<T>.OrderByDescending(const aFieldNames: string): ILinqQuery<T>;
  218. begin
  219. Result := Self;
  220. if aFieldNames = '' then raise ELinqError.Create('No order fields specified!');
  221. fOrderBy := aFieldNames.Split([',']);
  222. fOrderDirection := TOrderDirection.odDescending;
  223. end;
  224. function TLinqQuery<T>.Select(const aPropertyName: string): TFlexArray;
  225. var
  226. obj : T;
  227. value : TFlexValue;
  228. begin
  229. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  230. for obj in fList do
  231. begin
  232. if fWhereClause.Validate(obj) then
  233. begin
  234. //value := TRTTI.GetProperty(obj,aPropertyName);
  235. {$IFNDEF FPC}
  236. value := TRTTI.GetPathValue(obj,aPropertyName).AsVariant;
  237. {$ELSE}
  238. value.AsTValue := TRTTI.GetPathValue(obj,aPropertyName);
  239. {$ENDIF}
  240. Result.Add(value);
  241. end;
  242. end;
  243. end;
  244. function TLinqQuery<T>.Select: TxArray<T>;
  245. var
  246. obj : T;
  247. begin
  248. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  249. for obj in fList do
  250. begin
  251. if fWhereClause.Validate(obj) then Result.Add(obj);
  252. end;
  253. DoOrderBy(Result);
  254. end;
  255. function TLinqQuery<T>.SelectFirst: T;
  256. var
  257. obj : T;
  258. begin
  259. {$IFNDEF FPC}
  260. Result := nil;
  261. {$ENDIF}
  262. DoOrderBy(fList);
  263. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  264. for obj in fList do
  265. begin
  266. if fWhereClause.Validate(obj) then Exit(obj);
  267. end;
  268. end;
  269. function TLinqQuery<T>.SelectLast: T;
  270. var
  271. obj : T;
  272. begin
  273. {$IFNDEF FPC}
  274. Result := nil;
  275. {$ENDIF}
  276. DoOrderBy(fList);
  277. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  278. for obj in fList do
  279. begin
  280. if fWhereClause.Validate(obj) then Result := obj;
  281. end;
  282. end;
  283. function TLinqQuery<T>.SelectTop(aLimit: Integer): TxArray<T>;
  284. var
  285. obj : T;
  286. i : Integer;
  287. begin
  288. DoOrderBy(fList);
  289. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  290. i := 0;
  291. for obj in fList do
  292. begin
  293. if fWhereClause.Validate(obj) then
  294. begin
  295. Result.Add(obj);
  296. Inc(i);
  297. if i > aLimit then Exit;
  298. end;
  299. end;
  300. end;
  301. function TLinqQuery<T>.Update(const aFields: array of string; aValues: array of const): Integer;
  302. var
  303. obj : T;
  304. i : Integer;
  305. {$IFDEF FPC}
  306. value : TValue;
  307. {$ENDIF}
  308. begin
  309. Result := 0;
  310. if fWhereClause = nil then raise ELinqNotValidExpression.Create('Not valid expression defined!');
  311. for obj in fList do
  312. begin
  313. {$IFNDEF FPC}
  314. if obj = nil then continue;
  315. {$ELSE}
  316. if Pointer(obj) = nil then continue;
  317. {$ENDIF}
  318. if fWhereClause.Validate(obj) then
  319. begin
  320. for i := Low(aFields) to High(aFields) do
  321. begin
  322. if not TRTTI.PropertyExists(TypeInfo(T),aFields[i]) then raise ELinqError.CreateFmt('Linq update field "%s" not found in obj',[aFields[i]]);
  323. try
  324. {$IFNDEF FPC}
  325. TRTTI.SetPropertyValue(obj,aFields[i],aValues[i]);
  326. {$ELSE}
  327. case aValues[i].VType of
  328. vtString : value := string(aValues[i].VString^);
  329. vtChar : value := string(aValues[i].VChar);
  330. {$IFDEF MSWINDOWS}
  331. vtAnsiString : value := AnsiString(aValues[i].VAnsiString);
  332. vtWideString : value := WideString(aValues[i].VWideString);
  333. {$ENDIF}
  334. {$IFDEF UNICODE}
  335. vtUnicodeString: AsString := string(aValues[i].VUnicodeString);
  336. {$ENDIF UNICODE}
  337. vtInteger : value := aValues[i].VInteger;
  338. vtInt64 : value := aValues[i].VInt64^;
  339. vtExtended : value := aValues[i].VExtended^;
  340. vtBoolean : value := aValues[i].VBoolean;
  341. else raise Exception.Create('DataType not supported by Linq update');
  342. end;
  343. TRTTI.SetPropertyValue(obj,aFields[i],value);
  344. {$ENDIF}
  345. except
  346. on E : Exception do raise ELinqError.CreateFmt('Linq update error: %s',[e.Message]);
  347. end;
  348. end;
  349. Inc(Result);
  350. end;
  351. end;
  352. end;
  353. function TLinqQuery<T>.FormatParams(const aWhereClause : string; aWhereParams : array of const) : string;
  354. var
  355. i : Integer;
  356. begin
  357. Result := aWhereClause;
  358. if aWhereClause = '' then
  359. begin
  360. Result := '1 = 1';
  361. Exit;
  362. end;
  363. for i := 0 to aWhereClause.CountChar('?') - 1 do
  364. begin
  365. case aWhereParams[i].VType of
  366. vtInteger : Result := StringReplace(Result,'?',IntToStr(aWhereParams[i].VInteger),[]);
  367. vtInt64 : Result := StringReplace(Result,'?',IntToStr(aWhereParams[i].VInt64^),[]);
  368. vtExtended : Result := StringReplace(Result,'?',FloatToStr(aWhereParams[i].VExtended^),[]);
  369. vtBoolean : Result := StringReplace(Result,'?',BoolToStr(aWhereParams[i].VBoolean),[]);
  370. vtAnsiString : Result := StringReplace(Result,'?',string(aWhereParams[i].VAnsiString),[]);
  371. vtWideString : Result := StringReplace(Result,'?',string(aWhereParams[i].VWideString^),[]);
  372. {$IFNDEF NEXTGEN}
  373. vtString : Result := StringReplace(Result,'?',aWhereParams[i].VString^,[]);
  374. {$ENDIF}
  375. vtChar : Result := StringReplace(Result,'?',aWhereParams[i].VChar,[]);
  376. vtPChar : Result := StringReplace(Result,'?',string(aWhereParams[i].VPChar),[]);
  377. else Result := StringReplace(Result,'?', DbQuotedStr(string(aWhereParams[i].VUnicodeString)),[]);
  378. end;
  379. end;
  380. end;
  381. function TLinqQuery<T>.Where(const aWhereClause: string; aWhereParams: array of const): ILinqQuery<T>;
  382. begin
  383. Result := Where(FormatParams(aWhereClause,aWhereParams));
  384. end;
  385. function TLinqQuery<T>.Where(const aWhereClause: string): ILinqQuery<T>;
  386. begin
  387. Result := Self;
  388. try
  389. fWhereClause := TExpressionParser.Parse(aWhereClause);
  390. except
  391. on E : Exception do raise ELinqNotValidExpression.Create(e.Message);
  392. end;
  393. end;
  394. { TLinq }
  395. {$IFNDEF FPC}
  396. class function TLinq<T>.From(aObjectList: TObjectList<T>): ILinqQuery<T>;
  397. begin
  398. Result := TLinqQuery<T>.Create(aObjectList);
  399. end;
  400. {$ENDIF}
  401. class function TLinq<T>.From(aArray: TArray<T>): ILinqQuery<T>;
  402. begin
  403. Result := TLinqQuery<T>.Create(aArray);
  404. end;
  405. class function TLinq<T>.From(aXArray : TXArray<T>) : ILinqQuery<T>;
  406. begin
  407. Result := TLinqQuery<T>.Create(aXArray);
  408. end;
  409. end.