-
-
Notifications
You must be signed in to change notification settings - Fork 137
/
Copy pathmormot.orm.core.pas
11953 lines (11315 loc) · 494 KB
/
mormot.orm.core.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// Object-Relational-Mapping (ORM) Main Types and Classes
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.orm.core;
{
*****************************************************************************
Main Shared Types and Definitions for our RESTful ORM
- ORM Specific TOrmPropInfoRtti Classes
- IRestOrm IRestOrmServer IRestOrmClient Definitions
- TOrm Definition
- RecordRef Wrapper Definition
- TOrmTable TOrmTableJson Definitions
- TOrmMany Definition
- TOrmVirtual Definitions
- TOrmProperties Definitions
- TOrmModel TOrmModelProperties Definitions
- TOrmCache Definition
- TRestBatch TRestBatchLocked Definitions
- TSynValidateRest TSynValidateUniqueField Definitions
- TOrmAccessRights Definition
- TOrm High-Level Parents
This unit is not depending from mormot.rest.core so can be used as a pure
ORM layer for your projects. IRestOrm is the main SOLID entry point.
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
variants,
contnrs,
{$ifdef ISDELPHI}
typinfo, // for proper Delphi inlining
{$endif ISDELPHI}
mormot.core.base,
mormot.core.os,
mormot.core.buffers,
mormot.core.unicode,
mormot.core.text,
mormot.core.datetime,
mormot.core.variants,
mormot.core.data,
mormot.core.rtti,
mormot.core.log,
mormot.core.json,
mormot.core.threads,
{$ifdef ORMGENERICS}
mormot.core.collections,
{$endif ORMGENERICS}
mormot.core.search, // for TSynFilterOrValidate
mormot.crypt.secure, // for TSynUniqueIdentifierGenerator
mormot.db.core,
mormot.orm.base;
{ ************ TOrm TOrmModel TOrmTable IRestOrm Core Definitions }
// most types are defined as a single "type" statement due to classes coupling
type
{$M+}
{ we expect RTTI information for the published properties of these
forward definitions - due to internal coupling, all those classes are
to be defined in a single "type" statement }
TOrmTable = class;
TOrm = class;
TOrmMany = class;
TOrmFts3 = class;
TOrmRTree = class;
TOrmProperties = class;
TOrmModel = class;
TOrmModelProperties = class;
TRestOrmParent = class;
TOrmCache = class;
TRestBatch = class;
{$M-}
/// class-reference type (metaclass) of TOrm
TOrmClass = class of TOrm;
/// pointer-level redirection of a TOrm metaclass
// - used for efficient POrmClass(aRecord)^ access to the class info
POrmClass = ^TOrmClass;
/// class-reference type (metaclass) of a FTS3/FTS4/FTS5 virtual table
// - either a TOrmFts3 TOrmFts4 or TOrmFts5 class
TOrmFts3Class = class of TOrmFts3;
/// class-reference type (metaclass) of RTREE virtual table
// - either a TOrmRTree or a TOrmRTreeInteger
TOrmRTreeClass = class of TOrmRTree;
/// a dynamic array storing TOrm instances
// - not used direcly, but as specialized T*ObjArray types
TOrmObjArray = array of TOrm;
/// pointer to a dynamic array storing TOrm instances
POrmObjArray = ^TOrmObjArray;
/// a dynamic array used to store the TOrm classes in a Database Model
TOrmClassDynArray = array of TOrmClass;
/// a dynamic array of TOrmMany instances
TOrmManyObjArray = array of TOrmMany;
/// exception raised in case of TRestBatch problem
EOrmBatchException = class(EOrmException);
/// exception raised in case of wrong Model definition
EModelException = class(EOrmException);
/// exception raised in case of IRestOrm asynchronous methods problem
EOrmAsyncException = class(EOrmException);
{ -------------------- ORM Specific TOrmPropInfoRtti Classes }
/// information about a TOrm class TOrm property
// - kind oftID, which are pointer(RecordID), not any true class instance
// - will store the content just as an integer value
// - will recognize any instance pre-allocated via Create*Joined() constructor
TOrmPropInfoRttiID = class(TOrmPropInfoRttiInstance)
public
/// raise an exception if was created by Create*Joined() constructor
procedure SetValue(Instance: TObject; Value: PUtf8Char; ValueLen: PtrInt;
wasString: boolean); override;
/// this method will recognize if the TOrm was allocated by
// a Create*Joined() constructor: in this case, it will write the ID
// of the nested property, and not the PtrInt() transtyped value
procedure GetJsonValues(Instance: TObject; W: TJsonWriter); override;
end;
TOrmPropInfoRttiIDObjArray = array of TOrmPropInfoRttiID;
/// information about a TID published property
// - identified as a oftTID kind of property, optionally tied to a TOrm
// class, via its custom type name, e.g.
// ! TOrmClientID = type TID; -> TOrmClient class
TOrmPropInfoRttiTID = class(TOrmPropInfoRttiRecordReference)
protected
fRecordClass: TOrmClass;
public
/// will setup the corresponding RecordClass property from the TID type name
// - the TOrm type should have previously been registered to the
// Rtti.RegisterClass list, e.g. in TOrmModel.Create, so that e.g.
// 'TOrmClientID' type name will match TOrmClient
// - in addition, the '...ToBeDeletedID' name pattern will set CascadeDelete
// to implement a 'ON DELETE CASCADE'-like behavior
constructor Create(aPropInfo: PRttiProp; aPropIndex: integer;
aOrmFieldType: TOrmFieldType; aOptions: TOrmPropInfoListOptions); override;
/// the TOrm class associated to this TID
// - is computed from its type name - for instance, if you define:
// ! type
// ! TOrmClientID = type TID;
// ! TOrmOrder = class(TOrm)
// ! ...
// ! published OrderedBy: TOrmClientID
// ! read fOrderedBy write fOrderedBy;
// ! ...
// then this OrderedBy property will be tied to the TOrmClient class
// of the corresponding model, and the field value will be reset to 0 when
// the targetting record is deleted (emulating ON DELETE SET DEFAULT)
// - equals TOrm for plain TID field
// - equals nil if T*ID type name doesn't match any registered class
property RecordClass: TOrmClass
read fRecordClass;
/// TRUE if this oftTID type name follows the '...ToBeDeletedID' pattern
// - e.g. 'TOrmClientToBeDeletedID' type name will match
// TOrmClient and set CascadeDelete
// - is computed from its type name - for instance, if you define:
// ! type
// ! TOrmClientToBeDeletedID = type TID;
// ! TOrmOrder = class(TOrm)
// ! ...
// ! published OrderedBy: TOrmClientToBeDeletedID
// ! read fOrderedBy write fOrderedBy;
// ! ...
// then this OrderedBy property will be tied to the TOrmClient class
// of the corresponding model, and the whole record will be deleted when
// the targetting record is deleted (emulating a ON DELETE CASCADE)
property CascadeDelete: boolean
read fCascadeDelete;
end;
{ -------------------- IRestOrm IRestOrmServer IRestOrmClient Definitions }
/// event signature for the IRestOrm.RetrieveAsync() callback
// - is called with Value = nil on any DB fatal error
// - function should call Value.Free when finished with it
TOnRestOrmRetrieveOne = procedure(Value: TOrm; Context: TObject) of object;
/// event signature for the IRestOrm.RetrieveAsyncListJson() callback
// - is called with Json = '' on any DB fatal error
TOnRestOrmRetrieveJson = procedure(const Json: RawJson; Context: TObject) of object;
/// event signature for the IRestOrm.RetrieveAsyncListObjArray() callback
// - is called with Values = nil on any DB fatal error
// - caller will clear Values^[] once the callback returns
TOnRestOrmRetrieveArray = procedure(Values: POrmObjArray; Context: TObject) of object;
/// Object-Relational-Mapping calls for CRUD access to a database
// - as implemented in TRest.ORM
// - this is the main abstract entry point to all ORM process, to be used as
// reference to the current TRest instance, without the REST/communication
// particularities
IRestOrm = interface
['{E3C24375-0E44-4C9F-B72C-89DBA8A8A9BD}']
/// get the row count of a specified table
// - returns -1 on error
// - returns the row count of the table on success
// - calls internally the "SELECT Count(*) FROM TableName;" SQL statement
function TableRowCount(Table: TOrmClass): Int64;
/// check if there is some data rows in a specified table
// - calls internally a "SELECT RowID FROM TableName LIMIT 1" SQL statement,
// which is much faster than testing if "SELECT count(*)" equals 0 - see
// @http://stackoverflow.com/questions/8988915
function TableHasRows(Table: TOrmClass): boolean;
/// search for the last inserted ID in a specified table
// - returns -1 on error
// - will execute by default "SELECT max(rowid) FROM TableName"
function TableMaxID(Table: TOrmClass): TID;
/// check if a given ID do exist for a given table
function MemberExists(Table: TOrmClass; ID: TID): boolean;
/// get the UTF-8 encoded value of an unique field with a Where Clause
// - example of use - including inlined parameters via :(...):
// ! aClient.OneFieldValue(TOrm, 'Name', 'ID=:(23):')
// you should better call the corresponding overloaded method as such:
// ! aClient.OneFieldValue(TOrm, 'Name', 'ID=?', [aID])
// which is the same as calling:
// ! aClient.OneFieldValue(TOrm, 'Name', FormatUtf8('ID=?', [], [23]))
// - call internally ExecuteList() to get the value
function OneFieldValue(Table: TOrmClass;
const FieldName, WhereClause: RawUtf8): RawUtf8; overload;
/// get the Int64 value of an unique field with a Where Clause
// - call internally ExecuteList() to get the value
function OneFieldValueInt64(Table: TOrmClass;
const FieldName, WhereClause: RawUtf8; Default: Int64 = 0): Int64;
/// get the UTF-8 encoded value of an unique field with a Where Clause
// - this overloaded function will call FormatUtf8 to create the Where Clause
// from supplied parameters, binding all '?' chars with Args[] values
// - example of use:
// ! aClient.OneFieldValue(TOrm, 'Name', 'ID=?', [aID])
// - call internally ExecuteList() to get the value
// - note that this method prototype changed with revision 1.17 of the
// framework: array of const used to be Args and '%' in the FormatSqlWhere
// statement, whereas it now expects bound parameters as '?'
function OneFieldValue(Table: TOrmClass; const FieldName: RawUtf8;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const): RawUtf8; overload;
/// get the UTF-8 encoded value of an unique field with a Where Clause
// - this overloaded function will call FormatUtf8 to create the Where Clause
// from supplied parameters, replacing all '%' chars with Args[], and all '?'
// chars with Bounds[] (inlining them with :(...): and auto-quoting strings)
// - example of use:
// ! OneFieldValue(TOrm,'Name', '%=?', ['ID'], [aID])
// - call internally ExecuteList() to get the value
function OneFieldValue(Table: TOrmClass; const FieldName: RawUtf8;
const WhereClauseFmt: RawUtf8; const Args, Bounds: array of const): RawUtf8; overload;
/// get one integer value of an unique field with a Where Clause
// - this overloaded function will return the field value as integer
function OneFieldValue(Table: TOrmClass; const FieldName: RawUtf8;
const WhereClauseFmt: RawUtf8; const Args, Bounds: array of const;
out Data: Int64): boolean; overload;
/// get the UTF-8 encoded value of an unique field from its ID
// - example of use: OneFieldValue(TOrm,'Name',23)
// - call internally ExecuteList() to get the value
function OneFieldValue(Table: TOrmClass; const FieldName: RawUtf8;
WhereID: TID): RawUtf8; overload;
/// get the UTF-8 encoded value of some fields with a Where Clause
// - example of use: MultiFieldValue(TOrm,['Name'],Name,'ID=:(23):')
// (using inlined parameters via :(...): is always a good idea)
// - FieldValue[] will have the same length as FieldName[]
// - return true on success, false on SQL error or no result
// - call internally ExecuteList() to get the list
function MultiFieldValue(Table: TOrmClass;
const FieldName: array of RawUtf8; var FieldValue: array of RawUtf8;
const WhereClause: RawUtf8): boolean; overload;
/// get the UTF-8 encoded value of some fields from its ID
// - example of use: MultiFieldValue(TOrm,['Name'],Name,23)
// - FieldValue[] will have the same length as FieldName[]
// - return true on success, false on SQL error or no result
// - call internally ExecuteList() to get the list
function MultiFieldValue(Table: TOrmClass;
const FieldName: array of RawUtf8; var FieldValue: array of RawUtf8;
WhereID: TID): boolean; overload;
/// get the UTF-8 encoded values of an unique field with a Where Clause
// - example of use: OneFieldValue(TOrm,'FirstName','Name=:("Smith"):',Data)
// (using inlined parameters via :(...): is always a good idea)
// - leave WhereClause void to get all records
// - call internally ExecuteList() to get the list
// - returns TRUE on success, FALSE if no data was retrieved
function OneFieldValues(Table: TOrmClass; const FieldName: RawUtf8;
const WhereClause: RawUtf8; out Data: TRawUtf8DynArray): boolean; overload;
/// get the integer value of an unique field with a Where Clause
// - example of use: OneFieldValue(TOrmPeople,'ID','Name=:("Smith"):',Data)
// (using inlined parameters via :(...): is always a good idea)
// - leave WhereClause void to get all records
// - call internally ExecuteList() to get the list
function OneFieldValues(Table: TOrmClass; const FieldName: RawUtf8;
const WhereClause: RawUtf8; var Data: TInt64DynArray;
SQL: PRawUtf8 = nil): boolean; overload;
/// get the CSV-encoded UTF-8 encoded values of an unique field with a Where Clause
// - example of use: OneFieldValue(TOrm,'FirstName','Name=:("Smith")',Data)
// (using inlined parameters via :(...): is always a good idea)
// - leave WhereClause void to get all records
// - call internally ExecuteList() to get the list
// - using inlined parameters via :(...): in WhereClause is always a good idea
function OneFieldValues(Table: TOrmClass; const FieldName: RawUtf8;
const WhereClause: RawUtf8 = ''; const Separator: RawUtf8 = ','): RawUtf8; overload;
/// get the string-encoded values of an unique field into some TStrings
// - Items[] will be filled with string-encoded values of the given field)
// - Objects[] will be filled with pointer(ID)
// - call internally ExecuteList() to get the list
// - returns TRUE on success, FALSE if no data was retrieved
// - if IDToIndex is set (to a true TID variable, not an integer), its value
// will be replaced with the index in Strings.Objects[] where ID=IDToIndex^
// - using inlined parameters via :(...): in WhereClause is always a good idea
function OneFieldValues(Table: TOrmClass; const FieldName, WhereClause: RawUtf8;
Strings: TStrings; IDToIndex: PID = nil): boolean; overload;
/// Execute directly a SQL statement, returning a TOrmTable list of resutls
// - return a TOrmTableJson instance on success, nil on failure
// - FieldNames can be the CSV list of field names to be retrieved
// - if FieldNames is '', will get all simple fields, excluding BLOBs
// - if FieldNames is '*', will get ALL fields, including ID and BLOBs
// - call internally ExecuteList() to get the list
// - using inlined parameters via :(...): in WhereClause is always a good idea
function MultiFieldValues(Table: TOrmClass; const FieldNames: RawUtf8;
const WhereClause: RawUtf8 = ''): TOrmTable; overload;
/// Execute directly a SQL statement, returning a TOrmTable list of resutls
// - return a TOrmTableJson instance on success, nil on failure
// - FieldNames can be the CSV list of field names to be retrieved
// - if FieldNames is '', will get all simple fields, excluding BLOBs
// - if FieldNames is '*', will get ALL fields, including ID and BLOBs
// - this overloaded function will call FormatUtf8 to create the Where Clause
// from supplied parameters, binding all '?' chars with Args[] values
// - example of use:
// ! aList := aClient.MultiFieldValues(
// ! TOrm, 'Name,FirstName', 'Salary>=?', [aMinSalary]);
// - call overloaded MultiFieldValues() / ExecuteList() to get the list
// - note that this method prototype changed with revision 1.17 of the
// framework: array of const used to be Args and '%' in the WhereClauseFormat
// statement, whereas it now expects bound parameters as '?'
function MultiFieldValues(Table: TOrmClass; const FieldNames: RawUtf8;
const WhereClauseFormat: RawUtf8; const BoundsSqlWhere: array of const): TOrmTable; overload;
/// Execute directly a SQL statement, returning a TOrmTable list of resutls
// - return a TOrmTableJson instance on success, nil on failure
// - FieldNames can be the CSV list of field names to be retrieved
// - if FieldNames is '', will get all simple fields, excluding BLOBs
// - if FieldNames is '*', will get ALL fields, including ID and BLOBs
// - in this version, the WHERE clause can be created with the same format
// as FormatUtf8() function, replacing all '%' chars with Args[], and all '?'
// chars with Bounds[] (inlining them with :(...): and auto-quoting strings)
// - example of use:
// ! Table := MultiFieldValues(TOrm, 'Name', '%=?', ['ID'], [aID]);
// - call overloaded MultiFieldValues() / ExecuteList() to get the list
function MultiFieldValues(Table: TOrmClass; const FieldNames: RawUtf8;
const WhereClauseFormat: RawUtf8; const Args, Bounds: array of const): TOrmTable; overload;
/// dedicated method used to retrieve free-text matching DocIDs
// - this method works for TOrmFts3, TOrmFts4 and TOrmFts5
// - this method expects the column/field names to be supplied in the MATCH
// statement clause
// - example of use: FtsMatch(TOrmMessage,'Body MATCH :("linu*"):',IntResult)
// (using inlined parameters via :(...): is always a good idea)
function FtsMatch(Table: TOrmFts3Class; const WhereClause: RawUtf8;
var DocID: TIDDynArray): boolean; overload;
/// dedicated method used to retrieve free-text matching DocIDs with
// enhanced ranking information
// - this method works for TOrmFts3, TOrmFts4 and TOrmFts5
// - this method will search in all FTS3 columns, and except some floating-point
// constants for weigthing each column (there must be the same number of
// PerFieldWeight parameters as there are columns in the TOrmFts3 table)
// - example of use: FtsMatch(TOrmDocuments,'"linu*"',IntResult,[1,0.5])
// which will sort the results by the rank obtained with the 1st column/field
// beeing given twice the weighting of those in the 2nd (and last) column
// - FtsMatch(TOrmDocuments,'linu*',IntResult,[1,0.5]) will perform a
// SQL query as such, which is the fastest way of ranking according to
// http://www.sqlite.org/fts3.html#appendix_a
// $ SELECT RowID FROM Documents WHERE Documents MATCH 'linu*'
// $ ORDER BY rank(matchinfo(Documents),1.0,0.5) DESC
function FtsMatch(Table: TOrmFts3Class; const MatchClause: RawUtf8;
var DocID: TIDDynArray; const PerFieldWeight: array of double;
limit: integer = 0; offset: integer = 0): boolean; overload;
/// retrieve the main field (mostly 'Name') value of the specified record
// - use GetMainFieldName() method to get the main field name
// - use OneFieldValue() method to get the field value
// - return '' if no such field or record exists
// - if ReturnFirstIfNoUnique is TRUE and no unique property is found,
// the first RawUtf8 property is returned anyway
function MainFieldValue(Table: TOrmClass; ID: TID;
ReturnFirstIfNoUnique: boolean = false): RawUtf8;
/// return the ID of the record which main field match the specified value
// - search field is mainly the "Name" property, i.e. the one with
// "stored AS_UNIQUE" (i.e. "stored false") definition on most TOrm
// - returns 0 if no matching record was found }
function MainFieldID(Table: TOrmClass; const Value: RawUtf8): TID;
/// return the IDs of the record which main field match the specified values
// - search field is mainly the "Name" property, i.e. the one with
// "stored AS_UNIQUE" (i.e. "stored false") definition on most TOrm
// - if any of the Values[] is not existing, then no ID will appear in the
// IDs[] array - e.g. it will return [] if no matching record was found
// - returns TRUE if any matching ID was found (i.e. if length(IDs)>0) }
function MainFieldIDs(Table: TOrmClass; const Values: array of RawUtf8;
out IDs: TIDDynArray): boolean;
/// get a member from a SQL statement
// - implements REST GET collection
// - return true on success
// - Execute 'SELECT * FROM TableName WHERE SqlWhere LIMIT 1' SQL Statememt
// (using inlined parameters via :(...): in SqlWhere is always a good idea)
// - since no record is specified, locking is pointless here
// - default implementation call ExecuteList(), and fill Value from a
// temporary TOrmTable
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// and TOrmMany fields (use RetrieveBlob method or set
// TRestClientUri.ForceBlobTransfert)
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - if this default set of simple fields does not fit your need, you could
// specify your own set
function Retrieve(const SqlWhere: RawUtf8; Value: TOrm;
const FieldsCsv: RawUtf8 = ''): boolean; overload;
/// get a member from a SQL statement
// - implements REST GET collection
// - return true on success
// - same as Retrieve(const SqlWhere: RawUtf8; Value: TOrm) method, but
// this overloaded function will call FormatUtf8 to create the Where Clause
// from supplied parameters, replacing all '%' chars with Args[], and all '?'
// chars with Bounds[] (inlining them with :(...): and auto-quoting strings)
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
function Retrieve(const WhereClauseFmt: RawUtf8;
const Args, Bounds: array of const; Value: TOrm;
const FieldsCsv: RawUtf8 = ''): boolean; overload;
/// get a member from its ID
// - return true on success
// - Execute 'SELECT * FROM TableName WHERE ID=:(aID): LIMIT 1' SQL Statememt
// - if ForUpdate is true, the REST method is LOCK and not GET: it tries to lock
// the corresponding record, then retrieve its content; caller has to call
// UnLock() method after Value usage, to release the record
// - this method will call EngineRetrieve() abstract method
// - the RawBlob (BLOB) fields are not retrieved by this method, to
// preserve bandwidth: use the RetrieveBlob() methods for handling
// BLOB fields, or set either the TRestClientUri.ForceBlobTransfert
// or TRestClientUri.ForceBlobTransfertTable[] properties
// - the TOrmMany fields are not retrieved either: they are separate
// instances created by TOrmMany.Create, with dedicated methods to
// access to the separated pivot table
function Retrieve(aID: TID; Value: TOrm;
ForUpdate: boolean = false): boolean; overload;
/// get a member from its TRecordReference property content
// - instead of the other Retrieve() methods, this implementation Create an
// instance, with the appropriated class stored in Reference
// - returns nil on any error (invalid Reference e.g.)
// - if ForUpdate is true, the REST method is LOCK and not GET: it tries to lock
// the corresponding record, then retrieve its content; caller has to call
// UnLock() method after Value usage, to release the record
// - the RawBlob (BLOB) fields are not retrieved by this method, to
// preserve bandwidth: use the RetrieveBlob() methods for handling
// BLOB fields, or set either the TRestClientUri.ForceBlobTransfert
// or TRestClientUri.ForceBlobTransfertTable[] properties
// - the TOrmMany fields are not retrieved either: they are separate
// instances created by TOrmMany.Create, with dedicated methods to
// access to the separated pivot table
function Retrieve(Reference: TRecordReference;
ForUpdate: boolean = false): TOrm; overload;
/// get a member from a published property TOrm
// - those properties are not class instances, but TObject(aRecordID)
// - is just a wrapper around Retrieve(aPublishedRecord.ID,aValue)
// - return true on success
function Retrieve(aPublishedRecord, aValue: TOrm): boolean; overload;
/// get a member from some SQL, calling a callback when the result is ready
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - raise EOrmAsyncException if the external DB does not support asynch requests
procedure RetrieveAsync(Context: TObject; Table: TOrmClass; const SqlWhere: RawUtf8;
const OnResult: TOnRestOrmRetrieveOne; const FieldsCsv: RawUtf8 = ''); overload;
/// get a member from some SQL, calling a callback when the result is ready
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - raise EOrmAsyncException if the external DB does not support asynch requests
procedure RetrieveAsync(Context: TObject; Table: TOrmClass;
const WhereClauseFmt: RawUtf8; const Args, Bounds: array of const;
const OnResult: TOnRestOrmRetrieveOne; const FieldsCsv: RawUtf8 = ''); overload;
/// get a member from its ID, calling a callback when the result is ready
// - Execute 'SELECT * FROM TableName WHERE ID=:(ID): LIMIT 1' SQL Statememt
// - in difference to Retrieve(ID), won't try the cache
// - raise EOrmAsyncException if the external DB does not support asynch requests
procedure RetrieveAsync(Context: TObject; Table: TOrmClass; ID: TID;
const OnResult: TOnRestOrmRetrieveOne); overload;
/// get some members from some SQL, calling a callback when the JSON is ready
// - raise EOrmAsyncException if the external DB does not support asynch requests
procedure RetrieveAsyncListJson(Context: TObject; Table: TOrmClass;
const SqlWhere: RawUtf8; const OnResult: TOnRestOrmRetrieveJson;
const FieldsCsv: RawUtf8 = ''; aForceAjax: boolean = false); overload;
/// get some members from some SQL, calling a callback with a TOrm array
// - raise EOrmAsyncException if the external DB does not support asynch requests
procedure RetrieveAsyncListObjArray(Context: TObject; Table: TOrmClass;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const;
const OnResult: TOnRestOrmRetrieveArray; const FieldsCsv: RawUtf8 = '');
/// get a known TOrm instance JSON representation
// - a slightly faster alternative to Value.GetJsonValues
procedure GetJsonValue(Value: TOrm; withID: boolean;
const Fields: TFieldBits; out Json: RawUtf8); overload;
/// get a known TOrm instance JSON representation
// - a slightly faster alternative to Value.GetJsonValues
procedure GetJsonValue(Value: TOrm; withID: boolean;
Occasion: TOrmOccasion; var Json: RawUtf8); overload;
/// get a list of members from a SQL statement as TObjectList
// - implements REST GET collection
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql()/DateTimeToSql() for TDateTime, or directly any integer,
// double, currency, RawUtf8 values to be bound to the request as parameters
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - return a TObjectList on success (possibly with Count=0) - caller is
// responsible of freeing the instance
// - this TObjectList will contain a list of all matching records
// - return nil on error
function RetrieveList(Table: TOrmClass;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const;
const FieldsCsv: RawUtf8 = ''): TObjectList; overload;
{$ifdef ORMGENERICS}
/// get a IList<TOrm> of members from a SQL statement
// - implements REST GET collection
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - return true and a IList<T> in Result on success (maybe with Count=0)
// - return false on error
// - untyped "var IList" and not directly IList<T: TOrm> because neither
// Delphi nor FPC allow parametrized interface methods
// - our IList<> and IKeyValue<> interfaces are faster and generates smaller
// executables than Generics.Collections, and need no try..finally Free: a
// single TIList<TOrm> class will be reused for all IList<>
// - you can write for instance:
// !var
// ! list: IList<TOrmTest>;
// ! R: TOrmTest;
// ! orm: IRestOrm
// ! ...
// ! if orm.RetrieveIList(TOrmTest, list, 'ID,Test') then
// ! for R in list do
// ! writeln(R.ID, '=', R.Test);
function RetrieveIList(T: TOrmClass; var IList;
const FieldsCsv: RawUtf8 = ''): boolean; overload;
/// get a IList<TOrm> of members from a SQL statement
// - implements REST GET collection with a WHERE clause
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql()/DateTimeToSql() for TDateTime, or directly any integer,
// double, currency, RawUtf8 values to be bound to the request as parameters
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - return true and a IList<T> in Result on success (maybe with Count=0)
// - return false on error
// - untyped "var IList" and not directly IList<T: TOrm> because neither
// Delphi nor FPC allow parametrized interface methods
function RetrieveIList(T: TOrmClass; var IList;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const;
const FieldsCsv: RawUtf8 = ''): boolean; overload;
{$endif ORMGENERICS}
/// get a list of members from a SQL statement as RawJson
// - implements REST GET collection
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql()/DateTimeToSql() for TDateTime, or directly any integer,
// double, currency, RawUtf8 values to be bound to the request as parameters
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - returns the raw JSON array content with all items on success, with
// our expanded / not expanded JSON format - so can be used with SOA methods
// and RawJson results, for direct process from the client side
// - returns '' on error
// - the data is directly retrieved from raw JSON as returned by the database
// without any conversion, so this method will be the fastest, but complex
// types like dynamic array will be returned as Base64-encoded blob value -
// if you need proper JSON access to those, see RetrieveDocVariantArray()
function RetrieveListJson(Table: TOrmClass;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const;
const FieldsCsv: RawUtf8 = ''; aForceAjax: boolean = false): RawJson; overload;
/// get a list of members from a SQL statement as RawJson
// - implements REST GET collection
// - this overloaded version expect the SqlWhere clause to be already
// prepared with inline parameters using a previous FormatUtf8() call
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - returns the raw JSON array content with all items on success, with
// our expanded / not expanded JSON format - so can be used with SOA methods
// and RawJson results, for direct process from the client side
// - returns '' on error
// - the data is directly retrieved from raw JSON as returned by the database
// without any conversion, so this method will be the fastest, but complex
// types like dynamic array will be returned as Base64-encoded blob value -
// if you need proper JSON access to those, see RetrieveDocVariantArray()
function RetrieveListJson(Table: TOrmClass;
const SqlWhere: RawUtf8; const FieldsCsv: RawUtf8 = '';
aForceAjax: boolean = false): RawJson; overload;
/// get a list of all members from a SQL statement as a TDocVariant
// - implements REST GET collection
// - if ObjectName='', it will return a TDocVariant of dvArray kind
// - if ObjectName is set, it will return a TDocVariant of dvObject kind,
// with one property containing the array of values: this returned variant
// can be pasted e.g. directly as parameter to TSynMustache.Render()
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - the data will be converted to variants and TDocVariant following the
// TOrm layout, so complex types like dynamic array will be returned
// as a true array of values (in contrast to the RetrieveListJson method)
// - warning: under FPC, we observed that assigning the result of this
// method to a local variable may circumvent a memory leak FPC bug
// - warning: FirstRecordID/LastRecordID should be true TID variables, not integer
function RetrieveDocVariantArray(Table: TOrmClass;
const ObjectName, FieldsCsv: RawUtf8;
FirstRecordID: PID = nil; LastRecordID: PID = nil): variant; overload;
/// get a list of members from a SQL statement as a TDocVariant
// - implements REST GET collection over a specified WHERE clause
// - if ObjectName='', it will return a TDocVariant of dvArray kind
// - if ObjectName is set, it will return a TDocVariant of dvObject kind,
// with one property containing the array of values: this returned variant
// can be pasted e.g. directly as parameter to TSynMustache.Render()
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql()/DateTimeToSql() for TDateTime, or directly any integer,
// double, currency, RawUtf8 values to be bound to the request as parameters
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - the data will be converted to variants and TDocVariant following the
// TOrm layout, so complex types like dynamic array will be returned
// as a true array of values (in contrast to the RetrieveListJson method)
// - warning: under FPC, we observed that assigning the result of this
// method to a local variable may circumvent a memory leak FPC bug
// - warning: FirstRecordID/LastRecordID should be true TID variables, not integer
function RetrieveDocVariantArray(Table: TOrmClass;
const ObjectName: RawUtf8; const FormatSqlWhere: RawUtf8;
const BoundsSqlWhere: array of const; const FieldsCsv: RawUtf8;
FirstRecordID: PID = nil; LastRecordID: PID = nil): variant; overload;
/// get all values of a SQL statement on a single column as a TDocVariant array
// - implements REST GET collection on a single field
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql()/DateTimeToSql() for TDateTime, or directly any integer,
// double, currency, RawUtf8 values to be bound to the request as parameters
// - the data will be converted to variants and TDocVariant following the
// TOrm layout, so complex types like dynamic array will be returned
// as a true array of values (in contrast to the RetrieveListJson method)
function RetrieveOneFieldDocVariantArray(Table: TOrmClass;
const FieldName, FormatSqlWhere: RawUtf8;
const BoundsSqlWhere: array of const): variant;
/// get one member from a SQL statement as a TDocVariant
// - implements REST GET collection
// - the data will be converted to a TDocVariant variant following the
// TOrm layout, so complex types like dynamic array will be returned
// as a true array of values
function RetrieveDocVariant(Table: TOrmClass;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const;
const FieldsCsv: RawUtf8): variant;
/// get a list of members from a SQL statement as T*ObjArray
// - implements REST GET collection
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql()/DateTimeToSql() for TDateTime, or directly any integer,
// double, currency, RawUtf8 values to be bound to the request as parameters
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - set the T*ObjArray variable with all items on success - so that it can
// be used with SOA methods
// - it is up to the caller to ensure that ObjClear(ObjArray) is called
// when the T*ObjArray list is not needed any more
// - returns true on success, false on error
function RetrieveListObjArray(var ObjArray; Table: TOrmClass;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const;
const FieldsCsv: RawUtf8 = ''): boolean;
/// get and append a list of members as an expanded JSON array
// - implements REST GET collection
// - generates '[{rec1},{rec2},...]' using a loop similar to:
// ! while FillOne do .. AppendJsonObject() ..
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql()/DateTimeToSql() for TDateTime, or directly any integer,
// double, currency, RawUtf8 values to be bound to the request as parameters
// - if OutputFieldName is set, the JSON array will be written as a JSON
// property i.e. as '"OutputFieldName":[....],' - note the ending ','
// - FieldsCsv can be the CSV list of field names to be retrieved
// - if FieldsCsv is '', will get all simple fields, excluding BLOBs
// - if FieldsCsv is '*', will get ALL fields, including ID and BLOBs
// - is just a wrapper around TOrm.AppendFillAsJsonArray()
procedure AppendListAsJsonArray(Table: TOrmClass;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const;
const OutputFieldName: RawUtf8; W: TOrmWriter;
const FieldsCsv: RawUtf8 = '');
/// dedicated method used to retrieve matching IDs using a fast R-Tree index
// - a TOrmRTree is associated to a TOrm with a specified BLOB
// field, and will call TOrmRTree BlobToCoord and ContainedIn virtual
// class methods to execute an optimized SQL query
// - as alternative, with SQLite3 >= 3.24.0, you may use Auxiliary Columns
// - will return all matching DataTable IDs in DataID[]
// - will generate e.g. the following statement
// $ SELECT MapData.ID From MapData, MapBox WHERE MapData.ID=MapBox.ID
// $ AND minX>=:(-81.0): AND maxX<=:(-79.6): AND minY>=:(35.0): AND :(maxY<=36.2):
// $ AND MapBox_in(MapData.BlobField,:('\uFFF0base64encoded-81,-79.6,35,36.2'):);
// when the following Delphi code is executed:
// ! aClient.RTreeMatch(TOrmMapData, 'BlobField', TOrmMapBox,
// ! aMapData.BlobField, ResultID);
function RTreeMatch(DataTable: TOrmClass;
const DataTableBlobFieldName: RawUtf8; RTreeTable: TOrmRTreeClass;
const DataTableBlobField: RawByteString; var DataID: TIDDynArray): boolean;
/// Execute directly a SQL statement, returning a TOrmTableJson resultset
// - you should not have to use this method, but the ORM versions instead;
// it may give expected results only with our direct SQLite3 or our in-memory
// engines; with external databases, it may involve SQlite3 virtual tables,
// and fields name re-mapping, so the TOrmTable result may be unexpected
// - return a result table on success, nil on failure
// - will actually fill a TOrmTableJson from ExecuteJson() results
function ExecuteList(const Tables: array of TOrmClass;
const SQL: RawUtf8): TOrmTable;
/// Execute directly a SQL statement, returning its results as JSON
// - you should not have to use this method, but the ORM versions instead;
// it may give expected results only with our direct SQLite3 or our in-memory
// engines; with external databases, it may involve SQlite3 virtual tables,
// and fields renaming, so the JSON result may not be what you would expect
// - return a result set as JSON on success, '' on failure
// - will call EngineList() abstract method to retrieve its JSON content
// - note that ReturnedRowCount should be either nil or a true PtrInt variable
// (not a plain integer nor Int64) to avoid GPF or invalid numbers
function ExecuteJson(const Tables: array of TOrmClass;
const SQL: RawUtf8; ForceAjax: boolean = false;
ReturnedRowCount: PPtrInt = nil): RawJson;
/// Execute directly a SQL statement, without any expected result
// - implements POST SQL on ModelRoot URI
// - return true on success
// - will call EngineExecute() abstract method to run the SQL statement
function Execute(const aSql: RawUtf8): boolean;
/// Execute directly a SQL statement with supplied parameters, with no result
// - expect the same format as FormatUtf8() function, replacing all '%' chars
// with Args[] values
// - return true on success
function ExecuteFmt(const SqlFormat: RawUtf8;
const Args: array of const): boolean; overload;
/// Execute directly a SQL statement with supplied parameters, with no result
// - expect the same format as FormatUtf8() function, replacing all '%' chars
// with Args[] values, and all '?' chars with Bounds[] (inlining them
// with :(...): and auto-quoting strings)
// - return true on success
function ExecuteFmt(const SqlFormat: RawUtf8;
const Args, Bounds: array of const): boolean; overload;
/// unlock the corresponding record
// - record should have been locked previously e.g. with Retrieve() and
// forupdate=true, i.e. retrieved not via GET with LOCK REST-like verb
// - use our custom UNLOCK REST-like verb
// - returns true on success
function UnLock(Table: TOrmClass; aID: TID): boolean; overload;
/// unlock the corresponding record
// - record should have been locked previously e.g. with Retrieve() and
// forupdate=true, i.e. retrieved not via GET with LOCK REST-like verb
// - use our custom UNLOCK REST-like method
// - calls internally UnLock() above
// - returns true on success
function UnLock(Rec: TOrm): boolean; overload;
/// create a new member
// - implements REST POST collection
// - if SendData is true, client sends the current content of Value with the
// request, otherwise record is created with default values
// - if ForceID is true, client sends the Value.ID field to use this ID for
// adding the record (instead of a database-generated ID)
// - on success, returns the new RowID value; on error, returns 0
// - on success, Value.ID is updated with the new RowID
// - the RawBlob(BLOB) fields values are not set by this method, to
// preserve bandwidth - see UpdateBlobFields() and AddWithBlobs() methods
// - the TOrmMany fields are not set either: they are separate
// instances created by TOrmMany.Create, with dedicated methods to
// access to the separated pivot table
// - this method will call EngineAdd() to perform the request
function Add(Value: TOrm; SendData: boolean;
ForceID: boolean = false; DoNotAutoComputeFields: boolean = false): TID; overload;
/// create a new member, including selected fields
// - implements REST POST collection
// - if ForceID is true, client sends the Value.ID field to use this ID for
// adding the record (instead of a database-generated ID)
// - this method will call EngineAdd() to perform the request
function Add(Value: TOrm; const CustomCsvFields: RawUtf8;
ForceID: boolean = false; DoNotAutoComputeFields: boolean = false): TID; overload;
/// create a new member, including selected fields
// - implements REST POST collection
// - if ForceID is true, client sends the Value.ID field to use this ID for
// adding the record (instead of a database-generated ID)
// - this method will call EngineAdd() to perform the request
function Add(Value: TOrm; const CustomFields: TFieldBits;
ForceID: boolean = false; DoNotAutoComputeFields: boolean = false): TID; overload;
/// create a new member, including its BLOB fields
// - implements REST POST collection
// - this method will create a JSON representation of the document
// including the BLOB fields as Base64 encoded text, so will be less
// efficient than a dual Add() + UpdateBlobFields() methods if the
// binary content has a non trivial size
// - this method will call EngineAdd() to perform the request
function AddWithBlobs(Value: TOrm;
ForceID: boolean = false; DoNotAutoComputeFields: boolean = false): TID;
/// create a new member, from a supplied list of field values
// - implements REST POST collection
// - the aSimpleFields parameters must follow explicitly the order of
// published properties of the supplied aTable class, excepting the RawBlob
// and TOrmMany kind (i.e. only so called "simple fields")
// - the aSimpleFields must have exactly the same count of parameters as
// there are "simple fields" in the published properties
// - if ForcedID is set to non null, client sends this ID to be used
// when adding the record (instead of a database-generated ID)
// - on success, returns the new RowID value; on error, returns 0
// - call internally the Add virtual method above
function AddSimple(aTable: TOrmClass;
const aSimpleFields: array of const; ForcedID: TID = 0): TID;
/// update a member from Value simple fields content
// - implements REST PUT collection
// - return true on success
// - the RawBlob(BLOB) fields values are not updated by this method, to
// preserve bandwidth: use the UpdateBlob() methods for handling BLOB fields
// - the TOrmMany fields are not set either: they are separate
// instances created by TOrmMany.Create, with dedicated methods to
// access to the separated pivot table
// - if CustomFields is left void, the simple fields will be used, or the
// fields retrieved via a previous FillPrepare() call; otherwise, you can
// specify your own set of fields to be transmitted (including BLOBs, even
// if they will be Base64-encoded within the JSON content) - CustomFields
// could be computed by TOrmProperties.FieldBitsFromCsv()
// or TOrmProperties.FieldBitsFrom()
// - this method will always compute and send any TModTime fields
// - this method will call EngineUpdate() to perform the request
function Update(Value: TOrm; const CustomFields: TFieldBits = [];
DoNotAutoComputeFields: boolean = false): boolean; overload;
/// update a member from Value simple fields content
// - implements REST PUT collection
// - return true on success
// - is an overloaded method to Update(Value,FieldBitsFromCsv())
// - note that by design 'ID' should not be included within CustomCsvFields
function Update(Value: TOrm; const CustomCsvFields: RawUtf8;
DoNotAutoComputeFields: boolean = false): boolean; overload;
/// update a member from a supplied list of simple field values
// - implements REST PUT collection
// - the aSimpleFields parameters MUST follow explicitly both count and
// order of published properties of the supplied aTable class, excepting the
// RawBlob and TOrmMany kind (i.e. only so called "simple fields")
// - return true on success
// - call internally the Update() / EngineUpdate() virtual methods
function Update(aTable: TOrmClass; aID: TID;
const aSimpleFields: array of const): boolean; overload;
/// create or update a member, depending if the Value has already an ID
// - implements REST POST if Value.ID=0 or ForceID is set, or a REST PUT
// collection to update the record pointed by a Value.ID<>0
// - will return the created or updated ID
function AddOrUpdate(Value: TOrm; ForceID: boolean = false): TID;
/// update one field/column value a given member
// - implements REST PUT collection with one field value
// - only one single field shall be specified in FieldValue, but could
// be of any kind of value - for BLOBs, you should better use UpdateBlob()
// - return true on success
// - call internally the EngineUpdateField() abstract method
// - note that this method won't update the TModTime properties: you should
// rather use a classic Retrieve()/FillPrepare() followed by Update(); but
// it will notify the internal Cache
function UpdateField(Table: TOrmClass; ID: TID;
const FieldName: RawUtf8; const FieldValue: array of const): boolean; overload;
/// update one field in one or several members, depending on a WHERE clause
// - implements REST PUT collection with one field value on a one where value
// - only one single field shall be specified in FieldValue, but could
// be of any kind of value - for BLOBs, you should better use UpdateBlob()
// - only one single field shall be specified in WhereFieldValue, but could
// be of any kind of value
// - warning: void WHERE clause won't be rejected, but interpreted as a
// "UPDATE ... from ...", i.e. modifying ALL rows of the table
// - return true on success
// - call internally the EngineUpdateField() abstract method
// - note that this method won't update the TModTime properties: you should
// rather use a classic Retrieve()/FillPrepare() followed by Update(); but
// it will notify the internal Cache
function UpdateField(Table: TOrmClass; const WhereFieldName: RawUtf8;
const WhereFieldValue: array of const; const FieldName: RawUtf8;
const FieldValue: array of const): boolean; overload;
/// update one field in a given member with a value specified as variant
// - implements REST PUT collection with one field value
// - any value can be set in FieldValue, but for BLOBs, you should better
// use UpdateBlob()
// - return true on success
// - call internally the EngineUpdateField() abstract method
// - note that this method won't update the TModTime properties: you should
// rather use a classic Retrieve()/FillPrepare() followed by Update(); but
// it will notify the internal Cache
function UpdateField(Table: TOrmClass; ID: TID;
const FieldName: RawUtf8; const FieldValue: variant): boolean; overload;
/// update one field in one or several members, depending on a WHERE clause,
// with both update and where values specified as variant
// - implements REST PUT collection with one field value on a one where value
// - any value can be set in FieldValue, but for BLOBs, you should better
// use UpdateBlob()
// - warning: void WHERE clause won't be rejected, but interpreted as a
// "UPDATE ... from ...", i.e. modifying ALL rows of the table
// - return true on success
// - call internally the EngineUpdateField() abstract method
// - note that this method won't update the TModTime properties, nor the
// internal table Cache: you should rather use a classic Retrieve()/FillPrepare()
// followed by an Update() of the whole record
function UpdateField(Table: TOrmClass;
const WhereFieldName: RawUtf8; const WhereFieldValue: variant;
const FieldName: RawUtf8; const FieldValue: variant): boolean; overload;
/// update one field in one or several members, depending on a set of IDs
// - return true on success
// - note that this method won't update the TModTime properties: you should
// rather use a classic Retrieve()/FillPrepare() followed by Update(), but
// it will be much slower, even over a BATCH; anyway, it will update the
// internal Cache
// - will be executed as a regular SQL statement:
// $ UPDATE table SET fieldname=fieldvalue WHERE RowID IN (...)
// - warning: this method will call directly EngineExecute(), and will
// work just fine with SQLite3, but some other DB engines may not allow
// a huge number of items within the IN(...) clause
function UpdateFieldAt(Table: TOrmClass; const IDs: array of TID;
const FieldName: RawUtf8; const FieldValue: variant): boolean;
/// increments one integer field value
// - if available, this method will use atomic value modification, e.g.
// $ UPDATE table SET field=field+?
function UpdateFieldIncrement(Table: TOrmClass; ID: TID;
const FieldName: RawUtf8; Increment: Int64 = 1): boolean;
/// override this method to guess if this record can be updated or deleted
// - this default implementation returns always true
// - e.g. you can add digital signature to a record to disallow record editing
// - the ErrorMsg can be set to a variable, which will contain an explicit
// error message
function RecordCanBeUpdated(Table: TOrmClass; ID: TID;
Action: TOrmEvent; ErrorMsg: PRawUtf8 = nil): boolean;
/// delete a member
// - implements REST DELETE collection
// - return true on success
// - call internally the EngineDelete() abstract method
function Delete(Table: TOrmClass; ID: TID): boolean; overload;
/// delete a member with a WHERE clause
// - implements REST DELETE collection
// - return true on success
// - this default method call OneFieldValues() to retrieve all matching IDs,
// then will delete each row using protected EngineDeleteWhere() virtual method
function Delete(Table: TOrmClass; const SqlWhere: RawUtf8): boolean; overload;
/// delete a member with a WHERE clause
// - implements REST DELETE collection
// - return true on success
// - for better server speed, the WHERE clause should use bound parameters
// identified as '?' in the FormatSqlWhere statement, which is expected to
// follow the order of values supplied in BoundsSqlWhere open array - use
// DateToSql/DateTimeToSql for TDateTime, or directly any integer / double /
// currency / RawUtf8 values to be bound to the request as parameters
// - is a simple wrapper around:
// ! Delete(Table, FormatUtf8(FormatSqlWhere, [], BoundsSqlWhere))
function Delete(Table: TOrmClass;
const FormatSqlWhere: RawUtf8; const BoundsSqlWhere: array of const): boolean; overload;
/// get a blob field content from its record ID and supplied blob field name
// - implements REST GET collection with a supplied member ID and a blob field name
// - return true on success
// - this method is defined as abstract, i.e. there is no default implementation:
// it must be implemented 100% RestFul with a
// GET ModelRoot/TableName/TableID/BlobFieldName request for example
// - this method retrieve the blob data as a RawBlob string using
// EngineRetrieveBlob()
function RetrieveBlob(Table: TOrmClass;
aID: TID; const BlobFieldName: RawUtf8;
out BlobData: RawBlob): boolean; overload;
/// get a blob field content from its record ID and supplied blob field name
// - implements REST GET collection with a supplied member ID and field name
// - return true on success
// - this method will create a TStream instance (which must be freed by the
// caller after use) and fill it with the blob data
function RetrieveBlob(Table: TOrmClass;
aID: TID; const BlobFieldName: RawUtf8;
out BlobStream: TCustomMemoryStream): boolean; overload;
/// update a blob field from its record ID and supplied blob field name
// - implements REST PUT collection with a supplied member ID and field name
// - return true on success
// - call internally the EngineUpdateBlob() abstract method