mORMot and Open Source friends
Check-in [a0eb5fb484]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:{898} BREAKING CHANGE: constant text parameter defined as PUTF8Char in FormatUTF8() TTextWriter.Add(Format) and defined as PWinAnsiChar in TSynLog.Log() methods have been changed into RawUTF8, to let the compiler handle any Unicode content as expected
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a0eb5fb48408685a5cdf0b23f0f267344cc2f97d
User & Date: ab 2015-02-14 10:10:10
Context
2015-02-14
10:52
{899} remove some code which is not necessary any more with Delphi 5 since we now use RawUTF8 instead of PWinAnsiChar/PUTF8Char check-in: c4ea5f0e13 user: ab tags: trunk
10:10
{898} BREAKING CHANGE: constant text parameter defined as PUTF8Char in FormatUTF8() TTextWriter.Add(Format) and defined as PWinAnsiChar in TSynLog.Log() methods have been changed into RawUTF8, to let the compiler handle any Unicode content as expected check-in: a0eb5fb484 user: ab tags: trunk
2015-02-13
14:43
{897} ensure SQL generated by the ORM for external databases will use 'as ID' for the primary key alias since 'as RowID' may be reserved by the dialect (e.g. for Oracle) check-in: 272e1d0937 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/Samples/30 - MVC Server/MVCViewModel.pas.

279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
  if scop^.GetAsRawUTF8('match',match) and fHasFTS then begin
    if scop^.GetAsDouble('lastrank',rank) then
      whereClause := 'and rank<? ';
    whereClause := 'join (select docid,rank(matchinfo(ArticleSearch),1) as rank '+
      'from ArticleSearch where ArticleSearch match ? '+whereClause+
      'order by rank desc'+ARTICLE_DEFAULT_LIMIT+')as r on (r.docid=Article.id)';
    articles := RestModel.RetrieveDocVariantArray(
      TSQLArticle,'',pointer(whereClause),[match,rank],
      'id,title,tags,author,authorname,createdat,abstract,contenthtml,rank');
    with DocVariantDataSafe(articles)^do
      if (Kind=dvArray) and (Count>0) then
        rank := Values[Count-1].rank else
        rank := 0;
    scope := _ObjFast(['Articles',articles,'lastrank',rank,'match',match]);
    exit;
................................................................................
      // uses custom function to search in BLOB
      whereClause := whereClause+' and IntegerDynArrayContains(Tags,?)';
  end;
  SetVariantNull(Scope);
  if (lastID=0) and (tag=0) then begin // use simple cache if no parameters
    if not fDefaultData.AddExistingProp('Articles',Scope) then
      fDefaultData.AddNewProp('Articles',RestModel.RetrieveDocVariantArray(
        TSQLArticle,'',pointer(ARTICLE_DEFAULT_ORDER),[],
        ARTICLE_FIELDS,nil,@fDefaultLastID),Scope);
    lastID := fDefaultLastID;
  end else // use more complex request using lastID + tag parameters
    scope := _ObjFast(['Articles',RestModel.RetrieveDocVariantArray(
        TSQLArticle,'',Pointer(whereClause+ARTICLE_DEFAULT_ORDER),[lastID,tag],
        ARTICLE_FIELDS,nil,@lastID)]);
  if lastID>1 then begin
    _ObjAddProps(['lastID',lastID],Scope);
    _ObjAddProps(['tag',tag],Scope); // should be there for a valid Scope object 
  end;
end;







|







 







|




|







279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
  if scop^.GetAsRawUTF8('match',match) and fHasFTS then begin
    if scop^.GetAsDouble('lastrank',rank) then
      whereClause := 'and rank<? ';
    whereClause := 'join (select docid,rank(matchinfo(ArticleSearch),1) as rank '+
      'from ArticleSearch where ArticleSearch match ? '+whereClause+
      'order by rank desc'+ARTICLE_DEFAULT_LIMIT+')as r on (r.docid=Article.id)';
    articles := RestModel.RetrieveDocVariantArray(
      TSQLArticle,'',whereClause,[match,rank],
      'id,title,tags,author,authorname,createdat,abstract,contenthtml,rank');
    with DocVariantDataSafe(articles)^do
      if (Kind=dvArray) and (Count>0) then
        rank := Values[Count-1].rank else
        rank := 0;
    scope := _ObjFast(['Articles',articles,'lastrank',rank,'match',match]);
    exit;
................................................................................
      // uses custom function to search in BLOB
      whereClause := whereClause+' and IntegerDynArrayContains(Tags,?)';
  end;
  SetVariantNull(Scope);
  if (lastID=0) and (tag=0) then begin // use simple cache if no parameters
    if not fDefaultData.AddExistingProp('Articles',Scope) then
      fDefaultData.AddNewProp('Articles',RestModel.RetrieveDocVariantArray(
        TSQLArticle,'',ARTICLE_DEFAULT_ORDER,[],
        ARTICLE_FIELDS,nil,@fDefaultLastID),Scope);
    lastID := fDefaultLastID;
  end else // use more complex request using lastID + tag parameters
    scope := _ObjFast(['Articles',RestModel.RetrieveDocVariantArray(
        TSQLArticle,'',whereClause+ARTICLE_DEFAULT_ORDER,[lastID,tag],
        ARTICLE_FIELDS,nil,@lastID)]);
  if lastID>1 then begin
    _ObjAddProps(['lastID',lastID],Scope);
    _ObjAddProps(['tag',tag],Scope); // should be there for a valid Scope object 
  end;
end;

Changes to SQLite3/mORMot.pas.

708
709
710
711
712
713
714


715
716
717
718
719
720
721
....
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
....
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
....
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
....
5376
5377
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
....
5424
5425
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
....
5445
5446
5447
5448
5449
5450
5451
5452
5453
5454
5455
5456
5457
5458
5459
....
5537
5538
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
....
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
5762
5763
....
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
....
5823
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
....
8560
8561
8562
8563
8564
8565
8566
8567
8568
8569
8570
8571
8572
8573
8574
....
8841
8842
8843
8844
8845
8846
8847
8848
8849
8850
8851
8852
8853
8854
8855
....
9124
9125
9126
9127
9128
9129
9130
9131
9132
9133
9134
9135
9136
9137
9138
.....
10040
10041
10042
10043
10044
10045
10046
10047
10048
10049
10050
10051
10052
10053
10054
.....
10235
10236
10237
10238
10239
10240
10241
10242
10243
10244
10245
10246
10247
10248
10249
10250
10251
10252
10253
10254
10255
10256
10257
10258
10259
10260
10261
10262
.....
10354
10355
10356
10357
10358
10359
10360
10361
10362
10363
10364
10365
10366
10367
10368
10369
10370
10371
10372
10373
10374
10375
10376
10377
10378
10379
10380
10381
.....
10413
10414
10415
10416
10417
10418
10419
10420
10421
10422
10423
10424
10425
10426
10427
.....
10466
10467
10468
10469
10470
10471
10472
10473
10474
10475
10476
10477
10478
10479
10480
.....
10487
10488
10489
10490
10491
10492
10493
10494
10495
10496
10497
10498
10499
10500
10501
.....
10526
10527
10528
10529
10530
10531
10532
10533
10534
10535
10536
10537
10538
10539
10540
10541
10542
10543
10544
10545
10546
10547
10548
10549
.....
10554
10555
10556
10557
10558
10559
10560
10561
10562
10563
10564
10565
10566
10567
10568
.....
10569
10570
10571
10572
10573
10574
10575
10576
10577
10578
10579
10580
10581
10582
10583
10584
10585
10586
10587
10588
10589
.....
10616
10617
10618
10619
10620
10621
10622
10623

10624
10625
10626
10627
10628
10629
10630
.....
10646
10647
10648
10649
10650
10651
10652

10653
10654
10655
10656
10657
10658
10659
10660
.....
10728
10729
10730
10731
10732
10733
10734
10735
10736
10737
10738
10739
10740
10741
10742
.....
10928
10929
10930
10931
10932
10933
10934
10935
10936
10937
10938
10939
10940
10941
10942
.....
11141
11142
11143
11144
11145
11146
11147
11148
11149
11150
11151
11152
11153
11154
11155
.....
12057
12058
12059
12060
12061
12062
12063
12064
12065
12066
12067
12068
12069
12070
12071
.....
13380
13381
13382
13383
13384
13385
13386
13387
13388
13389
13390
13391
13392
13393
13394
13395
13396
13397
13398
13399
13400
13401
13402
13403
13404
13405
13406
.....
14668
14669
14670
14671
14672
14673
14674
14675
14676
14677
14678
14679
14680
14681
14682
.....
21139
21140
21141
21142
21143
21144
21145
21146
21147
21148
21149
21150
21151
21152
21153
21154
21155
21156
.....
21158
21159
21160
21161
21162
21163
21164
21165
21166
21167
21168
21169
21170
21171
21172
21173
21174
21175
21176
21177
21178
21179
21180
21181
21182
21183
21184
21185
21186
21187
21188
21189
21190
21191
21192
21193
21194
21195
21196
21197
21198
21199
21200
21201
21202
21203
21204
21205
21206
21207
21208
21209
21210
21211
21212
21213
21214
21215
.....
23134
23135
23136
23137
23138
23139
23140
23141
23142
23143
23144
23145
23146
23147
23148
23149
23150
23151
23152
23153
23154
23155
23156
.....
23312
23313
23314
23315
23316
23317
23318
23319
23320
23321
23322
23323
23324
23325
23326
23327
23328
23329
23330
23331
23332
23333
23334
23335
23336
23337
23338
23339
.....
23926
23927
23928
23929
23930
23931
23932
23933
23934
23935
23936
23937
23938
23939
23940
23941
23942
23943
23944
23945
23946
23947
23948
.....
23971
23972
23973
23974
23975
23976
23977
23978
23979
23980
23981
23982
23983
23984
23985
.....
23999
24000
24001
24002
24003
24004
24005
24006
24007
24008
24009
24010
24011
24012
24013
24014
24015
24016
24017
24018
24019
24020
24021
24022
24023
24024
24025
24026
24027
24028
24029
24030
24031
24032
24033
24034
24035
24036
24037
24038
24039
.....
24180
24181
24182
24183
24184
24185
24186
24187
24188

24189
24190

24191
24192
24193
24194
24195
24196
24197
24198
24199
24200
24201
24202

24203
24204
24205
24206

24207
24208
24209
24210
24211
24212
24213
24214
.....
25871
25872
25873
25874
25875
25876
25877
25878
25879
25880
25881
25882
25883
25884
25885
.....
25923
25924
25925
25926
25927
25928
25929
25930
25931
25932
25933
25934
25935
25936
25937
25938
25939
25940
25941
25942
25943
.....
25951
25952
25953
25954
25955
25956
25957
25958
25959
25960
25961
25962
25963
25964
25965
.....
26148
26149
26150
26151
26152
26153
26154
26155
26156
26157
26158
26159
26160
26161
26162
26163
26164
26165
26166
26167
26168
.....
26217
26218
26219
26220
26221
26222
26223
26224
26225
26226
26227
26228
26229
26230
26231
.....
26234
26235
26236
26237
26238
26239
26240
26241
26242
26243
26244
26245
26246
26247
26248
26249
26250
26251
26252
26253
26254
26255
26256
26257
26258
26259
26260
.....
26265
26266
26267
26268
26269
26270
26271
26272
26273
26274
26275
26276
26277
26278
26279
.....
26292
26293
26294
26295
26296
26297
26298
26299
26300
26301
26302
26303
26304
26305
26306
26307
26308
26309
26310
26311
26312
26313
26314
26315
26316
26317
26318
26319
26320
26321
26322
26323
26324
26325
26326
26327
26328
26329
26330
26331
26332
26333
26334
26335
26336
26337
26338
26339
26340
.....
26369
26370
26371
26372
26373
26374
26375
26376
26377
26378
26379
26380
26381
26382
26383
.....
26526
26527
26528
26529
26530
26531
26532
26533
26534
26535
26536
26537
26538
26539
26540
.....
26900
26901
26902
26903
26904
26905
26906
26907
26908
26909
26910
26911
26912
26913
26914
26915
26916
26917
26918
26919
26920
.....
27075
27076
27077
27078
27079
27080
27081
27082
27083
27084
27085
27086
27087
27088
27089
27090
27091
27092
.....
29238
29239
29240
29241
29242
29243
29244
29245
29246
29247
29248
29249
29250
29251
29252
.....
29926
29927
29928
29929
29930
29931
29932
29933
29934
29935
29936
29937
29938
29939
29940
.....
29949
29950
29951
29952
29953
29954
29955
29956
29957
29958
29959
29960
29961
29962
29963
.....
30497
30498
30499
30500
30501
30502
30503
30504
30505
30506
30507
30508
30509
30510
30511
30512
30513
30514
30515
30516
30517
30518
.....
34676
34677
34678
34679
34680
34681
34682
34683
34684
34685
34686
34687
34688
34689
34690
34691
34692
34693
34694
34695
34696
.....
35126
35127
35128
35129
35130
35131
35132
35133
35134
35135
35136
35137
35138
35139
35140
35141
35142
35143
35144
35145
.....
36656
36657
36658
36659
36660
36661
36662
36663
36664
36665
36666
36667
36668
36669
36670
.....
36753
36754
36755
36756
36757
36758
36759
36760
36761
36762
36763
36764
36765
36766
36767
.....
39470
39471
39472
39473
39474
39475
39476
39477
39478
39479
39480
39481
39482
39483
39484
.....
39533
39534
39535
39536
39537
39538
39539
39540
39541
39542
39543
39544
39545
39546
39547
.....
40345
40346
40347
40348
40349
40350
40351
40352
40353
40354
40355
40356
40357
40358
40359
.....
40566
40567
40568
40569
40570
40571
40572
40573
40574
40575
40576
40577
40578
40579
40580
.....
40611
40612
40613
40614
40615
40616
40617
40618

40619
40620
40621
40622
40623
40624
40625
.....
40753
40754
40755
40756
40757
40758
40759
40760
40761
40762
40763
40764
40765
40766
40767
.....
41238
41239
41240
41241
41242
41243
41244
41245
41246
41247
41248
41249
41250
41251
41252
    - BREAKING CHANGE with newly added reSQLSelectWithoutTable security policy
      flags in TSQLAccessRight.AllowRemoteExecute - older applications which
      expected any SELECT statement to be executed on the server may break:
      you need to explicitely set this flag for the User's TSQLAuthGroup - note
      that SELECT with a simple table name in its FROM clause will now be
      checked againsts TSQLAccessRight.GET[] access rights
    - BREAKING CHANGE: added aSentData parameter to TNotifySQLEvent/OnUpdateEvent


    - remove some unused TPropInfo methods, which were duplicates of the
      TSQLPropInfo cleaner class hierarchy: SetValue/GetValue/GetValueVar
      GetBinary/SetBinary GetVariant/SetVariant NormalizeValue/SameValue GetHash
      IsSimpleField AppendName GetCaption GetSQLFromFieldValue SetFieldAddr
    - following the Liskov substitution principle, Execute/ExecuteFmt and
      protected EngineExecute() are defined for TSQLRest, replacing ExecuteAll()
    - TSQLRestServerRemoteDB will now redirect into any TSQLRest instance
................................................................................

/// will serialize any TObject into a TDocVariant document
// - just a wrapper around _JsonFast(ObjectToJSONDebug()) with an optional
// "Context":"..." text message
// - if the supplied context format matches '{....}' then it will be added
// as a corresponding TDocVariant JSON object
function ObjectToVariantDebug(Value: TObject;
  ContextFormat: PUTF8Char; const ContextArgs: array of const;
  const ContextName: RawUTF8='context'): variant; overload;

/// will serialize any TObject into a TDocVariant document
// - just a wrapper around _JsonFast(ObjectToJSONDebug()) 
function ObjectToVariantDebug(Value: TObject): variant; overload;

/// encode supplied parameters to be compatible with URI encoding
................................................................................
    // supplied error text
    // - if no ErrorMessage is specified, will return a default text
    // corresponding to the Status code
    procedure Error(const ErrorMessage: RawUTF8='';
      Status: integer=HTML_BADREQUEST); overload;
    /// use this method to send back an error to the caller
    // - implementation is just a wrapper over Error(FormatUTF8(Format,Args))
    procedure Error(Format: PUTF8Char; const Args: array of const;
      Status: integer=HTML_BADREQUEST); overload;
    /// use this method to send back an error to the caller
    // - will serialize the supplied exception, with an optional error message
    procedure Error(E: Exception; Format: PUTF8Char; const Args: array of const;
      Status: integer=HTML_BADREQUEST); overload;
    /// at Client Side, compute URI and BODY according to the routing scheme
    // - abstract implementation which is to be overridden
    // - as input, method should be the method name to be executed,
    // params should contain the incoming parameters as JSON CSV (without []),
    // and clientDriven ID should contain the optional Client ID value 
    // - at output, should update the HTTP uri corresponding to the proper
................................................................................
      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
    - note that this method prototype changed with revision 1.17 of the
      framework: array of const used to be ParamsSQLWhere and '%' in the
      FormatSQLWhere statement, whereas it now expects bound parameters as '?' }
    constructor Create(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
      const BoundsSQLWhere: array of const); overload;
    {/ this constructor initializes the object as above, and fills its content
      from a client or server connection, using a specified WHERE clause
      with parameters
      - the FormatSQLWhere clause will replace all '%' chars with the supplied
      ParamsSQLWhere[] values, and all '?' chars with BoundsSQLWhere[] values,
      as :(...): inlined parameters - you should either call:
................................................................................
      or (letting the inlined parameters being computed by FormatUTF8)
      !  Rec := TSQLMyRecord.Create(aClient,'Count=?',[],[aCount]);
      or even better, using the other Create overloaded constructor:
      !  Rec := TSQLMyRecord.Create(aClient,'Count=?',[aCount]);
      - using '?' and BoundsSQLWhere[] is perhaps more readable in your code, and
      will in all case create a request with :(..): inline parameters, with
      automatic RawUTF8 quoting if necessary }
    constructor Create(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
      const ParamsSQLWhere, BoundsSQLWhere: array of const); overload;

    {/ this constructor initializes the object as above, and prepares itself to
      loop through a statement using a specified WHERE clause
      - this method creates a TSQLTableJSON, retrieves all records corresponding
        to the WHERE clause, then call FillPrepare - previous Create(aClient)
        methods retrieve only one record, this one more multiple rows
................................................................................
        you may need  to access only one or several fields, and will save remote
        bandwidth by specifying the needed fields
      - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs
      - note that you should not use this aCustomFieldsCSV optional parameter if
        you want to Update the retrieved record content later, since any
        missing fields will be left with previous values - but BatchUpdate() can be
        safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    constructor CreateAndFillPrepare(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
      const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8=''); overload;
    {/ this constructor initializes the object as above, and prepares itself to
      loop through a statement using a specified WHERE clause
      - this method creates a TSQLTableJSON, retrieves all records corresponding
        to the WHERE clause, then call FillPrepare - previous Create(aClient)
        methods retrieve only one record, this one more multiple rows
      - you should then loop for all rows using 'while Rec.FillOne do ...'
................................................................................
        you may need  to access only one or several fields, and will save remote
        bandwidth by specifying the needed fields
      - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs
      - note that you should not use this aCustomFieldsCSV optional parameter if
        you want to Update the retrieved record content later, since any
        missing fields will be left with previous values - but BatchUpdate() can be
        safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    constructor CreateAndFillPrepare(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
      const ParamsSQLWhere, BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''); overload;
    {/ this constructor initializes the object as above, and prepares itself to
      loop through a given list of IDs
      - this method creates a TSQLTableJSON, retrieves all records corresponding
        to the specified IDs, then call FillPrepare - previous Create(aClient)
        methods retrieve only one record, this one more multiple rows
................................................................................
        $  p.Owner='mark' and c.Name='for boy' and (s.Name='small' or s.Name='medium')
      - you SHALL call explicitely the FillClose method before using any
        methods of nested TSQLRecordMany instances which may override the Dest
        instance content (e.g. ManySelect) to avoid any GPF
      - the aFormatSQLJoin clause will replace all '%' chars with the supplied
        aParamsSQLJoin[] supplied values, and bind all '?' chars as bound
        parameters with aBoundsSQLJoin[] values }
    constructor CreateAndFillPrepareMany(aClient: TSQLRest; aFormatSQLJoin: PUTF8Char;
      const aParamsSQLJoin, aBoundsSQLJoin: array of const);

    {/ this method create a clone of the current record, with same ID and properties
      - copy all COPIABLE_FIELDS, i.e. all fields excluding tftMany (because
        those fields don't contain any data, but a TSQLRecordMany instance
        which allow to access to the pivot table data)
      - you can override this method to allow custom copy of the object,
................................................................................
       you may need  to access only one or several fields, and will save remote
       bandwidth by specifying the needed fields
     - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs 
     - note that you should not use this aCustomFieldsCSV optional parameter if
       you want to Update the retrieved record content later, since any
       missing fields will be left with previous values - but BatchUpdate() can be
       safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    function FillPrepare(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
      const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8=''): boolean; overload;
    {/ prepare to get values using a specified WHERE clause with '%' and '?' parameters
     - returns true in case of success, false in case of an error during SQL request
     - then call FillRow(1..Table.RowCount) to get any row value
     - or you can also loop through all rows with
     ! while Rec.FillOne do
     !   dosomethingwith(Rec);
................................................................................
       you may need  to access only one or several fields, and will save remote
       bandwidth by specifying the needed fields
     - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs 
     - note that you should not use this aCustomFieldsCSV optional parameter if
       you want to Update the retrieved record content later, since any
       missing fields will be left with previous values - but BatchUpdate() can be
       safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    function FillPrepare(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
      const ParamsSQLWhere, BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): boolean; overload;
    {/ prepare to get values from a list of IDs
     - returns true in case of success, false in case of an error during SQL request
     - then call FillRow(1..Table.RowCount) to get any row value
     - or you can also loop through all rows with
     ! while Rec.FillOne do
................................................................................
     - the FormatSQLWhere clause will replace all '%' chars with the supplied
       ParamsSQLWhere[] supplied values, and bind all '?' chars as parameters
       with BoundsSQLWhere[] values
     - you SHALL call explicitely the FillClose method before using any
       methods of nested TSQLRecordMany instances which may override the Dest
       instance content (e.g. ManySelect) to avoid any GPF
     - is used by TSQLRecord.CreateAndFillPrepareMany constructor }
    function FillPrepareMany(aClient: TSQLRest; aFormatSQLJoin: PUTF8Char;
      const aParamsSQLJoin, aBoundsSQLJoin: array of const): boolean;
    {/ fill all published properties of an object from a TSQLTable prepared row
      - FillPrepare() must have been called before
      - if Dest is nil, this object values are filled
      - if Dest is not nil, this object values will be filled, but it won't
        work with TSQLRecordMany properties (i.e. after FillPrepareMany call)
      - ID field is updated if first Field Name is 'ID'
................................................................................
  public
    /// constructor of one parameters marshalling instance
    constructor Create(aSender: TInterfaceStub; aMethod: PServiceMethod;
      const aParams,aEventParams: RawUTF8); virtual;
    /// call this method if the callback implementation failed
    procedure Error(const aErrorMessage: RawUTF8); overload;
    /// call this method if the callback implementation failed
    procedure Error(Format: PUTF8Char; const Args: array of const); overload;
    /// the stubbing / mocking generator
    property Sender: TInterfaceStub read fSender;
    /// the mocking generator associated test case
    // - will raise an exception if the associated Sender generator is not
    // a TInterfaceMock
    property TestCase: TSynTestCase read GetSenderAsMockTestCase;
    /// pointer to the method which is to be executed
................................................................................
    fLogs: TInterfaceStubLogDynArray;
    fLog: TDynArray;
    fLogCount: integer;
    fInterfaceExpectedTraceHash: cardinal;
    function TryResolve(aInterface: PTypeInfo; out Obj): boolean; override;
    procedure InternalGetInstance(out aStubbedInterface); virtual;
    function InternalCheck(aValid,aExpectationFailed: boolean;
      aErrorMsgFmt: PUTF8Char; const aErrorMsgArgs: array of const): boolean; virtual;
    // match TOnFakeInstanceInvoke callback signature
    function Invoke(const aMethod: TServiceMethod;
      const aParams: RawUTF8; aResult, aErrorMsg: PRawUTF8;
      aClientDrivenID: PCardinal; aServiceCustomAnswer: PServiceCustomAnswer): boolean;
    // will launch InternalCheck() process if some expectations defined by
    // ExpectsCount() are not met, i.e. raise an exception for TInterfaceStub
    // or notify the associated test case for TInterfaceMock
................................................................................
  // are defined before running the test, and verification is performed
  // when the instance is released - use TInterfaceMockSpy if you prefer the
  // more explicit run-verify pattern
  TInterfaceMock = class(TInterfaceStub)
  protected
    fTestCase: TSynTestCase;
    function InternalCheck(aValid,aExpectationFailed: boolean;
      aErrorMsgFmt: PUTF8Char; const aErrorMsgArgs: array of const): boolean; override;
  public
    /// initialize an interface mock from TypeInfo(IMyInterface)
    // - aTestCase.Check() will be called in case of mocking failure
    // ! procedure TMyTestCase.OneTestCaseMethod;
    // ! var Persist: IPersistence;
    // ! ...
    // !   TInterfaceMock.Create(TypeInfo(IPersistence),Persist,self).
................................................................................
    fLogFamily: TSynLogFamily; // =SQLite3Log.Family by default
    procedure SetLogClass(aClass: TSynLogClass); virtual;
    function GetLogClass: TSynLogClass;
    {$endif}
    /// log the corresponding text (if logging is enabled)
    procedure InternalLog(const Text: RawUTF8; Level: TSynLogInfo); overload;
      {$ifdef HASINLINE}inline;{$endif}
    procedure InternalLog(Format: PWinAnsiChar; const Args: array of const;
      Level: TSynLogInfo); overload;
    /// internal method used by Delete(Table,SQLWhere) method
    function InternalDeleteNotifyAndGetIDs(Table: TSQLRecordClass; const SQLWhere: RawUTF8;
      var IDs: TIDDynArray): boolean; 
    /// retrieve the server time stamp
    // - default implementation will use fServerTimeStampOffset to compute
    // the value from PC time (i.e. NowUTC+fServerTimeStampOffset as TTimeLog)
................................................................................
    // - example of use:
    // ! aClient.OneFieldValue(TSQLRecord,'Name','ID=?',[aID])
    // - call internaly 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: TSQLRecordClass; const FieldName: RawUTF8;
      FormatSQLWhere: PUTF8Char; 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(TSQLRecord,'Name','%=?',['ID'],[aID])
    // - call internaly ExecuteList() to get the value
    function OneFieldValue(Table: TSQLRecordClass; const FieldName: RawUTF8;
      WhereClauseFmt: PUTF8Char; 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: TSQLRecordClass; const FieldName: RawUTF8;
      WhereClauseFmt: PUTF8Char; 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(TSQLRecord,'Name',23)
    // - call internaly ExecuteList() to get the value
    function OneFieldValue(Table: TSQLRecordClass;
      const FieldName: RawUTF8; WhereID: TID): RawUTF8; overload;
    /// get the UTF-8 encoded value of some fields with a Where Clause
................................................................................
    // - example of use:
    // ! aList := aClient.MultiFieldValues(TSQLRecord,'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: TSQLRecordClass; const FieldNames: RawUTF8;
      WhereClauseFormat: PUTF8Char; const BoundsSQLWhere: array of const): TSQLTableJSON; overload;
    /// Execute directly a SQL statement, expecting a list of results
    // - return a result table 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(TSQLRecord,'Name','%=?',['ID'],[aID]);
    // - call overloaded MultiFieldValues() / ExecuteList() to get the list
    function MultiFieldValues(Table: TSQLRecordClass; const FieldNames: RawUTF8;
      WhereClauseFormat: PUTF8Char; const Args, Bounds: array of const): TSQLTableJSON; 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: TSQLRecordClass; ID: TID;
................................................................................
    /// get a member from a SQL statement
    // - implements REST GET collection
    // - return true on success
    // - same as Retrieve(const SQLWhere: RawUTF8; Value: TSQLRecord) 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)
    function Retrieve(WhereClauseFmt: PUTF8Char; const Args,Bounds: array of const;
      Value: TSQLRecord): 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
................................................................................
    // - aCustomFieldsCSV can be the CSV list of field names to be retrieved
    // - if aCustomFieldsCSV is '', will get all simple fields, excluding BLOBs
    // - if aCustomFieldsCSV 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: TSQLRecordClass; FormatSQLWhere: PUTF8Char;
      const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): TObjectList; overload;
    /// 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
................................................................................
    // 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 would be the fastest, but complex
    // types like dynamic array would be returned as Base64-encoded blob value -
    // if you need proper JSON access to those, see RetrieveDocVariantArray()
    function RetrieveListJSON(Table: TSQLRecordClass; FormatSQLWhere: PUTF8Char;
      const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): RawJSON;
    {$ifndef NOVARIANTS}
    /// 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,
................................................................................
    // - if aCustomFieldsCSV is '', will get all simple fields, excluding BLOBs
    // - if aCustomFieldsCSV is '*', will get ALL fields, including ID and BLOBs
    // - the data will be converted to variants and TDocVariant following the
    // TSQLRecord layout, so complex types like dynamic array would be returned
    // as a true array of values (in contrast to the RetrieveListJSON method)
    function RetrieveDocVariantArray(Table: TSQLRecordClass;
      const ObjectName: RawUTF8;
      FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const;
      const CustomFieldsCSV: RawUTF8; FirstRecordID: PID=nil;
      LastRecordID: PID=nil): variant; overload;
    /// 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
    // TSQLRecord layout, so complex types like dynamic array would be returned
    // as a true array of values 
    function RetrieveDocVariant(Table: TSQLRecordClass;
      FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const;
      const CustomFieldsCSV: RawUTF8): variant; 
    {$endif NOVARIANTS}
    /// 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
................................................................................
    // - if aCustomFieldsCSV 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: TSQLRecordClass;
      FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): boolean;
    /// Execute directly a SQL statement, expecting a list of results
    // - return a result table on success, nil on failure
    // - will call EngineList() abstract method to retrieve its JSON content
    function ExecuteList(const Tables: array of TSQLRecordClass; const SQL: RawUTF8): TSQLTableJSON; virtual;
    /// 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; virtual;
    /// 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(SQLFormat: PUTF8Char; 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(SQLFormat: PUTF8Char; 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: TSQLRecordClass; aID: TID): boolean; overload; virtual; abstract;
    /// unlock the corresponding record
................................................................................
    // TSQLRecordMany 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 internaly the Add virtual method above
    function Add(aTable: TSQLRecordClass; const aSimpleFields: array of const; ForcedID: TID=0): TID; overload;

    /// update a member from Value simple fields content
    // - implements REST PUT collection
    // - return true on success
    // - the TSQLRawBlob(BLOB) fields values are not updated by this method, to
    // preserve bandwidth: use the UpdateBlob() methods for handling BLOB fields
    // - the TSQLRecordMany fields are not set either: they are separate
    // instances created by TSQLRecordMany.Create, with dedicated methods to
................................................................................
    /// update a member from a supplied list of simple field values
    // - implements REST PUT collection
    // - the aSimpleFields parameters MUST follow explicitely both count and
    // order of published properties of the supplied aTable class, excepting the
    // TSQLRawBlob and TSQLRecordMany kind (i.e. only so called "simple fields")
    // - return true on success
    // - call internaly the Update() / EngineUpdate() virtual methods 

    function Update(aTable: TSQLRecordClass; 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 PUT collection on Value.ID
    // - will return the created or updated ID
    function AddOrUpdate(Value: TSQLRecord): 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
................................................................................
    // - 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: TSQLRecordClass; FormatSQLWhere: PUTF8Char;
      const BoundsSQLWhere: array of const): boolean; overload;

    /// access the internal caching parameters for a given TSQLRecord
    // - purpose of this caching mechanism is to speed up retrieval of some
    // common values at either Client or Server level (like configuration settings)
    // - by default, this CRUD level per-ID cache is disabled
    // - use Cache.SetCache() and Cache.SetTimeOut() methods to set the appropriate
................................................................................
    // double, currency, RawUTF8 values to be bound to the request as parameters
    // - aCustomFieldsCSV can be the CSV list of field names to be retrieved
    // - if aCustomFieldsCSV is '', will get all simple fields, excluding BLOBs
    // - if aCustomFieldsCSV is '*', will get ALL fields, including ID and BLOBs
    // - return a TObjectList<T> on success (possibly with Count=0) - caller is
    // responsible of freeing the instance
    // - return nil on error
    function RetrieveList<T: TSQLRecord>(FormatSQLWhere: PUTF8Char;
      const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): TObjectList<T>; overload;
    {$endif}

    /// you can call this method in TThread.Execute to ensure that
    // the thread will be taken in account during process
    // - this abstract method won't do anything, but TSQLRestServer's will
................................................................................
    Where: PAnsiChar;
    /// returned JSON field value of optional total row counts
    // - default value is nil, i.e. no total row counts field
    // - computing total row counts can be very expensive, depending on the
    // database back-end used (especially for external databases)
    // - can be set e.g. to ',"totalRows":%' value (note that the initial "," is
    // expected by the produced JSON content, and % will be set with the value)
    SendTotalRowsCountFmt: PUTF8Char;
  end;

  /// used for statistics update in TSQLRestServer.URI()
  TSQLRestServerStats = class(TPersistent)
  private
    /// used to determine if something changed
    fLastIncomingBytes: QWord;
................................................................................
    fTrackChangesHistoryTableIndex: TIntegerDynArray;
    fTrackChangesHistory: array of record
      CurrentRow: integer;
      MaxSentDataJsonRow: integer;
      MaxRevisionJSON: integer;
      MaxUncompressedBlobSize: integer;
    end;
    function CreateBackgroundThread(Format: PUTF8Char; const Args: array of const): TSynBackgroundThreadMethod;
    function GetAuthenticationSchemesCount: integer;
    /// fast get the associated static server, if any
    function GetStaticDataServer(aClass: TSQLRecordClass): TSQLRest;
    /// retrieve a TSQLRestStorage instance associated to a Virtual Table
    // - is e.g. TSQLRestStorageInMemory instance associated to a
    // TSQLVirtualTableBinary or TSQLVirtualTableJSON class
    // - may be a TSQLRestStorageExternal (as defined in mORMotDB unit)
................................................................................
    /// retrieve a list of members as a TSQLTable
    // - implements REST GET collection
    // - in this version, the WHERE clause can be created with the same format
    // as FormatUTF8() function, replacing all '%' chars with Args[] values
    // - using inlined parameters via :(...): in SQLWhereFormat is always a good idea
    // - for one TClass, you should better use TSQLRest.MultiFieldValues()
    // - will call the List virtual method internaly
    function ListFmt(const Tables: array of TSQLRecordClass; const SQLSelect: RawUTF8;
      SQLWhereFormat: PUTF8Char; const Args: array of const): TSQLTableJSON; overload;
    /// retrieve a list of members as a TSQLTable
    // - implements REST GET collection
    // - 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 := ListFmt([TSQLRecord],'Name','ID=?',[],[aID]);
    // - for one TClass, you should better use TSQLRest.MultiFieldValues()
    // - will call the List virtual method internaly
    function ListFmt(const Tables: array of TSQLRecordClass; const SQLSelect: RawUTF8;
      SQLWhereFormat: PUTF8Char; const Args, Bounds: array of const): TSQLTableJSON; overload;
    /// dedicated method used to retrieve matching IDs using a fast R-Tree index
    // - a TSQLRecordRTree is associated to a TSQLRecord with a specified BLOB
    // field, and will call TSQLRecordRTree BlobToCoord and ContainedIn virtual
    // class methods to execute an optimized SQL query
    // - 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
................................................................................
  PAGINGPARAMETERS_YAHOO: TSQLRestServerURIPagingParameters = (
    Sort: 'SORT=';
    Dir: 'DIR=';
    StartIndex: 'STARTINDEX=';
    Results: 'RESULTS=';
    Select: 'SELECT=';
    Where: 'WHERE=';
    SendTotalRowsCountFmt: nil);

  /// options to specify no index createon for TSQLRestServer.CreateMissingTables
  // and TSQLRecord.InitializeTable methods
  INITIALIZETABLE_NOINDEX: TSQLInitializeTableOptions =
    [itoNoIndex4ID..itoNoIndex4RecordReference];


................................................................................
  SetString(fPrivateCopy,PAnsiChar(pointer(aJSON)),len);
  CreateWithColumnTypes(ColumnTypes,aSQL,pointer(fPrivateCopy),len);
end;


{ TINIWriter }

const
  sWriteObject1: PWinAnsiChar = #13'[%]'#13;
  sWriteObject2: PWinAnsiChar = '%%=%'#13;
  
procedure TINIWriter.WriteObject(Value: TObject; const SubCompName: RawUTF8='';
  WithSection: boolean=true);
var P: PPropInfo;
    i, V: integer;
    VT: shortstring; // for str()
    Obj: TObject;
    WS: WideString;
................................................................................
    VV: Variant;
    {$endif}
begin
  if Value<>nil then begin
    if WithSection then
      // new TObject.ClassName is UnicodeString (Delphi 20009) -> inline code with
      // vmtClassName = UTF-8 encoded text stored in a shortstring = -44
      Add(sWriteObject1,[PShortString(PPointer(PPtrInt(Value)^+vmtClassName)^)^]);
    for i := 1 to InternalClassPropInfo(PPointer(Value)^,P) do begin
      case P^.PropType^.Kind of
        tkInt64{$ifdef FPC}, tkQWord{$endif}:
          Add(sWriteObject2,[SubCompName,P^.Name,P^.GetInt64Prop(Value)]);
        {$ifdef FPC}tkBool,{$endif}
        tkEnumeration, tkInteger, tkSet: begin
          V := P^.GetOrdProp(Value);
          if V<>P^.Default then
            Add(sWriteObject2,[SubCompName,P^.Name,V]);
        end;
        {$ifdef FPC}tkAString,{$endif} tkLString:
          Add(sWriteObject2,[SubCompName,P^.Name,P^.GetLongStrValue(Value)]);
        tkFloat: begin
          VT[0] := AnsiChar(ExtendedToString(VT,P^.GetFloatProp(Value),DOUBLE_PRECISION));
          Add(sWriteObject2,[SubCompName,P^.Name,VT]);
        end;
        tkWString: begin
          P^.GetWideStrProp(Value,WS);
          Add(sWriteObject2,[SubCompName,P^.Name,WS]);
        end;
        {$ifdef UNICODE}
        tkUString: // write converted to UTF-8
          Add(sWriteObject2,[SubCompName,P^.Name,P^.GetUnicodeStrProp(Value)]);
        {$endif}
        tkDynArray: begin
          Add(sWriteObject2,[SubCompName,P^.Name]);
          AddDynArrayJSON(P^.GetDynArray(Value));
          Add(#13);
        end;
        {$ifdef PUBLISHRECORD}
        tkRecord{$ifdef FPC},tkObject{$endif}:
          Add(sWriteObject2,[SubCompName,P^.Name,BinToBase64WithMagic(
            RecordSave(P^.GetFieldAddr(Value)^,P^.PropType^))]);
        {$endif}
        tkClass: begin
          Obj := P^.GetObjProp(Value); 
          if (Obj<>nil) and Obj.InheritsFrom(TPersistent) then
             WriteObject(Obj,SubCompName+RawUTF8(P^.Name)+'.',false);
        end;
        {$ifndef NOVARIANTS}
        tkVariant: begin // stored as JSON, e.g. '1.234' or '"text"'
          P^.GetVariantProp(Value,VV);
          Add(sWriteObject2,[SubCompName,P^.Name,VariantSaveJSON(VV)]);
        end;
        {$endif}
      end; // tkString (shortstring) is not handled
      P := P^.Next;
    end;
  end;
end;
................................................................................
constructor TSQLRecord.Create(aClient: TSQLRest; const aSQLWhere: RawUTF8);
begin
  Create;
  if aClient<>nil then
    aClient.Retrieve(aSQLWhere,self);
end;

constructor TSQLRecord.Create(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
  const BoundsSQLWhere: array of const);
begin
  Create;
  if aClient<>nil then
    aClient.Retrieve(FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere),self);
end;

constructor TSQLRecord.Create(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
  const ParamsSQLWhere, BoundsSQLWhere: array of const);
begin
  Create;
  if aClient<>nil then
    aClient.Retrieve(FormatUTF8(FormatSQLWhere,ParamsSQLWhere,BoundsSQLWhere),self);
end;

................................................................................
  if T=nil then
    exit;
  T.OwnerMustFree := true;
  FillPrepare(T,aCheckTableName);
  result := true;
end;

function TSQLRecord.FillPrepare(aClient: TSQLRest; FormatSQLWhere: PUTF8Char;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8=''): boolean;
begin
  if (FormatSQLWhere=nil) or (high(BoundsSQLWhere)<0) then
    result := false else
    result := FillPrepare(aClient,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere),
      aCustomFieldsCSV);
end;

function TSQLRecord.FillPrepare(aClient: TSQLRest;
  FormatSQLWhere: PUTF8Char; const ParamsSQLWhere, BoundsSQLWhere: array of const;
  const aCustomFieldsCSV: RawUTF8): boolean;
begin
  if (FormatSQLWhere=nil) or ((high(ParamsSQLWhere)<0)and(high(BoundsSQLWhere)<0)) then
    result := false else
    result := FillPrepare(aClient,
      FormatUTF8(FormatSQLWhere,ParamsSQLWhere,BoundsSQLWhere),aCustomFieldsCSV);
end;

function TSQLRecord.FillPrepare(aClient: TSQLRest; const aIDs: array of Int64;
  const aCustomFieldsCSV: RawUTF8=''): boolean;
................................................................................
  if aTable=nil then
    exit;
  aTable.OwnerMustFree := true;
  FillPrepare(aTable);
end;

constructor TSQLRecord.CreateAndFillPrepare(aClient: TSQLRest;
  FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const;
  const aCustomFieldsCSV: RawUTF8='');
begin
  CreateAndFillPrepare(aClient,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere),
    aCustomFieldsCSV);
end;

constructor TSQLRecord.CreateAndFillPrepare(aClient: TSQLRest;
  FormatSQLWhere: PUTF8Char; const ParamsSQLWhere,
  BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8);
begin
  CreateAndFillPrepare(aClient,
    FormatUTF8(FormatSQLWhere,ParamsSQLWhere,BoundsSQLWhere),aCustomFieldsCSV);
end;

constructor TSQLRecord.CreateAndFillPrepare(aClient: TSQLRest;
................................................................................
begin
  Create;
  props := aClient.Model.Props[PSQLRecordClass(Self)^];
  if props.props.JoinedFields=nil then
    raise EORMException.CreateUTF8('No nested TSQLRecord to JOIN in %',[self]);
  SQL := props.SQL.SelectAllJoined;
  if aFormatSQLJoin<>'' then
    SQL := SQL+FormatUTF8(pointer(SQLFromWhere(aFormatSQLJoin)),
      aParamsSQLJoin,aBoundsSQLJoin);
  T := aClient.ExecuteList(props.props.JoinedFieldsTable,SQL);
  if T=nil then
    exit;
  fFill := TSQLRecordFill.Create;
  fFill.fJoinedFields := True;
  fFill.fTable := T;
................................................................................
constructor TSQLRecord.CreateJoined(aClient: TSQLRest; aID: TID);
begin
  CreateAndFillPrepareJoined(aClient,'%.RowID=?',[RecordProps.SQLTableName],[aID]);
  FillOne;
end;

constructor TSQLRecord.CreateAndFillPrepareMany(aClient: TSQLRest;
  aFormatSQLJoin: PUTF8Char; const aParamsSQLJoin, aBoundsSQLJoin: array of const);
begin
  Create;
  if Length(RecordProps.ManyFields)=0 then
    raise EModelException.CreateUTF8(
      '%.CreateAndFillPrepareMany() with no many-to-many fields',[self]);
  if not FillPrepareMany(aClient,aFormatSQLJoin,aParamsSQLJoin,aBoundsSQLJoin) then
    raise EModelException.CreateUTF8(
      '%.CreateAndFillPrepareMany(): FillPrepareMany() failure',[self]);
end;

function TSQLRecord.FillPrepareMany(aClient: TSQLRest;
  aFormatSQLJoin: PUTF8Char; const aParamsSQLJoin, aBoundsSQLJoin: array of const): boolean;
var aSQLFields, aSQLFrom, aSQLWhere, aSQL: RawUTF8;
    aField: string[3];
    aMany: RawUTF8;
    f, n, i, SQLFieldsCount: Integer;
    Props: TSQLRecordProperties;
    T: TSQLTable;
    SQLFields: array of record
      SQL: string[3];
      Prop: TSQLPropInfo;
      Instance: TSQLRecord;
    end;
    M: TSQLRecordMany;
    D: TSQLRecord;
    P: PUTF8Char;
    Objects: array of TSQLRecord;
    ObjectsClass: array of TSQLRecordClass;

  function AddField(aProp: TSQLPropInfo): Boolean;
  begin
    if SQLFieldsCount>=MAX_SQLFIELDS then
      result := false else
................................................................................
    end;
  if Props.fSQLFillPrepareMany<>'' then
    aSQL := Props.fSQLFillPrepareMany else begin
    aSQL := FormatUTF8('select % from % where %',[aSQLFields,aSQLFrom,aSQLWhere]);
    Props.fSQLFillPrepareMany := aSQL;
  end;
  // process aFormatSQLJoin,aParamsSQLJoin and aBoundsSQLJoin parameters
  if aFormatSQLJoin<>nil then begin
    aSQLWhere := '';

    repeat
      P := aFormatSQLJoin;

      while not (ord(P^) in IsIdentifier) do begin
        case P^ of
        '"':  repeat inc(P) until P^ in [#0,'"'];
        '''': repeat inc(P) until P^ in [#0,''''];
        end;
        if P^=#0 then break;
        inc(P);
      end;
      if P<>aFormatSQLJoin then begin // append ' ',')'..
        SetString(aSQLFrom,aFormatSQLJoin,P-aFormatSQLJoin);
        aSQLWhere := aSQLWhere+aSQLFrom;
        aFormatSQLJoin := P;

      end;
      if P^=#0 then break;
      aSQLWhere := aSQLWhere+ProcessField(aFormatSQLJoin);
    until aFormatSQLJoin^=#0;

    aSQL := aSQL+' and ('+FormatUTF8(pointer(aSQLWhere),aParamsSQLJoin,aBoundsSQLJoin)+')';
  end;
  // execute SQL statement and retrieve data
  T := aClient.ExecuteList(ObjectsClass,aSQL);
  if (T=nil) or (T.fResults=nil) then
    exit;
  fFill.fTable := T;
  T.OwnerMustFree := true;
................................................................................
begin
  {$ifdef WITHLOG}
  if Level in fLogFamily.Level then
    fLogFamily.SynLog.Log(Level,Text,self);
  {$endif}
end;

procedure TSQLRest.InternalLog(Format: PWinAnsiChar;
  const Args: array of const; Level: TSynLogInfo);
begin
  {$ifdef WITHLOG}
  if Level in fLogFamily.Level then
    fLogFamily.SynLog.Log(Level,Format,Args,self);
  {$endif}
end;
................................................................................
begin
  if MultiFieldValue(Table,[FieldName],Res,WhereClause) then
    result := Res[0] else
    result := '';
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass; const FieldName: RawUTF8;
  FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const): RawUTF8;
begin
  result := OneFieldValue(Table,FieldName,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere));
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass;
  const FieldName: RawUTF8; WhereClauseFmt: PUTF8Char;
  const Args, Bounds: array of const): RawUTF8;
begin
  result := OneFieldValue(Table,FieldName,FormatUTF8(WhereClauseFmt,Args,Bounds));
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass;
  const FieldName: RawUTF8; WhereID: TID): RawUTF8;
................................................................................

function TSQLRest.MemberExists(Table: TSQLRecordClass; ID: TID): boolean;
begin
  result := OneFieldValue(Table,'RowID',ID)<>'';
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass; const FieldName: RawUTF8;
  WhereClauseFmt: PUTF8Char; const Args, Bounds: array of const;
  out Data: Int64): boolean;
var Res: array[0..0] of RawUTF8;
    err: integer;
begin
  result := false;
  if MultiFieldValue(Table,[FieldName],Res,FormatUTF8(WhereClauseFmt,Args,Bounds)) then
    if Res[0]<>'' then begin
................................................................................
  sql := SQLComputeForSelect(Table,FieldNames,WhereClause);
  if sql='' then
    result := nil else
    result := ExecuteList([Table],sql);
end;

function TSQLRest.MultiFieldValues(Table: TSQLRecordClass; const FieldNames: RawUTF8;
  WhereClauseFormat: PUTF8Char; const BoundsSQLWhere: array of const): TSQLTableJSON;
begin
  result := MultiFieldValues(Table,FieldNames,FormatUTF8(WhereClauseFormat,[],BoundsSQLWhere));
end;

function TSQLRest.MultiFieldValues(Table: TSQLRecordClass;
  const FieldNames: RawUTF8; WhereClauseFormat: PUTF8Char;
  const Args, Bounds: array of const): TSQLTableJSON;
begin
  result := MultiFieldValues(Table,FieldNames,FormatUTF8(WhereClauseFormat,Args,Bounds));
end;

function TSQLRest.MultiFieldValue(Table: TSQLRecordClass;
  const FieldName: array of RawUTF8; var FieldValue: array of RawUTF8;
................................................................................
        result := false;
      end;
    finally
      T.Free;
    end;
end;

function TSQLRest.RetrieveList(Table: TSQLRecordClass; FormatSQLWhere: PUTF8Char;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8): TObjectList;
var T: TSQLTable;
begin
  result := nil;
  if (self=nil) or (Table=nil) then
    exit;
  T := MultiFieldValues(Table,aCustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
    result := TObjectList.Create;
    T.ToObjectList(result,Table);
  finally
    T.Free;
  end;
end;

function TSQLRest.RetrieveListJSON(Table: TSQLRecordClass; FormatSQLWhere: PUTF8Char;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8): RawJSON;
var sql: RawUTF8;
begin
  sql := SQLComputeForSelect(Table,aCustomFieldsCSV,
    FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere));
  if sql='' then
    result := '' else
    result := EngineList(sql);
end;

function TSQLRest.RetrieveListObjArray(var ObjArray; Table: TSQLRecordClass;
  FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const;
  const aCustomFieldsCSV: RawUTF8): boolean;
var T: TSQLTable;
begin
  result := false;
  if (self=nil) or (Table=nil) then
    exit;
  T := MultiFieldValues(Table,aCustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
    T.Free;
  end;
end;

{$ifndef NOVARIANTS}
function TSQLRest.RetrieveDocVariantArray(Table: TSQLRecordClass;
  const ObjectName: RawUTF8;
  FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const;
  const CustomFieldsCSV: RawUTF8; FirstRecordID,LastRecordID: PID): variant;
var T: TSQLTable;
    res: variant;
begin
  TVarData(res).VType := varNull;
  if (self<>nil) and (Table<>nil) then begin
    T := MultiFieldValues(Table,CustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
    result := _ObjFast([ObjectName,res]) else
    result := res;
end;

function TSQLRest.RetrieveDocVariantArray(Table: TSQLRecordClass;
  const ObjectName, CustomFieldsCSV: RawUTF8; FirstRecordID,LastRecordID: PID): variant;
begin
  result := RetrieveDocVariantArray(Table,ObjectName,nil,[],CustomFieldsCSV,
    FirstRecordID,LastRecordID);
end;

function TSQLRest.RetrieveDocVariant(Table: TSQLRecordClass;
  FormatSQLWhere: PUTF8Char; const BoundsSQLWhere: array of const;
  const CustomFieldsCSV: RawUTF8): variant;
var T: TSQLTable;
    bits: TSQLFieldBits;
    Rec: TSQLRecord;
    len: integer;
begin
  SetVariantNull(result);
  if (self<>nil) and (Table<>nil) then begin
    with Table.RecordProps do
    if Cache.IsCached(Table) and (length(BoundsSQLWhere)=1) and
       (BoundsSQLWhere[0].VType=vtInteger) and
       FieldIndexsFromCSV(CustomFieldsCSV,bits) then
      if IsZero(bits) then
        exit else
      if bits-SimpleFieldsBits[soSelect]=[] then begin
        len := StrLen(FormatSQLWhere);
        if IdemPropNameU('RowID=?',FormatSQLWhere,len) or
           IdemPropNameU('ID=?',FormatSQLWhere,len) then begin
          Rec := Table.Create(self,BoundsSQLWhere[0].VInteger);
          try
            result := Rec.GetAsDocVariant(True,bits);
          finally
            Rec.Free;
          end;
          exit;
        end;
      end;
    T := MultiFieldValues(Table,CustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
    if T<>nil then
    try
      T.ToDocVariant(1,result)
    finally
      T.Free;
    end;
  end;
end;
................................................................................
    fCache.Notify(Tableindex,aID,Resp,soSelect);
  end;
  // fill Value from JSON if was correctly retrieved
  Value.FillFrom(Resp);
  result := true;
end;

function TSQLRest.Retrieve(WhereClauseFmt: PUTF8Char; const Args,Bounds: array of const;
  Value: TSQLRecord): boolean;
var where: RawUTF8;
begin
  where := FormatUTF8(WhereClauseFmt,Args,Bounds);
  result := Retrieve(where,Value);
end;

................................................................................
var IDs: TIDDynArray;
begin
  if InternalDeleteNotifyAndGetIDs(Table,SQLWhere,IDs) then
    result := EngineDeleteWhere(Model.GetTableIndexExisting(Table),SQLWhere,IDs) else
    result := false;
end;

function TSQLRest.Delete(Table: TSQLRecordClass; FormatSQLWhere: PUTF8Char;
  const BoundsSQLWhere: array of const): boolean;
begin
  result := Delete(Table,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere));
end;

function TSQLRest.Update(Value: TSQLRecord; const CustomFields: TSQLFieldBits): boolean;
var JSONValues: RawUTF8;
................................................................................
end;

function TSQLRest.Execute(const aSQL: RawUTF8): boolean;
begin
  result := EngineExecute(aSQL);
end;

function TSQLRest.ExecuteFmt(SQLFormat: PUTF8Char;
  const Args: array of const): boolean;
begin
  result := EngineExecute(FormatUTF8(SQLFormat,Args));
end;

function TSQLRest.ExecuteFmt(SQLFormat: PUTF8Char;
  const Args, Bounds: array of const): boolean;
begin
  result := EngineExecute(FormatUTF8(SQLFormat,Args,Bounds));
end;

function TSQLRest.MainFieldValue(Table: TSQLRecordClass; ID: TID;
   ReturnFirstIfNoUnique: boolean=false): RawUTF8;
................................................................................
  service := fServices.Info(TypeInfo(T));
  if (service=nil) or not service.Get(result) then
    result := Default(T);
end;

function TSQLRest.RetrieveList<T>(const aCustomFieldsCSV: RawUTF8): TObjectList<T>;
begin
  result := RetrieveList<T>(nil,[],aCustomFieldsCSV);
end;

function TSQLRest.RetrieveList<T>(FormatSQLWhere: PUTF8Char;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8): TObjectList<T>;
var Table: TSQLTable;
begin
  result := nil;
  if self=nil then
    exit;
  Table := MultiFieldValues(TSQLRecordClass(T),aCustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
begin
  result := true;
  for i := 0 to high(FieldNames) do
    if not CreateSQLMultiIndex(Table,[FieldNames[i]],Unique) then
     result := false;
end;

function TSQLRestServer.CreateBackgroundThread(Format: PUTF8Char; const Args: array of const): TSynBackgroundThreadMethod;
begin
  result := TSynBackgroundThreadMethod.Create(nil,FormatUTF8(Format,Args));
  result.OnBeforeExecute := BeginCurrentThread;
  result.OnAfterExecute := EndCurrentThread;
end;

function TSQLRestServer.GetAuthenticationSchemesCount: integer;
................................................................................
             not ContainsUTF8(pointer(SQLWhere),'ORDER BY') then begin
            if SameTextU(SQLDir,'DESC') then
              SQLSort := SQLSort+' DESC'; // allow DESC, default is ASC
            SQLWhere := SQLWhere+' ORDER BY '+SQLSort;
          end;
          SQLWhere := trim(SQLWhere);
          if (SQLResults<>0) and not ContainsUTF8(pointer(SQLWhere),'LIMIT ') then begin
            if (Server.URIPagingParameters.SendTotalRowsCountFmt<>nil) then begin
              if SQLWhere=SQLWhereCount then begin
                i := PosEx('ORDER BY ',UpperCase(SQLWhereCount));
                if i>0 then // if ORDER BY already in the SQLWhere clause
                  SetLength(SQLWhereCount,i-1);
              end;
              ResultList := Server.ExecuteList([Table],
                Server.Model.TableProps[TableIndex].SQLFromSelectWhere('Count(*)',SQLWhereCount));
................................................................................
          end;
        end;
        SQL := Server.Model.TableProps[TableIndex].
          SQLFromSelectWhere(SQLSelect,trim(SQLWhere));
        Call.OutBody := Server.InternalListRawUTF8(TableIndex,SQL);
        if Call.OutBody<>'' then begin // got JSON list '[{...}]' ?
          Call.OutStatus := HTML_SUCCESS;  // 200 OK
          if Server.URIPagingParameters.SendTotalRowsCountFmt<>nil then
            if Server.NoAJAXJSON then begin
              P := pointer(Call.OutBody);
              L := length(Call.OutBody);
              P := NotExpandedBufferRowCountPos(P,P+L);
              j := 0;
              if P<>nil then
                j := P-pointer(Call.OutBody)-11 else
................................................................................
procedure TSQLRestServerURIContext.Success(Status: integer);
begin
  if (Status in [HTML_SUCCESS,HTML_CREATED]) or (Status=HTML_NOTMODIFIED) then
    Call.OutStatus := Status else
    Error('',Status);
end;

procedure TSQLRestServerURIContext.Error(Format: PUTF8Char;
  const Args: array of const; Status: integer);
begin
  Error(FormatUTF8(Format,Args),Status);
end;

procedure TSQLRestServerURIContext.Error(E: Exception;
  Format: PUTF8Char; const Args: array of const; Status: integer);
var msg,exc: RawUTF8;
begin
  msg := FormatUTF8(Format,Args);
  if E=nil then
    Error(msg,Status) else begin
    exc := ObjectToJSONDebug(E);
    if msg='' then
................................................................................

procedure TSQLRestClient.RollBack(SessionID: cardinal);
begin
  inherited;
end;

function TSQLRestClient.ListFmt(const Tables: array of TSQLRecordClass; const SQLSelect: RawUTF8;
  SQLWhereFormat: PUTF8Char; const Args: array of const): TSQLTableJSON;
begin
  result := List(Tables,SQLSelect,FormatUTF8(SQLWhereFormat,Args));
end;

function TSQLRestClient.ListFmt(const Tables: array of TSQLRecordClass;
  const SQLSelect: RawUTF8; SQLWhereFormat: PUTF8Char;
  const Args, Bounds: array of const): TSQLTableJSON;
begin
  result := List(Tables,SQLSelect,FormatUTF8(SQLWhereFormat,Args,Bounds));
end;

function TSQLRestClient.RTreeMatch(DataTable: TSQLRecordClass;
  const DataTableBlobFieldName: RawUTF8; RTreeTable: TSQLRecordRTreeClass;
................................................................................

function ObjectToVariantDebug(Value: TObject): variant;
begin
  result := _JsonFast(ObjectToJSONDebug(Value));
end;

function ObjectToVariantDebug(Value: TObject;
  ContextFormat: PUTF8Char; const ContextArgs: array of const;
  const ContextName: RawUTF8): variant;
begin
  result := _JsonFast(ObjectToJSONDebug(Value));
  if ContextFormat<>'' then
    if ContextFormat[0]='{' then
      _ObjAddProps([ContextName,_JsonFastFmt(ContextFormat,[],ContextArgs)],result) else
      _ObjAddProps([ContextName,FormatUTF8(ContextFormat,ContextArgs)],result);
end;

function UrlEncode(const NameValuePairs: array of const): RawUTF8;
// (['select','*','where','ID=12','offset',23,'object',aObject]);
var A, n: PtrInt;
................................................................................
    if PosEx(RawUTF8(':('),aDestWhereSQL,1)>0 then
      // statement is globaly inlined -> cache prepared statement
      SQL := 'SELECT % FROM %,% WHERE %.Source=:(%): AND %.Dest=%.RowID AND %' else
      // statement is not globaly inlined -> no caching of prepared statement
      SQL := 'SELECT % FROM %,% WHERE %.Source=% AND %.Dest=%.RowID AND %';
  result := aClient.ExecuteList([PSQLRecordClass(Self)^,
     TSQLRecordClass(SelfProps.Props.fRecordManyDestProp.ObjectClass)],
    FormatUTF8(pointer(SQL),
      [Select, DestProps.Props.SQLTableName,SelfProps.Props.SQLTableName,
       SelfProps.Props.SQLTableName,aSourceID, SelfProps.Props.SQLTableName,
       DestProps.Props.SQLTableName, aDestWhereSQL]));
end;

function TSQLRecordMany.DestGet(aClient: TSQLRest;
  out DestIDs: TIDDynArray): boolean;
................................................................................
     (aClient=nil) then
    Result := '' else begin
    if aAndWhereSQL<>'' then
      if PosEx(RawUTF8(':('),aAndWhereSQL,1)>0 then
        Result := '%:(%): AND %' else // inlined parameters
        Result := '%% AND %' else // no inlined parameters -> not cached
      Result := '%:(%):'; // no additional where clause -> inline ID
    Result := FormatUTF8(pointer(result),[FieldName[isDest],aID,aAndWhereSQL]);
  end;
end;

function TSQLRecordMany.SourceGet(aClient: TSQLRest; aDestID: TID;
  out SourceIDs: TIDDynArray): boolean;
var Where: RawUTF8;
begin
................................................................................
  end;

  EInterfaceStub = class(EInterfaceFactoryException)
  public
    constructor Create(Sender: TInterfaceStub; const Method: TServiceMethod;
      const Error: RawUTF8); overload;
    constructor Create(Sender: TInterfaceStub; const Method: TServiceMethod;
      Format: PUTF8Char; const Args: array of const); overload;
  end;


constructor TInterfacedObjectFake.Create(aFactory: TInterfaceFactory;
  aInvoke: TOnFakeInstanceInvoke; aNotifyDestroy: TOnFakeInstanceDestroy);
begin
  inherited Create(aFactory,aInvoke,aNotifyDestroy);
................................................................................
function TInterfacedObjectFake.FakeCall(var aCall: TFakeCallStack): Int64;
{$ifdef CPUARM}
begin
  raise EInterfaceFactoryException.Create('You encountered an ALF! ARM not yet supported');
end;
{$else}
var method: ^TServiceMethod;
procedure RaiseError(Format: PUTF8Char; const Args: array of const);
begin
  raise EInterfaceFactoryException.CreateUTF8('Invalid %.FakeCall() for %.%: %',
    [self,fFactory.fInterfaceTypeInfo^.Name,method^.URI,FormatUTF8(Format,Args)]);
end;
var resultType: TServiceMethodValueType; // type of value stored into result
procedure InternalProcess;
var Params: TJSONSerializer;
................................................................................
    PP: ^PPTypeInfo absolute P;
    Ancestor: PTypeInfo;
    Kind: TMethodKind;
    f: TParamFlags;
    m,a: integer;
    n: cardinal;
    aURI: RawUTF8;
procedure RaiseError(Format: PUTF8Char; const Args: array of const);
begin
  raise EInterfaceFactoryException.CreateUTF8(
    '%.AddMethodsFromTypeInfo: %.% %',
    [self,fInterfaceTypeInfo^.Name,aURI,FormatUTF8(Format,Args)]);
end;
begin
  // handle interface inheritance via recursive calls
................................................................................
  const Method: TServiceMethod; const Error: RawUTF8);
begin
  inherited CreateUTF8('Error in % for %.% - %',
    [Sender,Sender.fInterface.fInterfaceTypeInfo^.Name,Method.URI,Error]);
end;

constructor EInterfaceStub.Create(Sender: TInterfaceStub;
  const Method: TServiceMethod; Format: PUTF8Char; const Args: array of const);
begin
  Create(Sender,Method,FormatUTF8(Format,Args));
end;

function TInterfaceStubLog.Results: RawUTF8;
begin
  if CustomResults='' then
................................................................................
begin
  fSender := aSender;
  fMethod := aMethod;
  fParams := aParams;
  fEventParams := aEventParams;
end;

procedure TOnInterfaceStubExecuteParamsAbstract.Error(Format: PUTF8Char; const Args: array of const);

begin
  Error(FormatUTF8(Format,Args));
end;

procedure TOnInterfaceStubExecuteParamsAbstract.Error(const aErrorMessage: RawUTF8);
begin
  fFailed := true;
................................................................................
  with TInterfacedObjectFake.Create(fInterface,Invoke,InstanceDestroyed) do begin
    pointer(aStubbedInterface) := @fVTable;
    _AddRef;
  end;
end;

function TInterfaceStub.InternalCheck(aValid,aExpectationFailed: boolean;
  aErrorMsgFmt: PUTF8Char; const aErrorMsgArgs: array of const): boolean;
begin
  result := aValid;
  if aExpectationFailed and not aValid then
    raise EInterfaceStub.CreateUTF8('%.InternalCheck(%) failed: %',
      [self,fInterface.fInterfaceTypeInfo^.Name,FormatUTF8(aErrorMsgFmt,aErrorMsgArgs)]);
end;

................................................................................
constructor TInterfaceMock.Create(const aGUID: TGUID; aTestCase: TSynTestCase);
begin
  inherited Create(aGUID);
  fTestCase := aTestCase;
end;

function TInterfaceMock.InternalCheck(aValid,aExpectationFailed: boolean;
      aErrorMsgFmt: PUTF8Char; const aErrorMsgArgs: array of const): boolean; 
begin
  if fTestCase=nil then
    result := inherited InternalCheck(aValid,aExpectationFailed,aErrorMsgFmt,aErrorMsgArgs) else begin
    if aValid xor (imoMockFailsWillPassTestCase in Options) then
      fTestCase.Check(true) else
      fTestCase.Check(false,UTF8ToString(FormatUTF8(aErrorMsgFmt,aErrorMsgArgs)));
    result := true; // do not raise any exception at this stage for TInterfaceMock






>
>







 







|







 







|



|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|








|



|







 







|












|







 







|







 







|







 







|







 







|








|







 







|







 







|





|







 







|
>







 







>
|







 







|







 







|







 







|







 







|







 







|
|









|
|







 







|







 







<
<
<
<







 







|



|




|


|


|



|



|


|





|










|







 







|







|







 







|


|






|


|







 







|







|







 







|







 







|











|













|







 







|

>

<
>
|
|
|
|

|
|

|
|

<
>

|
|
<
>
|







 







|







 







|





|







 







|







 







|





|







 







|







 







|











|







 







|







 







|




|




|



|

|



|
<
|
|
|







<

|







 







|







 







|







 







|





|







 







|


|







 







|







 







|







 







|







 







|






|







 







|





|







 







|




|







 







|







 







|







 







|







 







|







 







|







 







|







 







|
>







 







|







 







|







708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
....
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
....
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
....
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376
....
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
5391
5392
....
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
5439
5440
....
5447
5448
5449
5450
5451
5452
5453
5454
5455
5456
5457
5458
5459
5460
5461
....
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
5552
5553
....
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
5762
5763
5764
5765
....
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
5786
5787
....
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
....
8562
8563
8564
8565
8566
8567
8568
8569
8570
8571
8572
8573
8574
8575
8576
....
8843
8844
8845
8846
8847
8848
8849
8850
8851
8852
8853
8854
8855
8856
8857
....
9126
9127
9128
9129
9130
9131
9132
9133
9134
9135
9136
9137
9138
9139
9140
.....
10042
10043
10044
10045
10046
10047
10048
10049
10050
10051
10052
10053
10054
10055
10056
.....
10237
10238
10239
10240
10241
10242
10243
10244
10245
10246
10247
10248
10249
10250
10251
10252
10253
10254
10255
10256
10257
10258
10259
10260
10261
10262
10263
10264
.....
10356
10357
10358
10359
10360
10361
10362
10363
10364
10365
10366
10367
10368
10369
10370
10371
10372
10373
10374
10375
10376
10377
10378
10379
10380
10381
10382
10383
.....
10415
10416
10417
10418
10419
10420
10421
10422
10423
10424
10425
10426
10427
10428
10429
.....
10468
10469
10470
10471
10472
10473
10474
10475
10476
10477
10478
10479
10480
10481
10482
.....
10489
10490
10491
10492
10493
10494
10495
10496
10497
10498
10499
10500
10501
10502
10503
.....
10528
10529
10530
10531
10532
10533
10534
10535
10536
10537
10538
10539
10540
10541
10542
10543
10544
10545
10546
10547
10548
10549
10550
10551
.....
10556
10557
10558
10559
10560
10561
10562
10563
10564
10565
10566
10567
10568
10569
10570
.....
10571
10572
10573
10574
10575
10576
10577
10578
10579
10580
10581
10582
10583
10584
10585
10586
10587
10588
10589
10590
10591
.....
10618
10619
10620
10621
10622
10623
10624
10625
10626
10627
10628
10629
10630
10631
10632
10633
.....
10649
10650
10651
10652
10653
10654
10655
10656
10657
10658
10659
10660
10661
10662
10663
10664
.....
10732
10733
10734
10735
10736
10737
10738
10739
10740
10741
10742
10743
10744
10745
10746
.....
10932
10933
10934
10935
10936
10937
10938
10939
10940
10941
10942
10943
10944
10945
10946
.....
11145
11146
11147
11148
11149
11150
11151
11152
11153
11154
11155
11156
11157
11158
11159
.....
12061
12062
12063
12064
12065
12066
12067
12068
12069
12070
12071
12072
12073
12074
12075
.....
13384
13385
13386
13387
13388
13389
13390
13391
13392
13393
13394
13395
13396
13397
13398
13399
13400
13401
13402
13403
13404
13405
13406
13407
13408
13409
13410
.....
14672
14673
14674
14675
14676
14677
14678
14679
14680
14681
14682
14683
14684
14685
14686
.....
21143
21144
21145
21146
21147
21148
21149




21150
21151
21152
21153
21154
21155
21156
.....
21158
21159
21160
21161
21162
21163
21164
21165
21166
21167
21168
21169
21170
21171
21172
21173
21174
21175
21176
21177
21178
21179
21180
21181
21182
21183
21184
21185
21186
21187
21188
21189
21190
21191
21192
21193
21194
21195
21196
21197
21198
21199
21200
21201
21202
21203
21204
21205
21206
21207
21208
21209
21210
21211
21212
21213
21214
21215
.....
23134
23135
23136
23137
23138
23139
23140
23141
23142
23143
23144
23145
23146
23147
23148
23149
23150
23151
23152
23153
23154
23155
23156
.....
23312
23313
23314
23315
23316
23317
23318
23319
23320
23321
23322
23323
23324
23325
23326
23327
23328
23329
23330
23331
23332
23333
23334
23335
23336
23337
23338
23339
.....
23926
23927
23928
23929
23930
23931
23932
23933
23934
23935
23936
23937
23938
23939
23940
23941
23942
23943
23944
23945
23946
23947
23948
.....
23971
23972
23973
23974
23975
23976
23977
23978
23979
23980
23981
23982
23983
23984
23985
.....
23999
24000
24001
24002
24003
24004
24005
24006
24007
24008
24009
24010
24011
24012
24013
24014
24015
24016
24017
24018
24019
24020
24021
24022
24023
24024
24025
24026
24027
24028
24029
24030
24031
24032
24033
24034
24035
24036
24037
24038
24039
.....
24180
24181
24182
24183
24184
24185
24186
24187
24188
24189
24190

24191
24192
24193
24194
24195
24196
24197
24198
24199
24200
24201
24202

24203
24204
24205
24206

24207
24208
24209
24210
24211
24212
24213
24214
24215
.....
25872
25873
25874
25875
25876
25877
25878
25879
25880
25881
25882
25883
25884
25885
25886
.....
25924
25925
25926
25927
25928
25929
25930
25931
25932
25933
25934
25935
25936
25937
25938
25939
25940
25941
25942
25943
25944
.....
25952
25953
25954
25955
25956
25957
25958
25959
25960
25961
25962
25963
25964
25965
25966
.....
26149
26150
26151
26152
26153
26154
26155
26156
26157
26158
26159
26160
26161
26162
26163
26164
26165
26166
26167
26168
26169
.....
26218
26219
26220
26221
26222
26223
26224
26225
26226
26227
26228
26229
26230
26231
26232
.....
26235
26236
26237
26238
26239
26240
26241
26242
26243
26244
26245
26246
26247
26248
26249
26250
26251
26252
26253
26254
26255
26256
26257
26258
26259
26260
26261
.....
26266
26267
26268
26269
26270
26271
26272
26273
26274
26275
26276
26277
26278
26279
26280
.....
26293
26294
26295
26296
26297
26298
26299
26300
26301
26302
26303
26304
26305
26306
26307
26308
26309
26310
26311
26312
26313
26314
26315
26316
26317
26318
26319
26320

26321
26322
26323
26324
26325
26326
26327
26328
26329
26330

26331
26332
26333
26334
26335
26336
26337
26338
26339
.....
26368
26369
26370
26371
26372
26373
26374
26375
26376
26377
26378
26379
26380
26381
26382
.....
26525
26526
26527
26528
26529
26530
26531
26532
26533
26534
26535
26536
26537
26538
26539
.....
26899
26900
26901
26902
26903
26904
26905
26906
26907
26908
26909
26910
26911
26912
26913
26914
26915
26916
26917
26918
26919
.....
27074
27075
27076
27077
27078
27079
27080
27081
27082
27083
27084
27085
27086
27087
27088
27089
27090
27091
.....
29237
29238
29239
29240
29241
29242
29243
29244
29245
29246
29247
29248
29249
29250
29251
.....
29925
29926
29927
29928
29929
29930
29931
29932
29933
29934
29935
29936
29937
29938
29939
.....
29948
29949
29950
29951
29952
29953
29954
29955
29956
29957
29958
29959
29960
29961
29962
.....
30496
30497
30498
30499
30500
30501
30502
30503
30504
30505
30506
30507
30508
30509
30510
30511
30512
30513
30514
30515
30516
30517
.....
34675
34676
34677
34678
34679
34680
34681
34682
34683
34684
34685
34686
34687
34688
34689
34690
34691
34692
34693
34694
34695
.....
35125
35126
35127
35128
35129
35130
35131
35132
35133
35134
35135
35136
35137
35138
35139
35140
35141
35142
35143
35144
.....
36655
36656
36657
36658
36659
36660
36661
36662
36663
36664
36665
36666
36667
36668
36669
.....
36752
36753
36754
36755
36756
36757
36758
36759
36760
36761
36762
36763
36764
36765
36766
.....
39469
39470
39471
39472
39473
39474
39475
39476
39477
39478
39479
39480
39481
39482
39483
.....
39532
39533
39534
39535
39536
39537
39538
39539
39540
39541
39542
39543
39544
39545
39546
.....
40344
40345
40346
40347
40348
40349
40350
40351
40352
40353
40354
40355
40356
40357
40358
.....
40565
40566
40567
40568
40569
40570
40571
40572
40573
40574
40575
40576
40577
40578
40579
.....
40610
40611
40612
40613
40614
40615
40616
40617
40618
40619
40620
40621
40622
40623
40624
40625
.....
40753
40754
40755
40756
40757
40758
40759
40760
40761
40762
40763
40764
40765
40766
40767
.....
41238
41239
41240
41241
41242
41243
41244
41245
41246
41247
41248
41249
41250
41251
41252
    - BREAKING CHANGE with newly added reSQLSelectWithoutTable security policy
      flags in TSQLAccessRight.AllowRemoteExecute - older applications which
      expected any SELECT statement to be executed on the server may break:
      you need to explicitely set this flag for the User's TSQLAuthGroup - note
      that SELECT with a simple table name in its FROM clause will now be
      checked againsts TSQLAccessRight.GET[] access rights
    - BREAKING CHANGE: added aSentData parameter to TNotifySQLEvent/OnUpdateEvent
    - BREAKING CHANGE: SQL "where" clause defined as PUTF8Char constant text
      have been changed into RawUTF8, to let the compiler fully handle Unicode
    - remove some unused TPropInfo methods, which were duplicates of the
      TSQLPropInfo cleaner class hierarchy: SetValue/GetValue/GetValueVar
      GetBinary/SetBinary GetVariant/SetVariant NormalizeValue/SameValue GetHash
      IsSimpleField AppendName GetCaption GetSQLFromFieldValue SetFieldAddr
    - following the Liskov substitution principle, Execute/ExecuteFmt and
      protected EngineExecute() are defined for TSQLRest, replacing ExecuteAll()
    - TSQLRestServerRemoteDB will now redirect into any TSQLRest instance
................................................................................

/// will serialize any TObject into a TDocVariant document
// - just a wrapper around _JsonFast(ObjectToJSONDebug()) with an optional
// "Context":"..." text message
// - if the supplied context format matches '{....}' then it will be added
// as a corresponding TDocVariant JSON object
function ObjectToVariantDebug(Value: TObject;
  const ContextFormat: RawUTF8; const ContextArgs: array of const;
  const ContextName: RawUTF8='context'): variant; overload;

/// will serialize any TObject into a TDocVariant document
// - just a wrapper around _JsonFast(ObjectToJSONDebug()) 
function ObjectToVariantDebug(Value: TObject): variant; overload;

/// encode supplied parameters to be compatible with URI encoding
................................................................................
    // supplied error text
    // - if no ErrorMessage is specified, will return a default text
    // corresponding to the Status code
    procedure Error(const ErrorMessage: RawUTF8='';
      Status: integer=HTML_BADREQUEST); overload;
    /// use this method to send back an error to the caller
    // - implementation is just a wrapper over Error(FormatUTF8(Format,Args))
    procedure Error(const Format: RawUTF8; const Args: array of const;
      Status: integer=HTML_BADREQUEST); overload;
    /// use this method to send back an error to the caller
    // - will serialize the supplied exception, with an optional error message
    procedure Error(E: Exception; const Format: RawUTF8; const Args: array of const;
      Status: integer=HTML_BADREQUEST); overload;
    /// at Client Side, compute URI and BODY according to the routing scheme
    // - abstract implementation which is to be overridden
    // - as input, method should be the method name to be executed,
    // params should contain the incoming parameters as JSON CSV (without []),
    // and clientDriven ID should contain the optional Client ID value 
    // - at output, should update the HTTP uri corresponding to the proper
................................................................................
      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
    - note that this method prototype changed with revision 1.17 of the
      framework: array of const used to be ParamsSQLWhere and '%' in the
      FormatSQLWhere statement, whereas it now expects bound parameters as '?' }
    constructor Create(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
      const BoundsSQLWhere: array of const); overload;
    {/ this constructor initializes the object as above, and fills its content
      from a client or server connection, using a specified WHERE clause
      with parameters
      - the FormatSQLWhere clause will replace all '%' chars with the supplied
      ParamsSQLWhere[] values, and all '?' chars with BoundsSQLWhere[] values,
      as :(...): inlined parameters - you should either call:
................................................................................
      or (letting the inlined parameters being computed by FormatUTF8)
      !  Rec := TSQLMyRecord.Create(aClient,'Count=?',[],[aCount]);
      or even better, using the other Create overloaded constructor:
      !  Rec := TSQLMyRecord.Create(aClient,'Count=?',[aCount]);
      - using '?' and BoundsSQLWhere[] is perhaps more readable in your code, and
      will in all case create a request with :(..): inline parameters, with
      automatic RawUTF8 quoting if necessary }
    constructor Create(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
      const ParamsSQLWhere, BoundsSQLWhere: array of const); overload;

    {/ this constructor initializes the object as above, and prepares itself to
      loop through a statement using a specified WHERE clause
      - this method creates a TSQLTableJSON, retrieves all records corresponding
        to the WHERE clause, then call FillPrepare - previous Create(aClient)
        methods retrieve only one record, this one more multiple rows
................................................................................
        you may need  to access only one or several fields, and will save remote
        bandwidth by specifying the needed fields
      - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs
      - note that you should not use this aCustomFieldsCSV optional parameter if
        you want to Update the retrieved record content later, since any
        missing fields will be left with previous values - but BatchUpdate() can be
        safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    constructor CreateAndFillPrepare(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
      const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8=''); overload;
    {/ this constructor initializes the object as above, and prepares itself to
      loop through a statement using a specified WHERE clause
      - this method creates a TSQLTableJSON, retrieves all records corresponding
        to the WHERE clause, then call FillPrepare - previous Create(aClient)
        methods retrieve only one record, this one more multiple rows
      - you should then loop for all rows using 'while Rec.FillOne do ...'
................................................................................
        you may need  to access only one or several fields, and will save remote
        bandwidth by specifying the needed fields
      - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs
      - note that you should not use this aCustomFieldsCSV optional parameter if
        you want to Update the retrieved record content later, since any
        missing fields will be left with previous values - but BatchUpdate() can be
        safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    constructor CreateAndFillPrepare(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
      const ParamsSQLWhere, BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''); overload;
    {/ this constructor initializes the object as above, and prepares itself to
      loop through a given list of IDs
      - this method creates a TSQLTableJSON, retrieves all records corresponding
        to the specified IDs, then call FillPrepare - previous Create(aClient)
        methods retrieve only one record, this one more multiple rows
................................................................................
        $  p.Owner='mark' and c.Name='for boy' and (s.Name='small' or s.Name='medium')
      - you SHALL call explicitely the FillClose method before using any
        methods of nested TSQLRecordMany instances which may override the Dest
        instance content (e.g. ManySelect) to avoid any GPF
      - the aFormatSQLJoin clause will replace all '%' chars with the supplied
        aParamsSQLJoin[] supplied values, and bind all '?' chars as bound
        parameters with aBoundsSQLJoin[] values }
    constructor CreateAndFillPrepareMany(aClient: TSQLRest; const aFormatSQLJoin: RawUTF8;
      const aParamsSQLJoin, aBoundsSQLJoin: array of const);

    {/ this method create a clone of the current record, with same ID and properties
      - copy all COPIABLE_FIELDS, i.e. all fields excluding tftMany (because
        those fields don't contain any data, but a TSQLRecordMany instance
        which allow to access to the pivot table data)
      - you can override this method to allow custom copy of the object,
................................................................................
       you may need  to access only one or several fields, and will save remote
       bandwidth by specifying the needed fields
     - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs 
     - note that you should not use this aCustomFieldsCSV optional parameter if
       you want to Update the retrieved record content later, since any
       missing fields will be left with previous values - but BatchUpdate() can be
       safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    function FillPrepare(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
      const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8=''): boolean; overload;
    {/ prepare to get values using a specified WHERE clause with '%' and '?' parameters
     - returns true in case of success, false in case of an error during SQL request
     - then call FillRow(1..Table.RowCount) to get any row value
     - or you can also loop through all rows with
     ! while Rec.FillOne do
     !   dosomethingwith(Rec);
................................................................................
       you may need  to access only one or several fields, and will save remote
       bandwidth by specifying the needed fields
     - if aCustomFieldsCSV='*', it will retrieve all fields, including BLOBs 
     - note that you should not use this aCustomFieldsCSV optional parameter if
       you want to Update the retrieved record content later, since any
       missing fields will be left with previous values - but BatchUpdate() can be
       safely used after FillPrepare (will set only ID, TModTime and mapped fields) }
    function FillPrepare(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
      const ParamsSQLWhere, BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): boolean; overload;
    {/ prepare to get values from a list of IDs
     - returns true in case of success, false in case of an error during SQL request
     - then call FillRow(1..Table.RowCount) to get any row value
     - or you can also loop through all rows with
     ! while Rec.FillOne do
................................................................................
     - the FormatSQLWhere clause will replace all '%' chars with the supplied
       ParamsSQLWhere[] supplied values, and bind all '?' chars as parameters
       with BoundsSQLWhere[] values
     - you SHALL call explicitely the FillClose method before using any
       methods of nested TSQLRecordMany instances which may override the Dest
       instance content (e.g. ManySelect) to avoid any GPF
     - is used by TSQLRecord.CreateAndFillPrepareMany constructor }
    function FillPrepareMany(aClient: TSQLRest; const aFormatSQLJoin: RawUTF8;
      const aParamsSQLJoin, aBoundsSQLJoin: array of const): boolean;
    {/ fill all published properties of an object from a TSQLTable prepared row
      - FillPrepare() must have been called before
      - if Dest is nil, this object values are filled
      - if Dest is not nil, this object values will be filled, but it won't
        work with TSQLRecordMany properties (i.e. after FillPrepareMany call)
      - ID field is updated if first Field Name is 'ID'
................................................................................
  public
    /// constructor of one parameters marshalling instance
    constructor Create(aSender: TInterfaceStub; aMethod: PServiceMethod;
      const aParams,aEventParams: RawUTF8); virtual;
    /// call this method if the callback implementation failed
    procedure Error(const aErrorMessage: RawUTF8); overload;
    /// call this method if the callback implementation failed
    procedure Error(const Format: RawUTF8; const Args: array of const); overload;
    /// the stubbing / mocking generator
    property Sender: TInterfaceStub read fSender;
    /// the mocking generator associated test case
    // - will raise an exception if the associated Sender generator is not
    // a TInterfaceMock
    property TestCase: TSynTestCase read GetSenderAsMockTestCase;
    /// pointer to the method which is to be executed
................................................................................
    fLogs: TInterfaceStubLogDynArray;
    fLog: TDynArray;
    fLogCount: integer;
    fInterfaceExpectedTraceHash: cardinal;
    function TryResolve(aInterface: PTypeInfo; out Obj): boolean; override;
    procedure InternalGetInstance(out aStubbedInterface); virtual;
    function InternalCheck(aValid,aExpectationFailed: boolean;
      const aErrorMsgFmt: RawUTF8; const aErrorMsgArgs: array of const): boolean; virtual;
    // match TOnFakeInstanceInvoke callback signature
    function Invoke(const aMethod: TServiceMethod;
      const aParams: RawUTF8; aResult, aErrorMsg: PRawUTF8;
      aClientDrivenID: PCardinal; aServiceCustomAnswer: PServiceCustomAnswer): boolean;
    // will launch InternalCheck() process if some expectations defined by
    // ExpectsCount() are not met, i.e. raise an exception for TInterfaceStub
    // or notify the associated test case for TInterfaceMock
................................................................................
  // are defined before running the test, and verification is performed
  // when the instance is released - use TInterfaceMockSpy if you prefer the
  // more explicit run-verify pattern
  TInterfaceMock = class(TInterfaceStub)
  protected
    fTestCase: TSynTestCase;
    function InternalCheck(aValid,aExpectationFailed: boolean;
      const aErrorMsgFmt: RawUTF8; const aErrorMsgArgs: array of const): boolean; override;
  public
    /// initialize an interface mock from TypeInfo(IMyInterface)
    // - aTestCase.Check() will be called in case of mocking failure
    // ! procedure TMyTestCase.OneTestCaseMethod;
    // ! var Persist: IPersistence;
    // ! ...
    // !   TInterfaceMock.Create(TypeInfo(IPersistence),Persist,self).
................................................................................
    fLogFamily: TSynLogFamily; // =SQLite3Log.Family by default
    procedure SetLogClass(aClass: TSynLogClass); virtual;
    function GetLogClass: TSynLogClass;
    {$endif}
    /// log the corresponding text (if logging is enabled)
    procedure InternalLog(const Text: RawUTF8; Level: TSynLogInfo); overload;
      {$ifdef HASINLINE}inline;{$endif}
    procedure InternalLog(const Format: RawUTF8; const Args: array of const;
      Level: TSynLogInfo); overload;
    /// internal method used by Delete(Table,SQLWhere) method
    function InternalDeleteNotifyAndGetIDs(Table: TSQLRecordClass; const SQLWhere: RawUTF8;
      var IDs: TIDDynArray): boolean; 
    /// retrieve the server time stamp
    // - default implementation will use fServerTimeStampOffset to compute
    // the value from PC time (i.e. NowUTC+fServerTimeStampOffset as TTimeLog)
................................................................................
    // - example of use:
    // ! aClient.OneFieldValue(TSQLRecord,'Name','ID=?',[aID])
    // - call internaly 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: TSQLRecordClass; 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(TSQLRecord,'Name','%=?',['ID'],[aID])
    // - call internaly ExecuteList() to get the value
    function OneFieldValue(Table: TSQLRecordClass; 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: TSQLRecordClass; 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(TSQLRecord,'Name',23)
    // - call internaly ExecuteList() to get the value
    function OneFieldValue(Table: TSQLRecordClass;
      const FieldName: RawUTF8; WhereID: TID): RawUTF8; overload;
    /// get the UTF-8 encoded value of some fields with a Where Clause
................................................................................
    // - example of use:
    // ! aList := aClient.MultiFieldValues(TSQLRecord,'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: TSQLRecordClass; const FieldNames: RawUTF8;
      const WhereClauseFormat: RawUTF8; const BoundsSQLWhere: array of const): TSQLTableJSON; overload;
    /// Execute directly a SQL statement, expecting a list of results
    // - return a result table 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(TSQLRecord,'Name','%=?',['ID'],[aID]);
    // - call overloaded MultiFieldValues() / ExecuteList() to get the list
    function MultiFieldValues(Table: TSQLRecordClass; const FieldNames: RawUTF8;
      const WhereClauseFormat: RawUTF8; const Args, Bounds: array of const): TSQLTableJSON; 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: TSQLRecordClass; ID: TID;
................................................................................
    /// get a member from a SQL statement
    // - implements REST GET collection
    // - return true on success
    // - same as Retrieve(const SQLWhere: RawUTF8; Value: TSQLRecord) 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)
    function Retrieve(const WhereClauseFmt: RawUTF8; const Args,Bounds: array of const;
      Value: TSQLRecord): 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
................................................................................
    // - aCustomFieldsCSV can be the CSV list of field names to be retrieved
    // - if aCustomFieldsCSV is '', will get all simple fields, excluding BLOBs
    // - if aCustomFieldsCSV 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: TSQLRecordClass; const FormatSQLWhere: RawUTF8;
      const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): TObjectList; overload;
    /// 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
................................................................................
    // 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 would be the fastest, but complex
    // types like dynamic array would be returned as Base64-encoded blob value -
    // if you need proper JSON access to those, see RetrieveDocVariantArray()
    function RetrieveListJSON(Table: TSQLRecordClass; const FormatSQLWhere: RawUTF8;
      const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): RawJSON;
    {$ifndef NOVARIANTS}
    /// 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,
................................................................................
    // - if aCustomFieldsCSV is '', will get all simple fields, excluding BLOBs
    // - if aCustomFieldsCSV is '*', will get ALL fields, including ID and BLOBs
    // - the data will be converted to variants and TDocVariant following the
    // TSQLRecord layout, so complex types like dynamic array would be returned
    // as a true array of values (in contrast to the RetrieveListJSON method)
    function RetrieveDocVariantArray(Table: TSQLRecordClass;
      const ObjectName: RawUTF8;
      const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
      const CustomFieldsCSV: RawUTF8; FirstRecordID: PID=nil;
      LastRecordID: PID=nil): variant; overload;
    /// 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
    // TSQLRecord layout, so complex types like dynamic array would be returned
    // as a true array of values 
    function RetrieveDocVariant(Table: TSQLRecordClass;
      const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
      const CustomFieldsCSV: RawUTF8): variant; 
    {$endif NOVARIANTS}
    /// 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
................................................................................
    // - if aCustomFieldsCSV 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: TSQLRecordClass;
      const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): boolean;
    /// Execute directly a SQL statement, expecting a list of results
    // - return a result table on success, nil on failure
    // - will call EngineList() abstract method to retrieve its JSON content
    function ExecuteList(const Tables: array of TSQLRecordClass; const SQL: RawUTF8): TSQLTableJSON; virtual;
    /// 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; virtual;
    /// 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: TSQLRecordClass; aID: TID): boolean; overload; virtual; abstract;
    /// unlock the corresponding record
................................................................................
    // TSQLRecordMany 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 internaly the Add virtual method above
    function Add(aTable: TSQLRecordClass; const aSimpleFields: array of const;
      ForcedID: TID=0): TID; overload;
    /// update a member from Value simple fields content
    // - implements REST PUT collection
    // - return true on success
    // - the TSQLRawBlob(BLOB) fields values are not updated by this method, to
    // preserve bandwidth: use the UpdateBlob() methods for handling BLOB fields
    // - the TSQLRecordMany fields are not set either: they are separate
    // instances created by TSQLRecordMany.Create, with dedicated methods to
................................................................................
    /// update a member from a supplied list of simple field values
    // - implements REST PUT collection
    // - the aSimpleFields parameters MUST follow explicitely both count and
    // order of published properties of the supplied aTable class, excepting the
    // TSQLRawBlob and TSQLRecordMany kind (i.e. only so called "simple fields")
    // - return true on success
    // - call internaly the Update() / EngineUpdate() virtual methods 
    function Update(aTable: TSQLRecordClass; 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 PUT collection on Value.ID
    // - will return the created or updated ID
    function AddOrUpdate(Value: TSQLRecord): 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
................................................................................
    // - 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: TSQLRecordClass; const FormatSQLWhere: RawUTF8;
      const BoundsSQLWhere: array of const): boolean; overload;

    /// access the internal caching parameters for a given TSQLRecord
    // - purpose of this caching mechanism is to speed up retrieval of some
    // common values at either Client or Server level (like configuration settings)
    // - by default, this CRUD level per-ID cache is disabled
    // - use Cache.SetCache() and Cache.SetTimeOut() methods to set the appropriate
................................................................................
    // double, currency, RawUTF8 values to be bound to the request as parameters
    // - aCustomFieldsCSV can be the CSV list of field names to be retrieved
    // - if aCustomFieldsCSV is '', will get all simple fields, excluding BLOBs
    // - if aCustomFieldsCSV is '*', will get ALL fields, including ID and BLOBs
    // - return a TObjectList<T> on success (possibly with Count=0) - caller is
    // responsible of freeing the instance
    // - return nil on error
    function RetrieveList<T: TSQLRecord>(const FormatSQLWhere: RawUTF8;
      const BoundsSQLWhere: array of const;
      const aCustomFieldsCSV: RawUTF8=''): TObjectList<T>; overload;
    {$endif}

    /// you can call this method in TThread.Execute to ensure that
    // the thread will be taken in account during process
    // - this abstract method won't do anything, but TSQLRestServer's will
................................................................................
    Where: PAnsiChar;
    /// returned JSON field value of optional total row counts
    // - default value is nil, i.e. no total row counts field
    // - computing total row counts can be very expensive, depending on the
    // database back-end used (especially for external databases)
    // - can be set e.g. to ',"totalRows":%' value (note that the initial "," is
    // expected by the produced JSON content, and % will be set with the value)
    SendTotalRowsCountFmt: RawUTF8;
  end;

  /// used for statistics update in TSQLRestServer.URI()
  TSQLRestServerStats = class(TPersistent)
  private
    /// used to determine if something changed
    fLastIncomingBytes: QWord;
................................................................................
    fTrackChangesHistoryTableIndex: TIntegerDynArray;
    fTrackChangesHistory: array of record
      CurrentRow: integer;
      MaxSentDataJsonRow: integer;
      MaxRevisionJSON: integer;
      MaxUncompressedBlobSize: integer;
    end;
    function CreateBackgroundThread(const Format: RawUTF8; const Args: array of const): TSynBackgroundThreadMethod;
    function GetAuthenticationSchemesCount: integer;
    /// fast get the associated static server, if any
    function GetStaticDataServer(aClass: TSQLRecordClass): TSQLRest;
    /// retrieve a TSQLRestStorage instance associated to a Virtual Table
    // - is e.g. TSQLRestStorageInMemory instance associated to a
    // TSQLVirtualTableBinary or TSQLVirtualTableJSON class
    // - may be a TSQLRestStorageExternal (as defined in mORMotDB unit)
................................................................................
    /// retrieve a list of members as a TSQLTable
    // - implements REST GET collection
    // - in this version, the WHERE clause can be created with the same format
    // as FormatUTF8() function, replacing all '%' chars with Args[] values
    // - using inlined parameters via :(...): in SQLWhereFormat is always a good idea
    // - for one TClass, you should better use TSQLRest.MultiFieldValues()
    // - will call the List virtual method internaly
    function ListFmt(const Tables: array of TSQLRecordClass;
      const SQLSelect, SQLWhereFormat: RawUTF8; const Args: array of const): TSQLTableJSON; overload;
    /// retrieve a list of members as a TSQLTable
    // - implements REST GET collection
    // - 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 := ListFmt([TSQLRecord],'Name','ID=?',[],[aID]);
    // - for one TClass, you should better use TSQLRest.MultiFieldValues()
    // - will call the List virtual method internaly
    function ListFmt(const Tables: array of TSQLRecordClass;
      const SQLSelect, SQLWhereFormat: RawUTF8; const Args, Bounds: array of const): TSQLTableJSON; overload;
    /// dedicated method used to retrieve matching IDs using a fast R-Tree index
    // - a TSQLRecordRTree is associated to a TSQLRecord with a specified BLOB
    // field, and will call TSQLRecordRTree BlobToCoord and ContainedIn virtual
    // class methods to execute an optimized SQL query
    // - 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
................................................................................
  PAGINGPARAMETERS_YAHOO: TSQLRestServerURIPagingParameters = (
    Sort: 'SORT=';
    Dir: 'DIR=';
    StartIndex: 'STARTINDEX=';
    Results: 'RESULTS=';
    Select: 'SELECT=';
    Where: 'WHERE=';
    SendTotalRowsCountFmt: '');

  /// options to specify no index createon for TSQLRestServer.CreateMissingTables
  // and TSQLRecord.InitializeTable methods
  INITIALIZETABLE_NOINDEX: TSQLInitializeTableOptions =
    [itoNoIndex4ID..itoNoIndex4RecordReference];


................................................................................
  SetString(fPrivateCopy,PAnsiChar(pointer(aJSON)),len);
  CreateWithColumnTypes(ColumnTypes,aSQL,pointer(fPrivateCopy),len);
end;


{ TINIWriter }





procedure TINIWriter.WriteObject(Value: TObject; const SubCompName: RawUTF8='';
  WithSection: boolean=true);
var P: PPropInfo;
    i, V: integer;
    VT: shortstring; // for str()
    Obj: TObject;
    WS: WideString;
................................................................................
    VV: Variant;
    {$endif}
begin
  if Value<>nil then begin
    if WithSection then
      // new TObject.ClassName is UnicodeString (Delphi 20009) -> inline code with
      // vmtClassName = UTF-8 encoded text stored in a shortstring = -44
      Add(#13'[%]'#13,[PShortString(PPointer(PPtrInt(Value)^+vmtClassName)^)^]);
    for i := 1 to InternalClassPropInfo(PPointer(Value)^,P) do begin
      case P^.PropType^.Kind of
        tkInt64{$ifdef FPC}, tkQWord{$endif}:
          Add('%%=%'#13,[SubCompName,P^.Name,P^.GetInt64Prop(Value)]);
        {$ifdef FPC}tkBool,{$endif}
        tkEnumeration, tkInteger, tkSet: begin
          V := P^.GetOrdProp(Value);
          if V<>P^.Default then
            Add('%%=%'#13,[SubCompName,P^.Name,V]);
        end;
        {$ifdef FPC}tkAString,{$endif} tkLString:
          Add('%%=%'#13,[SubCompName,P^.Name,P^.GetLongStrValue(Value)]);
        tkFloat: begin
          VT[0] := AnsiChar(ExtendedToString(VT,P^.GetFloatProp(Value),DOUBLE_PRECISION));
          Add('%%=%'#13,[SubCompName,P^.Name,VT]);
        end;
        tkWString: begin
          P^.GetWideStrProp(Value,WS);
          Add('%%=%'#13,[SubCompName,P^.Name,WS]);
        end;
        {$ifdef UNICODE}
        tkUString: // write converted to UTF-8
          Add('%%=%'#13,[SubCompName,P^.Name,P^.GetUnicodeStrProp(Value)]);
        {$endif}
        tkDynArray: begin
          Add('%%=%'#13,[SubCompName,P^.Name]);
          AddDynArrayJSON(P^.GetDynArray(Value));
          Add(#13);
        end;
        {$ifdef PUBLISHRECORD}
        tkRecord{$ifdef FPC},tkObject{$endif}:
          Add('%%=%'#13,[SubCompName,P^.Name,BinToBase64WithMagic(
            RecordSave(P^.GetFieldAddr(Value)^,P^.PropType^))]);
        {$endif}
        tkClass: begin
          Obj := P^.GetObjProp(Value); 
          if (Obj<>nil) and Obj.InheritsFrom(TPersistent) then
             WriteObject(Obj,SubCompName+RawUTF8(P^.Name)+'.',false);
        end;
        {$ifndef NOVARIANTS}
        tkVariant: begin // stored as JSON, e.g. '1.234' or '"text"'
          P^.GetVariantProp(Value,VV);
          Add('%%=%'#13,[SubCompName,P^.Name,VariantSaveJSON(VV)]);
        end;
        {$endif}
      end; // tkString (shortstring) is not handled
      P := P^.Next;
    end;
  end;
end;
................................................................................
constructor TSQLRecord.Create(aClient: TSQLRest; const aSQLWhere: RawUTF8);
begin
  Create;
  if aClient<>nil then
    aClient.Retrieve(aSQLWhere,self);
end;

constructor TSQLRecord.Create(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
  const BoundsSQLWhere: array of const);
begin
  Create;
  if aClient<>nil then
    aClient.Retrieve(FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere),self);
end;

constructor TSQLRecord.Create(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
  const ParamsSQLWhere, BoundsSQLWhere: array of const);
begin
  Create;
  if aClient<>nil then
    aClient.Retrieve(FormatUTF8(FormatSQLWhere,ParamsSQLWhere,BoundsSQLWhere),self);
end;

................................................................................
  if T=nil then
    exit;
  T.OwnerMustFree := true;
  FillPrepare(T,aCheckTableName);
  result := true;
end;

function TSQLRecord.FillPrepare(aClient: TSQLRest; const FormatSQLWhere: RawUTF8;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8=''): boolean;
begin
  if (FormatSQLWhere='') or (high(BoundsSQLWhere)<0) then
    result := false else
    result := FillPrepare(aClient,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere),
      aCustomFieldsCSV);
end;

function TSQLRecord.FillPrepare(aClient: TSQLRest;
  const FormatSQLWhere: RawUTF8; const ParamsSQLWhere, BoundsSQLWhere: array of const;
  const aCustomFieldsCSV: RawUTF8): boolean;
begin
  if (FormatSQLWhere='') or ((high(ParamsSQLWhere)<0)and(high(BoundsSQLWhere)<0)) then
    result := false else
    result := FillPrepare(aClient,
      FormatUTF8(FormatSQLWhere,ParamsSQLWhere,BoundsSQLWhere),aCustomFieldsCSV);
end;

function TSQLRecord.FillPrepare(aClient: TSQLRest; const aIDs: array of Int64;
  const aCustomFieldsCSV: RawUTF8=''): boolean;
................................................................................
  if aTable=nil then
    exit;
  aTable.OwnerMustFree := true;
  FillPrepare(aTable);
end;

constructor TSQLRecord.CreateAndFillPrepare(aClient: TSQLRest;
  const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
  const aCustomFieldsCSV: RawUTF8='');
begin
  CreateAndFillPrepare(aClient,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere),
    aCustomFieldsCSV);
end;

constructor TSQLRecord.CreateAndFillPrepare(aClient: TSQLRest;
  const FormatSQLWhere: RawUTF8; const ParamsSQLWhere,
  BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8);
begin
  CreateAndFillPrepare(aClient,
    FormatUTF8(FormatSQLWhere,ParamsSQLWhere,BoundsSQLWhere),aCustomFieldsCSV);
end;

constructor TSQLRecord.CreateAndFillPrepare(aClient: TSQLRest;
................................................................................
begin
  Create;
  props := aClient.Model.Props[PSQLRecordClass(Self)^];
  if props.props.JoinedFields=nil then
    raise EORMException.CreateUTF8('No nested TSQLRecord to JOIN in %',[self]);
  SQL := props.SQL.SelectAllJoined;
  if aFormatSQLJoin<>'' then
    SQL := SQL+FormatUTF8(SQLFromWhere(aFormatSQLJoin),
      aParamsSQLJoin,aBoundsSQLJoin);
  T := aClient.ExecuteList(props.props.JoinedFieldsTable,SQL);
  if T=nil then
    exit;
  fFill := TSQLRecordFill.Create;
  fFill.fJoinedFields := True;
  fFill.fTable := T;
................................................................................
constructor TSQLRecord.CreateJoined(aClient: TSQLRest; aID: TID);
begin
  CreateAndFillPrepareJoined(aClient,'%.RowID=?',[RecordProps.SQLTableName],[aID]);
  FillOne;
end;

constructor TSQLRecord.CreateAndFillPrepareMany(aClient: TSQLRest;
  const aFormatSQLJoin: RawUTF8; const aParamsSQLJoin, aBoundsSQLJoin: array of const);
begin
  Create;
  if Length(RecordProps.ManyFields)=0 then
    raise EModelException.CreateUTF8(
      '%.CreateAndFillPrepareMany() with no many-to-many fields',[self]);
  if not FillPrepareMany(aClient,aFormatSQLJoin,aParamsSQLJoin,aBoundsSQLJoin) then
    raise EModelException.CreateUTF8(
      '%.CreateAndFillPrepareMany(): FillPrepareMany() failure',[self]);
end;

function TSQLRecord.FillPrepareMany(aClient: TSQLRest;
  const aFormatSQLJoin: RawUTF8; const aParamsSQLJoin, aBoundsSQLJoin: array of const): boolean;
var aSQLFields, aSQLFrom, aSQLWhere, aSQL: RawUTF8;
    aField: string[3];
    aMany: RawUTF8;
    f, n, i, SQLFieldsCount: Integer;
    Props: TSQLRecordProperties;
    T: TSQLTable;
    SQLFields: array of record
      SQL: string[3];
      Prop: TSQLPropInfo;
      Instance: TSQLRecord;
    end;
    M: TSQLRecordMany;
    D: TSQLRecord;
    J,JBeg: PUTF8Char;
    Objects: array of TSQLRecord;
    ObjectsClass: array of TSQLRecordClass;

  function AddField(aProp: TSQLPropInfo): Boolean;
  begin
    if SQLFieldsCount>=MAX_SQLFIELDS then
      result := false else
................................................................................
    end;
  if Props.fSQLFillPrepareMany<>'' then
    aSQL := Props.fSQLFillPrepareMany else begin
    aSQL := FormatUTF8('select % from % where %',[aSQLFields,aSQLFrom,aSQLWhere]);
    Props.fSQLFillPrepareMany := aSQL;
  end;
  // process aFormatSQLJoin,aParamsSQLJoin and aBoundsSQLJoin parameters
  if aFormatSQLJoin<>'' then begin
    aSQLWhere := '';
    JBeg := pointer(aFormatSQLJoin);
    repeat

      J := JBeg;
      while not (ord(J^) in IsIdentifier) do begin
        case J^ of
        '"':  repeat inc(J) until J^ in [#0,'"'];
        '''': repeat inc(J) until J^ in [#0,''''];
        end;
        if J^=#0 then break;
        inc(J);
      end;
      if J<>JBeg then begin // append ' ',')'..
        SetString(aSQLFrom,PAnsiChar(JBeg),J-JBeg);
        aSQLWhere := aSQLWhere+aSQLFrom;

        JBeg := J;
      end;
      if J^=#0 then break;
      aSQLWhere := aSQLWhere+ProcessField(JBeg);

    until JBeg^=#0;
    aSQL := aSQL+' and ('+FormatUTF8(aSQLWhere,aParamsSQLJoin,aBoundsSQLJoin)+')';
  end;
  // execute SQL statement and retrieve data
  T := aClient.ExecuteList(ObjectsClass,aSQL);
  if (T=nil) or (T.fResults=nil) then
    exit;
  fFill.fTable := T;
  T.OwnerMustFree := true;
................................................................................
begin
  {$ifdef WITHLOG}
  if Level in fLogFamily.Level then
    fLogFamily.SynLog.Log(Level,Text,self);
  {$endif}
end;

procedure TSQLRest.InternalLog(const Format: RawUTF8; 
  const Args: array of const; Level: TSynLogInfo);
begin
  {$ifdef WITHLOG}
  if Level in fLogFamily.Level then
    fLogFamily.SynLog.Log(Level,Format,Args,self);
  {$endif}
end;
................................................................................
begin
  if MultiFieldValue(Table,[FieldName],Res,WhereClause) then
    result := Res[0] else
    result := '';
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass; const FieldName: RawUTF8;
  const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const): RawUTF8;
begin
  result := OneFieldValue(Table,FieldName,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere));
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass;
  const FieldName: RawUTF8; const WhereClauseFmt: RawUTF8;
  const Args, Bounds: array of const): RawUTF8;
begin
  result := OneFieldValue(Table,FieldName,FormatUTF8(WhereClauseFmt,Args,Bounds));
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass;
  const FieldName: RawUTF8; WhereID: TID): RawUTF8;
................................................................................

function TSQLRest.MemberExists(Table: TSQLRecordClass; ID: TID): boolean;
begin
  result := OneFieldValue(Table,'RowID',ID)<>'';
end;

function TSQLRest.OneFieldValue(Table: TSQLRecordClass; const FieldName: RawUTF8;
  const WhereClauseFmt: RawUTF8; const Args, Bounds: array of const;
  out Data: Int64): boolean;
var Res: array[0..0] of RawUTF8;
    err: integer;
begin
  result := false;
  if MultiFieldValue(Table,[FieldName],Res,FormatUTF8(WhereClauseFmt,Args,Bounds)) then
    if Res[0]<>'' then begin
................................................................................
  sql := SQLComputeForSelect(Table,FieldNames,WhereClause);
  if sql='' then
    result := nil else
    result := ExecuteList([Table],sql);
end;

function TSQLRest.MultiFieldValues(Table: TSQLRecordClass; const FieldNames: RawUTF8;
  const WhereClauseFormat: RawUTF8; const BoundsSQLWhere: array of const): TSQLTableJSON;
begin
  result := MultiFieldValues(Table,FieldNames,FormatUTF8(WhereClauseFormat,[],BoundsSQLWhere));
end;

function TSQLRest.MultiFieldValues(Table: TSQLRecordClass;
  const FieldNames: RawUTF8; const WhereClauseFormat: RawUTF8;
  const Args, Bounds: array of const): TSQLTableJSON;
begin
  result := MultiFieldValues(Table,FieldNames,FormatUTF8(WhereClauseFormat,Args,Bounds));
end;

function TSQLRest.MultiFieldValue(Table: TSQLRecordClass;
  const FieldName: array of RawUTF8; var FieldValue: array of RawUTF8;
................................................................................
        result := false;
      end;
    finally
      T.Free;
    end;
end;

function TSQLRest.RetrieveList(Table: TSQLRecordClass; const FormatSQLWhere: RawUTF8;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8): TObjectList;
var T: TSQLTable;
begin
  result := nil;
  if (self=nil) or (Table=nil) then
    exit;
  T := MultiFieldValues(Table,aCustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
    result := TObjectList.Create;
    T.ToObjectList(result,Table);
  finally
    T.Free;
  end;
end;

function TSQLRest.RetrieveListJSON(Table: TSQLRecordClass; const FormatSQLWhere: RawUTF8;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8): RawJSON;
var sql: RawUTF8;
begin
  sql := SQLComputeForSelect(Table,aCustomFieldsCSV,
    FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere));
  if sql='' then
    result := '' else
    result := EngineList(sql);
end;

function TSQLRest.RetrieveListObjArray(var ObjArray; Table: TSQLRecordClass;
  const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
  const aCustomFieldsCSV: RawUTF8): boolean;
var T: TSQLTable;
begin
  result := false;
  if (self=nil) or (Table=nil) then
    exit;
  T := MultiFieldValues(Table,aCustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
    T.Free;
  end;
end;

{$ifndef NOVARIANTS}
function TSQLRest.RetrieveDocVariantArray(Table: TSQLRecordClass;
  const ObjectName: RawUTF8;
  const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
  const CustomFieldsCSV: RawUTF8; FirstRecordID,LastRecordID: PID): variant;
var T: TSQLTable;
    res: variant;
begin
  TVarData(res).VType := varNull;
  if (self<>nil) and (Table<>nil) then begin
    T := MultiFieldValues(Table,CustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
    result := _ObjFast([ObjectName,res]) else
    result := res;
end;

function TSQLRest.RetrieveDocVariantArray(Table: TSQLRecordClass;
  const ObjectName, CustomFieldsCSV: RawUTF8; FirstRecordID,LastRecordID: PID): variant;
begin
  result := RetrieveDocVariantArray(Table,ObjectName,'',[],CustomFieldsCSV,
    FirstRecordID,LastRecordID);
end;

function TSQLRest.RetrieveDocVariant(Table: TSQLRecordClass;
  const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
  const CustomFieldsCSV: RawUTF8): variant;
var T: TSQLTable;
    bits: TSQLFieldBits;
    Rec: TSQLRecord;
    ID: TID;
begin
  SetVariantNull(result);
  if (self<>nil) and (Table<>nil) then begin
    with Table.RecordProps do // optimized primary key direct access
    if Cache.IsCached(Table) and (length(BoundsSQLWhere)=1) and
       VarRecToInt64(BoundsSQLWhere[0],Int64(ID)) and 
       FieldIndexsFromCSV(CustomFieldsCSV,bits) then
      if IsZero(bits) then
        exit else
      if bits-SimpleFieldsBits[soSelect]=[] then

        if IdemPropNameU('RowID=?',FormatSQLWhere) or
           IdemPropNameU('ID=?',FormatSQLWhere) then begin
          Rec := Table.Create(self,ID);
          try
            result := Rec.GetAsDocVariant(True,bits);
          finally
            Rec.Free;
          end;
          exit;
        end;

    T := MultiFieldValues(Table,CustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
    if T<>nil then                   
    try
      T.ToDocVariant(1,result)
    finally
      T.Free;
    end;
  end;
end;
................................................................................
    fCache.Notify(Tableindex,aID,Resp,soSelect);
  end;
  // fill Value from JSON if was correctly retrieved
  Value.FillFrom(Resp);
  result := true;
end;

function TSQLRest.Retrieve(const WhereClauseFmt: RawUTF8; const Args,Bounds: array of const;
  Value: TSQLRecord): boolean;
var where: RawUTF8;
begin
  where := FormatUTF8(WhereClauseFmt,Args,Bounds);
  result := Retrieve(where,Value);
end;

................................................................................
var IDs: TIDDynArray;
begin
  if InternalDeleteNotifyAndGetIDs(Table,SQLWhere,IDs) then
    result := EngineDeleteWhere(Model.GetTableIndexExisting(Table),SQLWhere,IDs) else
    result := false;
end;

function TSQLRest.Delete(Table: TSQLRecordClass; const FormatSQLWhere: RawUTF8;
  const BoundsSQLWhere: array of const): boolean;
begin
  result := Delete(Table,FormatUTF8(FormatSQLWhere,[],BoundsSQLWhere));
end;

function TSQLRest.Update(Value: TSQLRecord; const CustomFields: TSQLFieldBits): boolean;
var JSONValues: RawUTF8;
................................................................................
end;

function TSQLRest.Execute(const aSQL: RawUTF8): boolean;
begin
  result := EngineExecute(aSQL);
end;

function TSQLRest.ExecuteFmt(const SQLFormat: RawUTF8;
  const Args: array of const): boolean;
begin
  result := EngineExecute(FormatUTF8(SQLFormat,Args));
end;

function TSQLRest.ExecuteFmt(const SQLFormat: RawUTF8;
  const Args, Bounds: array of const): boolean;
begin
  result := EngineExecute(FormatUTF8(SQLFormat,Args,Bounds));
end;

function TSQLRest.MainFieldValue(Table: TSQLRecordClass; ID: TID;
   ReturnFirstIfNoUnique: boolean=false): RawUTF8;
................................................................................
  service := fServices.Info(TypeInfo(T));
  if (service=nil) or not service.Get(result) then
    result := Default(T);
end;

function TSQLRest.RetrieveList<T>(const aCustomFieldsCSV: RawUTF8): TObjectList<T>;
begin
  result := RetrieveList<T>('',[],aCustomFieldsCSV);
end;

function TSQLRest.RetrieveList<T>(const FormatSQLWhere: RawUTF8;
  const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8): TObjectList<T>;
var Table: TSQLTable;
begin
  result := nil;
  if self=nil then
    exit;
  Table := MultiFieldValues(TSQLRecordClass(T),aCustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
................................................................................
begin
  result := true;
  for i := 0 to high(FieldNames) do
    if not CreateSQLMultiIndex(Table,[FieldNames[i]],Unique) then
     result := false;
end;

function TSQLRestServer.CreateBackgroundThread(const Format: RawUTF8; const Args: array of const): TSynBackgroundThreadMethod;
begin
  result := TSynBackgroundThreadMethod.Create(nil,FormatUTF8(Format,Args));
  result.OnBeforeExecute := BeginCurrentThread;
  result.OnAfterExecute := EndCurrentThread;
end;

function TSQLRestServer.GetAuthenticationSchemesCount: integer;
................................................................................
             not ContainsUTF8(pointer(SQLWhere),'ORDER BY') then begin
            if SameTextU(SQLDir,'DESC') then
              SQLSort := SQLSort+' DESC'; // allow DESC, default is ASC
            SQLWhere := SQLWhere+' ORDER BY '+SQLSort;
          end;
          SQLWhere := trim(SQLWhere);
          if (SQLResults<>0) and not ContainsUTF8(pointer(SQLWhere),'LIMIT ') then begin
            if (Server.URIPagingParameters.SendTotalRowsCountFmt<>'') then begin
              if SQLWhere=SQLWhereCount then begin
                i := PosEx('ORDER BY ',UpperCase(SQLWhereCount));
                if i>0 then // if ORDER BY already in the SQLWhere clause
                  SetLength(SQLWhereCount,i-1);
              end;
              ResultList := Server.ExecuteList([Table],
                Server.Model.TableProps[TableIndex].SQLFromSelectWhere('Count(*)',SQLWhereCount));
................................................................................
          end;
        end;
        SQL := Server.Model.TableProps[TableIndex].
          SQLFromSelectWhere(SQLSelect,trim(SQLWhere));
        Call.OutBody := Server.InternalListRawUTF8(TableIndex,SQL);
        if Call.OutBody<>'' then begin // got JSON list '[{...}]' ?
          Call.OutStatus := HTML_SUCCESS;  // 200 OK
          if Server.URIPagingParameters.SendTotalRowsCountFmt<>'' then
            if Server.NoAJAXJSON then begin
              P := pointer(Call.OutBody);
              L := length(Call.OutBody);
              P := NotExpandedBufferRowCountPos(P,P+L);
              j := 0;
              if P<>nil then
                j := P-pointer(Call.OutBody)-11 else
................................................................................
procedure TSQLRestServerURIContext.Success(Status: integer);
begin
  if (Status in [HTML_SUCCESS,HTML_CREATED]) or (Status=HTML_NOTMODIFIED) then
    Call.OutStatus := Status else
    Error('',Status);
end;

procedure TSQLRestServerURIContext.Error(const Format: RawUTF8;
  const Args: array of const; Status: integer);
begin
  Error(FormatUTF8(Format,Args),Status);
end;

procedure TSQLRestServerURIContext.Error(E: Exception;
  const Format: RawUTF8; const Args: array of const; Status: integer);
var msg,exc: RawUTF8;
begin
  msg := FormatUTF8(Format,Args);
  if E=nil then
    Error(msg,Status) else begin
    exc := ObjectToJSONDebug(E);
    if msg='' then
................................................................................

procedure TSQLRestClient.RollBack(SessionID: cardinal);
begin
  inherited;
end;

function TSQLRestClient.ListFmt(const Tables: array of TSQLRecordClass; const SQLSelect: RawUTF8;
  const SQLWhereFormat: RawUTF8; const Args: array of const): TSQLTableJSON;
begin
  result := List(Tables,SQLSelect,FormatUTF8(SQLWhereFormat,Args));
end;

function TSQLRestClient.ListFmt(const Tables: array of TSQLRecordClass;
  const SQLSelect: RawUTF8; const SQLWhereFormat: RawUTF8;
  const Args, Bounds: array of const): TSQLTableJSON;
begin
  result := List(Tables,SQLSelect,FormatUTF8(SQLWhereFormat,Args,Bounds));
end;

function TSQLRestClient.RTreeMatch(DataTable: TSQLRecordClass;
  const DataTableBlobFieldName: RawUTF8; RTreeTable: TSQLRecordRTreeClass;
................................................................................

function ObjectToVariantDebug(Value: TObject): variant;
begin
  result := _JsonFast(ObjectToJSONDebug(Value));
end;

function ObjectToVariantDebug(Value: TObject;
  const ContextFormat: RawUTF8; const ContextArgs: array of const;
  const ContextName: RawUTF8): variant;
begin
  result := _JsonFast(ObjectToJSONDebug(Value));
  if ContextFormat<>'' then
    if ContextFormat[1]='{' then
      _ObjAddProps([ContextName,_JsonFastFmt(ContextFormat,[],ContextArgs)],result) else
      _ObjAddProps([ContextName,FormatUTF8(ContextFormat,ContextArgs)],result);
end;

function UrlEncode(const NameValuePairs: array of const): RawUTF8;
// (['select','*','where','ID=12','offset',23,'object',aObject]);
var A, n: PtrInt;
................................................................................
    if PosEx(RawUTF8(':('),aDestWhereSQL,1)>0 then
      // statement is globaly inlined -> cache prepared statement
      SQL := 'SELECT % FROM %,% WHERE %.Source=:(%): AND %.Dest=%.RowID AND %' else
      // statement is not globaly inlined -> no caching of prepared statement
      SQL := 'SELECT % FROM %,% WHERE %.Source=% AND %.Dest=%.RowID AND %';
  result := aClient.ExecuteList([PSQLRecordClass(Self)^,
     TSQLRecordClass(SelfProps.Props.fRecordManyDestProp.ObjectClass)],
    FormatUTF8(SQL,
      [Select, DestProps.Props.SQLTableName,SelfProps.Props.SQLTableName,
       SelfProps.Props.SQLTableName,aSourceID, SelfProps.Props.SQLTableName,
       DestProps.Props.SQLTableName, aDestWhereSQL]));
end;

function TSQLRecordMany.DestGet(aClient: TSQLRest;
  out DestIDs: TIDDynArray): boolean;
................................................................................
     (aClient=nil) then
    Result := '' else begin
    if aAndWhereSQL<>'' then
      if PosEx(RawUTF8(':('),aAndWhereSQL,1)>0 then
        Result := '%:(%): AND %' else // inlined parameters
        Result := '%% AND %' else // no inlined parameters -> not cached
      Result := '%:(%):'; // no additional where clause -> inline ID
    Result := FormatUTF8(result,[FieldName[isDest],aID,aAndWhereSQL]);
  end;
end;

function TSQLRecordMany.SourceGet(aClient: TSQLRest; aDestID: TID;
  out SourceIDs: TIDDynArray): boolean;
var Where: RawUTF8;
begin
................................................................................
  end;

  EInterfaceStub = class(EInterfaceFactoryException)
  public
    constructor Create(Sender: TInterfaceStub; const Method: TServiceMethod;
      const Error: RawUTF8); overload;
    constructor Create(Sender: TInterfaceStub; const Method: TServiceMethod;
      const Format: RawUTF8; const Args: array of const); overload;
  end;


constructor TInterfacedObjectFake.Create(aFactory: TInterfaceFactory;
  aInvoke: TOnFakeInstanceInvoke; aNotifyDestroy: TOnFakeInstanceDestroy);
begin
  inherited Create(aFactory,aInvoke,aNotifyDestroy);
................................................................................
function TInterfacedObjectFake.FakeCall(var aCall: TFakeCallStack): Int64;
{$ifdef CPUARM}
begin
  raise EInterfaceFactoryException.Create('You encountered an ALF! ARM not yet supported');
end;
{$else}
var method: ^TServiceMethod;
procedure RaiseError(const Format: RawUTF8; const Args: array of const);
begin
  raise EInterfaceFactoryException.CreateUTF8('Invalid %.FakeCall() for %.%: %',
    [self,fFactory.fInterfaceTypeInfo^.Name,method^.URI,FormatUTF8(Format,Args)]);
end;
var resultType: TServiceMethodValueType; // type of value stored into result
procedure InternalProcess;
var Params: TJSONSerializer;
................................................................................
    PP: ^PPTypeInfo absolute P;
    Ancestor: PTypeInfo;
    Kind: TMethodKind;
    f: TParamFlags;
    m,a: integer;
    n: cardinal;
    aURI: RawUTF8;
procedure RaiseError(const Format: RawUTF8; const Args: array of const);
begin
  raise EInterfaceFactoryException.CreateUTF8(
    '%.AddMethodsFromTypeInfo: %.% %',
    [self,fInterfaceTypeInfo^.Name,aURI,FormatUTF8(Format,Args)]);
end;
begin
  // handle interface inheritance via recursive calls
................................................................................
  const Method: TServiceMethod; const Error: RawUTF8);
begin
  inherited CreateUTF8('Error in % for %.% - %',
    [Sender,Sender.fInterface.fInterfaceTypeInfo^.Name,Method.URI,Error]);
end;

constructor EInterfaceStub.Create(Sender: TInterfaceStub;
  const Method: TServiceMethod; const Format: RawUTF8; const Args: array of const);
begin
  Create(Sender,Method,FormatUTF8(Format,Args));
end;

function TInterfaceStubLog.Results: RawUTF8;
begin
  if CustomResults='' then
................................................................................
begin
  fSender := aSender;
  fMethod := aMethod;
  fParams := aParams;
  fEventParams := aEventParams;
end;

procedure TOnInterfaceStubExecuteParamsAbstract.Error(
  const Format: RawUTF8; const Args: array of const);
begin
  Error(FormatUTF8(Format,Args));
end;

procedure TOnInterfaceStubExecuteParamsAbstract.Error(const aErrorMessage: RawUTF8);
begin
  fFailed := true;
................................................................................
  with TInterfacedObjectFake.Create(fInterface,Invoke,InstanceDestroyed) do begin
    pointer(aStubbedInterface) := @fVTable;
    _AddRef;
  end;
end;

function TInterfaceStub.InternalCheck(aValid,aExpectationFailed: boolean;
  const aErrorMsgFmt: RawUTF8; const aErrorMsgArgs: array of const): boolean;
begin
  result := aValid;
  if aExpectationFailed and not aValid then
    raise EInterfaceStub.CreateUTF8('%.InternalCheck(%) failed: %',
      [self,fInterface.fInterfaceTypeInfo^.Name,FormatUTF8(aErrorMsgFmt,aErrorMsgArgs)]);
end;

................................................................................
constructor TInterfaceMock.Create(const aGUID: TGUID; aTestCase: TSynTestCase);
begin
  inherited Create(aGUID);
  fTestCase := aTestCase;
end;

function TInterfaceMock.InternalCheck(aValid,aExpectationFailed: boolean;
  const aErrorMsgFmt: RawUTF8; const aErrorMsgArgs: array of const): boolean; 
begin
  if fTestCase=nil then
    result := inherited InternalCheck(aValid,aExpectationFailed,aErrorMsgFmt,aErrorMsgArgs) else begin
    if aValid xor (imoMockFailsWillPassTestCase in Options) then
      fTestCase.Check(true) else
      fTestCase.Check(false,UTF8ToString(FormatUTF8(aErrorMsgFmt,aErrorMsgArgs)));
    result := true; // do not raise any exception at this stage for TInterfaceMock

Changes to SQLite3/mORMotService.pas.

512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
begin
  if (self=nil) or (FSCHandle=0) then
    result := ssErrorRetrievingState else
  if FHandle=0 then
    result := ssNotInstalled else
    result := CurrentStateToServiceState(Status.dwCurrentState);
  {$ifndef NOMORMOTKERNEL}
  SQLite3Log.Add.Log(sllTrace,pointer(FName),TypeInfo(TServiceState),result);
  {$endif}
end;

function TServiceController.GetStatus: TServiceStatus;
begin
  FillChar(FStatus, Sizeof(FStatus), 0);
  QueryServiceStatus(FHandle, FStatus);






|







512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
begin
  if (self=nil) or (FSCHandle=0) then
    result := ssErrorRetrievingState else
  if FHandle=0 then
    result := ssNotInstalled else
    result := CurrentStateToServiceState(Status.dwCurrentState);
  {$ifndef NOMORMOTKERNEL}
  SQLite3Log.Add.Log(sllTrace,FName,TypeInfo(TServiceState),result);
  {$endif}
end;

function TServiceController.GetStatus: TServiceStatus;
begin
  FillChar(FStatus, Sizeof(FStatus), 0);
  QueryServiceStatus(FHandle, FStatus);

Changes to SynCommons.pas.

370
371
372
373
374
375
376



377
378
379
380
381
382
383
....
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822





1823
1824
1825
1826
1827
1828
1829
....
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
....
5471
5472
5473
5474
5475
5476
5477
5478
5479
5480
5481
5482
5483
5484
5485
....
5573
5574
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
....
5648
5649
5650
5651
5652
5653
5654





5655
5656
5657
5658
5659
5660
5661
....
5681
5682
5683
5684
5685
5686
5687
5688
5689
5690
5691
5692
5693
5694
5695
....
6789
6790
6791
6792
6793
6794
6795
6796
6797
6798
6799
6800
6801
6802
6803
....
7431
7432
7433
7434
7435
7436
7437
7438
7439
7440
7441
7442
7443
7444
7445
....
7888
7889
7890
7891
7892
7893
7894
7895
7896
7897
7898
7899
7900
7901
7902
7903
7904
7905
7906
7907
.....
10743
10744
10745
10746
10747
10748
10749
10750
10751
10752
10753
10754
10755
10756
10757
.....
10794
10795
10796
10797
10798
10799
10800
10801
10802
10803
10804
10805
10806
10807
10808
.....
13183
13184
13185
13186
13187
13188
13189













13190
13191
13192
13193
13194
13195
13196
.....
15544
15545
15546
15547
15548
15549
15550
15551
15552
15553
15554
15555
15556
15557
15558
.....
15577
15578
15579
15580
15581
15582
15583
15584
15585
15586
15587
15588
15589
15590
15591
.....
15597
15598
15599
15600
15601
15602
15603
15604
15605
15606
15607
15608
15609
15610
15611
.....
15816
15817
15818
15819
15820
15821
15822
15823
15824
15825
15826
15827
15828
15829
15830
15831
15832
15833
15834
15835
15836
15837
15838
15839
15840
15841
15842
15843
15844
15845
15846
15847
15848
15849
15850
15851
15852
15853
15854
15855
15856
15857
15858

15859
15860
15861
15862
15863

15864
15865
15866
15867
15868
15869
15870
15871
15872
15873
15874
15875
15876
15877
15878
15879
15880
15881
15882
15883
15884
15885
15886
15887
15888
15889
15890
15891
15892
15893
15894
15895
15896
15897
15898
15899
15900
15901
15902
15903
15904




15905
15906
15907
15908
15909
15910
15911

15912
15913
15914
15915
15916
15917
15918
15919
15920
15921
15922
15923
15924
15925
15926
15927
15928
15929
15930
15931
15932
15933
15934
15935
.....
15955
15956
15957
15958
15959
15960
15961
15962
15963
15964
15965
15966
15967
15968
15969
15970
15971
15972
15973
15974
15975
15976
15977
15978
15979
15980
15981
15982
15983
15984
15985
15986
15987
15988
15989
15990
15991
15992
15993
.....
18267
18268
18269
18270
18271
18272
18273
18274
18275
18276
18277
18278
18279
18280
18281
18282
18283
18284
18285
18286
18287
18288
18289
18290
18291
18292
18293
18294
18295
18296
18297
18298
18299
18300
18301
18302
18303
18304
18305
18306
18307
18308
18309
18310
18311
18312
18313
18314
18315
18316
18317
18318
18319
18320
18321
18322
18323
18324
18325
18326
18327
18328
18329
18330
18331
18332
18333
18334
18335
18336
18337
18338
18339
18340
18341
18342
18343
18344
.....
18454
18455
18456
18457
18458
18459
18460
18461
18462
18463
18464
18465
18466
18467
18468
.....
18474
18475
18476
18477
18478
18479
18480
18481
18482
18483
18484
18485
18486
18487
18488
18489
18490
18491
18492
18493
18494
18495
18496
18497
18498
.....
20728
20729
20730
20731
20732
20733
20734
20735
20736
20737
20738
20739
20740
20741
20742
20743
20744
20745
20746
20747
20748
20749
20750
20751
20752
20753
20754
.....
30142
30143
30144
30145
30146
30147
30148
30149
30150
30151
30152
30153
30154
30155
30156
30157
30158
30159
30160
30161
30162
.....
32181
32182
32183
32184
32185
32186
32187
32188
32189
32190
32191
32192
32193
32194
32195
32196
32197
32198
32199
32200
32201
32202
32203
32204
32205
.....
33783
33784
33785
33786
33787
33788
33789
33790
33791
33792

33793
33794
33795
33796




33797

33798
33799
33800
33801
33802
33803
33804
33805
33806
33807
33808
33809
33810
33811
33812
33813
33814
33815
33816
33817
33818
33819
33820
33821
33822
33823
33824
33825
33826
33827
33828
33829
33830
33831
33832
33833
33834
33835
33836
33837
33838
33839
33840
33841
33842
33843
33844
33845
33846
33847
33848
33849
33850
33851
33852
33853
33854
33855
33856
33857
33858
33859
33860
33861
33862
33863
33864
33865
33866
33867
33868
33869
33870
33871
33872
33873
33874
33875
33876
33877
33878
.....
34504
34505
34506
34507
34508
34509
34510
34511




























34512











34513
34514
34515
34516
34517
34518
34519
34520
.....
35069
35070
35071
35072
35073
35074
35075
35076
35077
35078
35079
35080
35081
35082
35083
.....
37580
37581
37582
37583
37584
37585
37586
37587
37588
37589
37590
37591
37592
37593
37594
37595
37596
37597
37598
37599
37600
37601
37602
37603
37604
37605
37606
37607
37608
37609
37610
.....
42020
42021
42022
42023
42024
42025
42026
42027
42028
42029
42030
42031
42032
42033
42034
.....
42557
42558
42559
42560
42561
42562
42563
42564
42565
42566
42567
42568
42569

42570
42571
42572
42573
42574
42575
42576
  - BREAKING CHANGE of TTextWriter.WriteObject() method: serialization is now
    defined with a new TTextWriterWriteObjectOptions set
  - BREAKING CHANGE rename of Iso8601 low-level structure as TTimeLogBits, to use
    explicitly the TTimeLog type and name for all Int64 bit-oriented functions -
    now "Iso8601" naming will be only for standard ISO-8601 text, not Int64 value
  - BREAKING CHANGE: TTextWriter.Add(Format) won't handle the alternate $ % tags
    any more, unless you define the OLDTEXTWRITERFORMAT conditional



  - Delphi XE4/XE5/XE6/XE7/XE8 compatibility (Win32/Win64 target platform only
    for the SynCommons and mORMot* units, but see SynCrossPlatform* units for
    clients on all other targets, including OSX and the NextGen compilers)
  - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
  - now all variants created within our units will create string instances of
    kind varString and type RawUTF8 - prior to Delphi 2009, ensure you call
    UTF8ToString(aVariant) if you want to use the value with the VCL
................................................................................
/// fast Format() function replacement, optimized for RawUTF8
// - only supported token is %, which will be inlined in the resulting string
// according to each Args[] supplied item
// - resulting string has no length limit and uses fast concatenation
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
function FormatUTF8(Format: PUTF8Char; const Args: array of const): RawUTF8; overload;

/// fast Format() function replacement, handling % and ? parameters
// - will include Args[] for every % in Format
// - will inline Params[] for every ? in Format, handling special "inlined"
// parameters, as exected by mORMot.pas unit, i.e. :(1234): for numerical
// values, and :('quoted '' string'): for textual values
// - if optional JSONFormat parameter is TRUE, ? parameters will be written
// as JSON quoted strings, without :(...): tokens, e.g. "quoted "" string"
// - resulting string has no length limit and uses fast concatenation
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
function FormatUTF8(Format: PUTF8Char; const Args, Params: array of const;
  JSONFormat: boolean=false): RawUTF8; overload;

/// convert an open array (const Args: array of const) argument to an UTF-8
// encoded text
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
procedure VarRecToUTF8(const V: TVarRec; var result: RawUTF8;
  wasString: PBoolean=nil);






/// convert an open array (const Args: array of const) argument to a value
// encoded as with :(..:) inlined parameters in FormatUTF8(Format,Args,Params)
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
procedure VarRecToInlineValue(const V: TVarRec; var result: RawUTF8);
................................................................................
    // - expected element layout is to have a RawUTF8 field at first position
    // - the aName is searched (using hashing) to be unique, and if not the case,
    // an ESynException.CreateUTF8() is raised with the supplied arguments 
    // - use internaly FindHashedForAdding method
    // - this version will set the field content with the unique value
    // - returns a pointer to the newly added element (to set other fields)
    function AddUniqueName(const aName: RawUTF8;
      ExceptionMsg: PUTF8Char; const ExceptionArgs: array of const): pointer;
    /// search for a given element name, make it unique, and add it to the array
    // - expected element layout is to have a RawUTF8 field at first position
    // - the aName is searched (using hashing) to be unique, and if not the case,
    // some suffix is added to make it unique
    // - use internaly FindHashedForAdding method
    // - this version will set the field content with the unique value
    // - returns a pointer to the newly added element (to set other fields)
................................................................................
    // - CR = #13 indicates CR+LF chars
    // - � = #167 indicates to trim last comma
    // - | = #124 will write the next char e.g. Add('%|$',[10]) will write '10$'
    // - since some of this characters above are > #127, they are not UTF-8
    // ready, so we expect the input format to be WinAnsi, i.e. mostly English
    // text (with chars < #128) with some values to be inserted inside
    {$endif}
    procedure Add(Format: PWinAnsiChar; const Values: array of const;
      Escape: TTextWriterKind=twNone); overload;
{$endif DELPHI5OROLDER}
    /// append some values at once
    // - text values (e.g. RawUTF8) will be escaped as JSON
    procedure Add(const Values: array of const); overload;
    /// append CR+LF (#13#10) chars
    // - this method won't call EchoAdd() registered events - use AddEndOfLine()
................................................................................
    // versions of Delphi will retrieve the code page from string
    // - if CodePage is defined to a >= 0 value, the encoding will take place
    procedure AddAnyAnsiString(const s: RawByteString; Escape: TTextWriterKind;
      CodePage: Integer=-1);
    /// append some chars to the buffer
    // - if Len is 0, Len is calculated from zero-ended char
    // - don't escapes chars according to the JSON RFC
    procedure AddNoJSONEscape(P: Pointer; Len: integer=0);
    /// append some chars, quoting all " chars
    // - same algorithm than AddString(QuotedStr()) - without memory allocation
    // - this function implements what is specified in the official SQLite3
    // documentation: "A string constant is formed by enclosing the string in single
    // quotes ('). A single quote within the string can be encoded by putting two
    // single quotes in a row - as in Pascal."
    procedure AddQuotedStr(Text: PUTF8Char; Quote: AnsiChar; TextLen: integer=0); 
................................................................................
    // - escapes chars according to the JSON RFC
    procedure AddJSONEscapeW(P: PWord; Len: PtrInt=0);
    /// append an open array constant value to the buffer
    // - "" will be added if necessary
    // - escapes chars according to the JSON RFC
    // - very fast (avoid most temporary storage)
    procedure AddJSONEscape(const V: TVarRec); overload;





    /// encode the supplied data as an UTF-8 valid JSON object content
    // - data must be supplied two by two, as Name,Value pairs, e.g.
    // ! aWriter.AddJSONEscape(['name','John','year',1972]);
    // will append to the buffer:
    // ! '{"name":"John","year":1972}'
    // - or you can specify nested arrays or objects with '['..']' or '{'..'}':
    // ! aWriter.AddJSONEscape(['doc','{','name','John','ab','[','a','b']','}','id',123]);
................................................................................
    // ! new Date()   ObjectId()   MinKey   MaxKey  /<jRegex>/<jOptions>
    // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
    // !  aWriter.AddJSON('{name:?,field:/%/i}',['acme.*corp'],['John']))
    // ! // will write
    // ! '{"name":"John","field":{"$regex":"acme.*corp","$options":"i"}}'
    // - will call internally _JSONFastFmt() to create a temporary TDocVariant
    // with all its features - so is slightly slower than other AddJSON* methods
    procedure AddJSON(Format: PUTF8Char; const Args,Params: array of const);
{$endif}
    /// append a dynamic array content as UTF-8 encoded JSON array
    // - expect a dynamic array TDynArray wrapper as incoming parameter
    // - TIntegerDynArray, TInt64DynArray, TCardinalDynArray, TDoubleDynArray,
    // TCurrencyDynArray, TWordDynArray and TByteDynArray will be written as
    // numerical JSON values
    // - TRawUTF8DynArray, TWinAnsiDynArray, TRawByteStringDynArray,
................................................................................
// ! new Date()   ObjectId()   MinKey   MaxKey  /<jRegex>/<jOptions>
// see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
// !  aJSON := JSONEncode('{name:?,field:/%/i}',['acme.*corp'],['John']))
// ! // will return
// ! '{"name":"John","field":{"$regex":"acme.*corp","$options":"i"}}'
// - will call internally _JSONFastFmt() to create a temporary TDocVariant with
// all its features - so is slightly slower than other JSONEncode* functions
function JSONEncode(Format: PUTF8Char; const Args,Params: array of const): RawUTF8; overload;
{$endif}

/// encode the supplied RawUTF8 array data as an UTF-8 valid JSON array content
function JSONEncodeArray(const Values: array of RawUTF8): RawUTF8; overload;

/// encode the supplied integer array data as a valid JSON array
function JSONEncodeArray(const Values: array of integer): RawUTF8; overload;
................................................................................
  ESynException = class(Exception)
  public
    /// constructor which will use FormatUTF8() instead of Format()
    // - expect % as delimitor, so is less error prone than %s %d %g
    // - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments,
    // appending class name for any class or object, the hexa value for a
    // pointer, or the JSON representation of the supplied variant
    constructor CreateUTF8(Format: PUTF8Char; const Args: array of const);
    {$ifndef NOEXCEPTIONINTERCEPT}
    /// can be used to customize how the exception is logged
    // - this default implementation will call the DefaultSynLogExceptionToStr()
    // function or the TSynLogExceptionToStrCustom global callback, if defined
    // - override this method to provide a custom logging content
    // - should return TRUE if Context.EAddr and Stack trace is not to be
    // written (i.e. as for any TSynLogExceptionToStr callback)
................................................................................
function SearchFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer;

/// convert an array of field indexes into a TSQLFieldBits set of bits
function FieldIndexToBits(const Index: TSQLFieldIndexDynArray): TSQLFieldBits; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// name the current thread so that it would be easily identified in the IDE debugger
procedure SetCurrentThreadName(Format: PUTF8Char; const Args: array of const);

/// name a thread so that it would be easily identified in the IDE debugger
// - you can force this function to do nothing by setting the NOSETTHREADNAME
// conditional, if you have issues with this feature when debugging your app
procedure SetThreadName(ThreadID: cardinal; Format: PUTF8Char; const Args: array of const);

type
  TSynBackgroundThreadAbstract = class;
  TSynBackgroundThreadEvent = class;

  /// idle method called by TSynBackgroundThreadAbstract in the caller thread
  // during remote blocking process in a background thread
................................................................................
// ! assert(u='{"type":{"$in":["food","snack"]}}');
// ! u := VariantSaveMongoJSON(aVariant,modMongoShell);
// ! assert(u='{type:{$in:["food","snack"]}}');
// - by default, every internal value will be copied, so access of nested
// properties can be slow - if you expect the data to be read-only or not
// propagated into another place, add dvoValueCopiedByReference in Options
// will increase the process speed a lot, or use _JsonFast()
function _JsonFmt(Format: PUTF8Char; const Args,Params: array of const;
  Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): variant;

/// initialize a variant instance to store some document-based content
// from a supplied (extended) JSON content
// - this global function is an alias to TDocVariant.NewJSON(), and
// will return TRUE if JSON content was correctly converted into a variant
// - in addition to the JSON RFC specification strict mode, this method will
................................................................................
// from a supplied (extended) JSON content, with parameters formating
// - this global function is an handy alias e.g. to:
// ! aVariant := _JSONFmt('{%:{$in:[?,?]}}',['type'],['food','snack'],JSON_OPTIONS[true]);
// - so all created objects and arrays will be handled by reference, for best
// speed - but you should better write on the resulting variant tree with caution
// - in addition to the JSON RFC specification strict mode, this method will
// handle some BSON-like extensions, e.g. unquoted field names or ObjectID():
function _JsonFastFmt(Format: PUTF8Char; const Args,Params: array of const): variant;

/// ensure a document-based variant instance will have only per-value nested
// objects or array documents
// - is just a wrapper around:
// ! TDocVariantData(DocVariant).InitCopy(DocVariant,JSON_OPTIONS[false])
// - you can use this function to ensure that all internal properties of this
// variant will be copied per-value whatever options the nested objects or
................................................................................
begin
  case V.VType of
    vtChar:     result := ord(V.VChar);
    vtWideChar: result := ord(V.VWideChar);
    else        result := 0;
  end;
end;














procedure VarRecToUTF8(const V: TVarRec; var result: RawUTF8; wasString: PBoolean=nil);
var isString: boolean;
begin
  isString := not (V.VType in [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended]);
  with V do
  case V.VType of
................................................................................
  DigitChars = ['-','+','0'..'9'];
  DigitFirstChars = ['-','1'..'9']; // 0/- excluded by JSON!
  DigitFloatChars = ['-','+','0'..'9','.','E','e'];


function SQLParamContent(P: PUTF8Char; out ParamType: TSQLParamType; out ParamValue: RawUTF8;
  out wasNull: boolean): PUTF8Char;
var PDeb: PAnsiChar;
    L: integer;
    c: cardinal;
begin
  ParamType := sptUnknown;
  wasNull := false;
  result := nil;
  if P=nil then
................................................................................
        ParamValue := copy(ParamValue,4,L); // return ISO-8601 text
        ParamType := sptDateTime;           // identified as Date/Time
      end;
    end;
  end;
  '-','+','0'..'9': begin // allow 0 or + in SQL
    // check if P^ is a true numerical value
    PDeb := pointer(P);
    ParamType := sptInteger;
    repeat inc(P) until not (P^ in ['0'..'9']); // check digits
    if P^='.' then begin
      inc(P);
      if P^ in ['0'..'9'] then begin
        ParamType := sptFloat;
        repeat inc(P) until not (P^ in ['0'..'9']); // check fractional digits
................................................................................
    if byte(P^) and $DF=ord('E') then begin
      ParamType := sptFloat;
      inc(P);
      if P^='+' then inc(P) else
      if P^='-' then inc(P);
      while P^ in ['0'..'9'] do inc(P);
    end;
    SetRawUTF8(ParamValue,PDeb,P-PDeb);
  end;
  'n':
  if PInteger(P)^=NULL_LOW then begin
    inc(P,4);
    wasNull := true;
  end else
    exit; // invalid content (only :(null): expected)
................................................................................
var tmp: ShortString;
begin
  if Value=0 then
    result := '0' else
    SetString(result,PAnsiChar(@tmp[1]),ExtendedToString(tmp,Value,DOUBLE_PRECISION));
end;

function FormatUTF8(Format: PUTF8Char; const Args: array of const): RawUTF8;
// only supported token is %, with any const arguments
var i, blocksN, L, argN: PtrInt;
    blocks: array of record
      Text: PUTF8Char;
      Len: integer;
    end;
    Arg: TRawUTF8DynArray;
    PDeb: PUTF8Char;
procedure Add(aText: PUTF8Char; aLen: Integer);
begin
  if aLen>0 then begin
    inc(L,aLen);
    assert(blocksN<length(blocks));
    with blocks[blocksN] do begin // add inbetween text
      Text := aText;
      Len := aLen;
    end;
    inc(blocksN);
  end;
end;
begin
  if (Format=nil) or (high(Args)<0) then begin
    result := Format; // no formatting to process
    exit;
  end;
  if PWord(Format)^=ord('%') then begin
    VarRecToUTF8(Args[0],result); // optimize raw conversion
    exit;
  end;
  result := '';
  SetLength(Arg,length(Args));
  SetLength(blocks,length(Args)*2+1);
  blocksN := 0;
  argN := 0;
  L := 0;

  while Format^<>#0 do begin
    if Format^<>'%' then begin
      PDeb := Format;
      while (Format^<>'%') and (Format^<>#0) do inc(Format);
      Add(PDeb,Format-PDeb);

    end;
    if Format^=#0 then break;
    inc(Format); // jump '%'
    if argN<=high(Args) then begin
      VarRecToUTF8(Args[argN],arg[argN]);
      Add(pointer(arg[argN]),length(arg[argN]));
      Inc(argN);
    end else
    if Format^<>#0 then begin // no more available Args -> add all remaining text
      Add(Format,StrLen(Format));
      break;
    end;
  end;
  if L=0 then
    exit;
  SetLength(result,L);
  Format := pointer(result);
  for i := 0 to blocksN-1 do
  with blocks[i] do begin
    move(Text^,Format^,Len);
    inc(Format,Len);
  end;
end;

function FormatUTF8(Format: PUTF8Char; const Args, Params: array of const; JSONFormat: boolean): RawUTF8; overload;
// support both % and ? tokens
var i, tmpN, L, A, P, len: PtrInt;
    isParam: AnsiChar;
    tmp: TRawUTF8DynArray; 
    inlin: set of 0..255; 
    PDeb: PUTF8Char;
    wasString: Boolean;
const QUOTECHAR: array[boolean] of AnsiChar = ('''','"');
      NOTTOQUOTE: array[boolean] of set of 0..31 = (
        [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended],
        [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended,vtVariant]);
label Txt;
begin
  if (Format='') or ((high(Args)<0)and(high(Params)<0)) then begin
    result := Format; // no formatting to process
    exit;




  end;
  result := '';
  tmpN := 0;
  FillChar(inlin,SizeOf(inlin),0);
  L := 0;
  A := 0;
  P := 0;

  while Format^<>#0 do begin
    if Format^<>'%' then begin
      PDeb := Format;
      while not (Format^ in [#0,'%','?']) do inc(Format);
Txt:  len := Format-PDeb;
      if len>0 then begin
        inc(L,len);
        if tmpN=length(tmp) then
          SetLength(tmp,tmpN+8);
        SetString(tmp[tmpN],PDeb,len); // add inbetween text
        inc(tmpN);
      end;
    end;
    if Format^=#0 then
      break;
    isParam := Format^;
    inc(Format); // jump '%' or '?'
    if (isParam='%') and (A<=high(Args)) then begin // handle % substitution
      if tmpN=length(tmp) then
        SetLength(tmp,tmpN+8);
      VarRecToUTF8(Args[A],tmp[tmpN]);
      inc(A);
      if tmp[tmpN]<>'' then begin
        inc(L,length(tmp[tmpN]));
................................................................................
          include(inlin,tmpN);
        end;
      end;
      inc(P);
      inc(L,length(tmp[tmpN]));
      inc(tmpN);
    end else
    if Format^<>#0 then begin // no more available Args -> add all remaining text
      PDeb := Format;
      repeat inc(Format) until (Format^=#0);
      goto Txt;
    end;
  end;
  if L=0 then
    exit;
  if (not JSONFormat) and (tmpN>SizeOf(inlin)shl 3) then
    raise ESynException.CreateUTF8(
      'Too many parameters for FormatUTF8(): %>%',[tmpN,SizeOf(inlin)shl 3]);
  SetLength(result,L);
  Format := pointer(result);
  for i := 0 to tmpN-1 do
  if tmp[i]<>'' then begin
    if i in inlin then begin
      PWord(Format)^ := ord(':')+ord('(')shl 8;
      inc(Format,2);
    end;
    L := PInteger(PtrInt(tmp[i])-sizeof(integer))^;
    move(pointer(tmp[i])^,Format^,L);
    inc(Format,L);
    if i in inlin then begin
      PWord(Format)^ := ord(')')+ord(':')shl 8;
      inc(Format,2);
    end;
  end;
end;

function RawByteStringArrayConcat(const Values: array of RawByteString): RawByteString;
var i, L: integer;
    P: PAnsiChar;
................................................................................
@z: pop edx       // ignore source var, result := false
end;
{$endif}

/// find the Value of UpperName in P, till end of current section
// - expect UpperName as 'NAME='
function FindIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8;
var PDeb: PUTF8Char;
    L: integer;
begin
  while (P<>nil) and (P^<>'[') do begin
    PDeb := GetNextLineBegin(P,P); // since PDeb=P, we have PDeb<>nil
    if PDeb^=' ' then repeat inc(PDeb) until PDeb^<>' ';   // trim left ' '
    if IdemPChar(PDeb,UpperName) then begin
      inc(PDeb,StrLen(PUTF8Char(UpperName)));
      L := 0; while PDeb[L]>=' ' do inc(L); // get line length
      SetString(result,PDeb,L);
      exit;
    end;
  end;
  result := '';
end;

function ExistsIniName(P: PUTF8Char; UpperName: PAnsiChar): boolean;
var PDeb: PUTF8Char;
begin
  result := true;
  while (P<>nil) and (P^<>'[') do begin
    PDeb := GetNextLineBegin(P,P); // since PDeb=P, we have PDeb<>nil
    if PDeb^=' ' then repeat inc(PDeb) until PDeb^<>' ';   // trim left ' '
    if IdemPChar(PDeb,UpperName) then
      exit;
  end;
  result := false;
end;

function ExistsIniNameValue(P: PUTF8Char; const UpperName: RawUTF8;
  const UpperValues: array of RawUTF8): boolean;
var PDeb: PUTF8Char;
    i: integer;
begin
  result := true;
  if high(UpperValues)>=0 then
    while (P<>nil) and (P^<>'[') do begin
      PDeb := GetNextLineBegin(P,P); // since PDeb=P, we have PDeb<>nil
      if PDeb^=' ' then repeat inc(PDeb) until PDeb^<>' ';   // trim left ' '
      if IdemPChar(PDeb,pointer(UpperName)) then begin
        inc(PDeb,length(UpperName));
        for i := 0 to high(UpperValues) do
          if IdemPChar(PDeb,pointer(UpperValues[i])) then
            exit; // found one value
        break;
      end;
    end;
  result := false;
end;

function FindWinAnsiIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8;
begin
  result := WinAnsiToUtf8(RawByteString(FindIniNameValue(P,UpperName)));
end;

function GetSectionContent(SectionFirstLine: PUTF8Char): RawUTF8;
var PDeb: PUTF8Char;
begin
  PDeb := SectionFirstLine;
  while (SectionFirstLine<>nil) and (SectionFirstLine^<>'[') do
    GetNextLineBegin(SectionFirstLine,SectionFirstLine);
  if SectionFirstLine=nil then
    result := PDeb else
    SetString(result,PDeb,SectionFirstLine-PDeb);
end;

function GetSectionContent(const Content, SectionName: RawUTF8): RawUTF8; overload;
var P: PUTF8Char;
    UpperSection: array[byte] of AnsiChar;
begin
  P := pointer(Content);
................................................................................
    result := '' else
    result := FindIniEntry(Content,Section,Name);
end;

procedure UpdateIniEntry(var Content: RawUTF8; const Section,Name,Value: RawUTF8);
const CRLF = #13#10;
var P: PUTF8Char;
    PDeb: PUTF8Char;
    SectionFound: boolean;
    i, UpperNameLength: PtrInt;
    V: RawUTF8;
    UpperSection, UpperName: array[byte] of AnsiChar;
    // possible GPF if length(Section/Name)>255, but should be short const in code
label Sec;
begin
................................................................................
  if Section='' then
    goto Sec; // find the Name= entry before any [Section]
  SectionFound := false;
  PWord(UpperCopy255(UpperSection,Section))^ := ord(']');
  if FindSectionFirstLine(P,UpperSection) then begin 
Sec:SectionFound := true;
    while (P<>nil) and (P^<>'[') do begin
      PDeb := GetNextLineBegin(P,P); // since PDeb=P, we have PDeb<>nil
      while PDeb^=' ' do inc(PDeb);   // trim left ' '
      if IdemPChar(PDeb,UpperName) then begin
        // update Name=Value entry
        inc(PDeb,UpperNameLength);
        i := (PDeb-pointer(Content))+1;
        if (i=length(Value)) and CompareMem(PDeb,pointer(Value),i) then
          exit; // new Value is identical to the old one -> no change
        if P=nil then // avoid last line (P-PDeb) calculation error
          SetLength(Content,i-1) else
          delete(Content,i,P-PDeb); // delete old Value
        insert(V,Content,i); // set new value
        exit;
      end;
    end;
    // we reached next [Section] without having found Name=
   end;
  // 2. section or Name= entry not found: add Name=Value
................................................................................
  if source^=#10 then inc(source);
  if source^=#0 then
    next := nil else
    next := source;
end;

function FindIniNameValueW(P: PWideChar; UpperName: PUTF8Char): string;
var PDeb: PWideChar;
    L: PtrInt;
begin
  while (P<>nil) and (P^<>'[') do begin
    PDeb := P;
    while not (cardinal(P^) in [0,10,13]) do inc(P);
    while cardinal(P^) in [10,13] do inc(P);
    if P^=#0 then P := nil;
    if PDeb^=' ' then repeat inc(PDeb) until PDeb^<>' ';   // trim left ' '
    if IdemPCharW(PDeb,UpperName) then begin
      inc(PDeb,StrLen(UpperName));
      L := 0; while PDeb[L]>=' ' do inc(L); // get line length
      SetString(result,PDeb,L);
      exit;
    end;
  end;
  result := '';
end;

function FindIniEntryW(const Content: string; const Section, Name: RawUTF8): string;
................................................................................
end;

function _JsonFast(const JSON: RawUTF8): variant;
begin
  _Json(JSON,result,JSON_OPTIONS[true]);
end;

function _JsonFmt(Format: PUTF8Char; const Args,Params: array of const;
  Options: TDocVariantOptions): variant;
begin
  _Json(FormatUTF8(Format,Args,Params,true),result,Options);
end;

function _JsonFastFmt(Format: PUTF8Char; const Args,Params: array of const): variant;
begin
  _Json(FormatUTF8(Format,Args,Params,true),result,JSON_OPTIONS[true]);
end;

function _Json(const JSON: RawUTF8; var Value: variant;
  Options: TDocVariantOptions): boolean;
begin
................................................................................
  end;
  assert(ndx=Count-1);
  result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
  PRawUTF8(result)^ := aName; // store unique name at 1st elem position
end;

function TDynArrayHashed.AddUniqueName(const aName: RawUTF8;
  ExceptionMsg: PUTF8Char; const ExceptionArgs: array of const): pointer;
var ndx: integer;
    added: boolean;
begin
  ndx := FindHashedForAdding(aName,added);
  if added then begin
    assert(ndx=Count-1);
    result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
    PRawUTF8(result)^ := aName; // store unique name at 1st elem position
  end else
    if ExceptionMsg=nil then
      raise ESynException.CreateUTF8('Duplicated "%" name',[aName]) else
      raise ESynException.CreateUTF8(ExceptionMsg,ExceptionArgs);
end;

function TDynArrayHashed.FindHashedAndFill(var Elem): integer;
var P: PAnsiChar;
begin
................................................................................
    end;
    end;
  CancelLastComma;
  Add(']');
end;

{$ifndef DELPHI5OROLDER}
procedure TTextWriter.Add(Format: PWinAnsiChar; const Values: array of const;
  Escape: TTextWriterKind=twNone);
var ValuesIndex: integer;

label write;
begin // we put const char > #127 as #??? -> asiatic MBCS codepage OK
  if Format=nil then
    exit;




  ValuesIndex := 0;

  repeat
    repeat
      case ord(Format^) of
      0: exit;
      13: AddCR;
      ord('%'): break;
      {$ifdef OLDTEXTWRITERFORMAT}
      164: AddCR; // � -> add CR,LF
      167: if B^=',' then dec(B); // �
      ord('|'): begin
        inc(Format); // |% -> %
        goto write;
      end;
      ord('$'),163,181: // $,�,�
        break; // process command value
      {$endif}
      else begin
write:  if B>=BEnd then
          Flush;
        B[1] := Format^;
        inc(B);
      end;
      end;
      inc(Format);
    until false;
    // add next value as text
    if ValuesIndex<=high(Values) then // missing value will display nothing
    case ord(Format^) of
    ord('%'):
       with Values[ValuesIndex] do
       case Vtype of
         vtInteger:      Add(VInteger);
         vtBoolean:      AddU(byte(VBoolean));
         vtChar:         Add(@VChar,1,Escape);
         vtExtended:     Add(VExtended^);
         vtString:       Add(@VString^[1],ord(VString^[0]),Escape);
         vtPointer:      AddPointer(PtrUInt(VPointer));
         vtPChar:        Add(PUTF8Char(VPChar),Escape);
         vtObject:       WriteObject(VObject,[woFullExpand]);
         vtClass:
           if VClass<>nil then
             AddShort(PShortString(PPointer(PtrInt(VClass)+vmtClassName)^)^);
         vtWideChar:
           AddW(@VWideChar,1,Escape);
         vtPWideChar:
           AddW(pointer(VPWideChar),StrLenW(VPWideChar),Escape);
         vtAnsiString:
           Add(VAnsiString,Escape); // expect RawUTF8
         vtCurrency:
           AddCurr64(VInt64^);
         vtWideString:
           if VWideString<>nil then
             AddW(VWideString,length(WideString(VWideString)),Escape);
         vtInt64:
           Add(VInt64^);
{$ifndef NOVARIANTS}
         vtVariant:
           AddVariantJSON(VVariant^,Escape);
{$endif}
{$ifdef UNICODE}
         vtUnicodeString:
           if VUnicodeString<>nil then // convert to UTF-8
             AddW(VUnicodeString,length(UnicodeString(VUnicodeString)),Escape);
{$endif} end;
    {$ifdef OLDTEXTWRITERFORMAT}
    ord('$'): with Values[ValuesIndex] do
           if Vtype=vtInteger then Add2(VInteger);
    163: with Values[ValuesIndex] do // �
           if Vtype=vtInteger then Add4(VInteger);
    181: with Values[ValuesIndex] do // �
           if Vtype=vtInteger then Add3(VInteger);
    {$endif}
    end;
    inc(Format);
    inc(ValuesIndex);
  until false;
end;
{$endif}

procedure TTextWriter.AddLine(const Text: shortstring);
begin
................................................................................
    vtExtended: Add(VExtended^);
    vtCurrency: AddCurr64(VInt64^);
    {$ifndef NOVARIANTS}
    vtVariant:  AddVariantJSON(VVariant^,twJSONEscape);
    {$endif}
  end;
end;





























{$ifndef NOVARIANTS}











procedure TTextWriter.AddJSON(Format: PUTF8Char; const Args,Params: array of const);
begin
  AddVariantJSON(_JsonFastFmt(Format,Args,Params),twJSONEscape);
end;
{$endif}

procedure TTextWriter.AddJSONEscape(const NameValuePairs: array of const);
var a: integer;
................................................................................
      SetText(result);
    finally
      Free
    end;
end;

{$ifndef NOVARIANTS}
function JSONEncode(Format: PUTF8Char; const Args,Params: array of const): RawUTF8; overload;
begin
  with DefaultTextWriterJSONClass.CreateOwnedStream do
  try
    AddJSON(Format,Args,Params);
    SetText(result);
  finally
    Free
................................................................................
    Map.UnMap;
  end;
end;

procedure TRawUTF8List.SetTextPtr(P: PUTF8Char; const Delimiter: RawUTF8);
var DelimLen: PtrInt;
    DelimFirst: AnsiChar;
    PDeb, DelimNext: PUTF8Char;
    Line: RawUTF8;
begin
  DelimLen := length(Delimiter);
  BeginUpdate;
  Clear;
  if (P<>nil) and (DelimLen>0) then begin
    DelimFirst := Delimiter[1];
    DelimNext := PUTF8Char(pointer(Delimiter))+1;
    repeat
      PDeb := P;
      while P^<>#0 do begin
        if (P^=DelimFirst) and CompareMem(P+1,DelimNext,DelimLen-1) then
          break;
        inc(P);
      end;
      SetString(Line,PDeb,P-PDeb);
      AddObject(Line,nil);
      if P^=#0 then
        break;
      inc(P,DelimLen);
    until P^=#0;
  end;
  EndUpdate;
................................................................................
begin
  SynLZDecompress(pointer(Data),length(Data),result);
end;


{ ESynException }

constructor ESynException.CreateUTF8(Format: PUTF8Char; const Args: array of const);
begin
  Create(UTF8ToString(FormatUTF8(Format,Args)));
end;

{$ifndef NOEXCEPTIONINTERCEPT}
function ESynException.CustomLog(WR: TTextWriter;
  const Context: TSynLogExceptionContext): boolean;
................................................................................

{ TSynBackgroundThreadAbstract }

{$ifdef MSWINDOWS}
function IsDebuggerPresent: BOOL; stdcall; external kernel32; // since XP
{$endif}

procedure SetCurrentThreadName(Format: PUTF8Char; const Args: array of const);
begin
  SetThreadName(GetCurrentThreadId,Format,Args);
end;

procedure SetThreadName(ThreadID: cardinal; Format: PUTF8Char; const Args: array of const);

var name: RawByteString;
{$ifndef ISDELPHIXE2}
{$ifdef MSWINDOWS}
    info: record
      FType: LongWord;     // must be 0x1000
      FName: PAnsiChar;    // pointer to name (in user address space)
      FThreadID: LongWord; // thread ID (-1 indicates caller thread)






>
>
>







 







|












|









>
>
>
>
>







 







|







 







|







 







|







 







>
>
>
>
>







 







|







 







|







 







|







 







|




|







 







|







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







|







 







|







 







|







|













|



|









>
|
|
|
<
|
>

|
|



|

|
|






|


|
|



|
|




|










>
>
>
>







>
|
|
|
|
|




|



|

|
|







 







|
|
|









|



|
|


|
|

|
|







 







|



|
|
|
|
|
|







|



|
|
|







|





|
|
|
|

|













|

|



|
|







 







|







 







|
|
|

|
|
|

|

|







 







|



|



|
|
|
|
|







 







|





|







 







|









|







 







|


>


|

>
>
>
>

>


|







|








|



|



|

|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<









|







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
|







 







|







 







|









|





|







 







|







 







|




|
>







370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
....
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
....
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
....
5479
5480
5481
5482
5483
5484
5485
5486
5487
5488
5489
5490
5491
5492
5493
....
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
....
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
....
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
....
6802
6803
6804
6805
6806
6807
6808
6809
6810
6811
6812
6813
6814
6815
6816
....
7444
7445
7446
7447
7448
7449
7450
7451
7452
7453
7454
7455
7456
7457
7458
....
7901
7902
7903
7904
7905
7906
7907
7908
7909
7910
7911
7912
7913
7914
7915
7916
7917
7918
7919
7920
.....
10756
10757
10758
10759
10760
10761
10762
10763
10764
10765
10766
10767
10768
10769
10770
.....
10807
10808
10809
10810
10811
10812
10813
10814
10815
10816
10817
10818
10819
10820
10821
.....
13196
13197
13198
13199
13200
13201
13202
13203
13204
13205
13206
13207
13208
13209
13210
13211
13212
13213
13214
13215
13216
13217
13218
13219
13220
13221
13222
.....
15570
15571
15572
15573
15574
15575
15576
15577
15578
15579
15580
15581
15582
15583
15584
.....
15603
15604
15605
15606
15607
15608
15609
15610
15611
15612
15613
15614
15615
15616
15617
.....
15623
15624
15625
15626
15627
15628
15629
15630
15631
15632
15633
15634
15635
15636
15637
.....
15842
15843
15844
15845
15846
15847
15848
15849
15850
15851
15852
15853
15854
15855
15856
15857
15858
15859
15860
15861
15862
15863
15864
15865
15866
15867
15868
15869
15870
15871
15872
15873
15874
15875
15876
15877
15878
15879
15880
15881
15882
15883
15884
15885
15886
15887
15888

15889
15890
15891
15892
15893
15894
15895
15896
15897
15898
15899
15900
15901
15902
15903
15904
15905
15906
15907
15908
15909
15910
15911
15912
15913
15914
15915
15916
15917
15918
15919
15920
15921
15922
15923
15924
15925
15926
15927
15928
15929
15930
15931
15932
15933
15934
15935
15936
15937
15938
15939
15940
15941
15942
15943
15944
15945
15946
15947
15948
15949
15950
15951
15952
15953
15954
15955
15956
15957
15958
15959
15960
15961
15962
15963
15964
15965
15966
15967
.....
15987
15988
15989
15990
15991
15992
15993
15994
15995
15996
15997
15998
15999
16000
16001
16002
16003
16004
16005
16006
16007
16008
16009
16010
16011
16012
16013
16014
16015
16016
16017
16018
16019
16020
16021
16022
16023
16024
16025
.....
18299
18300
18301
18302
18303
18304
18305
18306
18307
18308
18309
18310
18311
18312
18313
18314
18315
18316
18317
18318
18319
18320
18321
18322
18323
18324
18325
18326
18327
18328
18329
18330
18331
18332
18333
18334
18335
18336
18337
18338
18339
18340
18341
18342
18343
18344
18345
18346
18347
18348
18349
18350
18351
18352
18353
18354
18355
18356
18357
18358
18359
18360
18361
18362
18363
18364
18365
18366
18367
18368
18369
18370
18371
18372
18373
18374
18375
18376
.....
18486
18487
18488
18489
18490
18491
18492
18493
18494
18495
18496
18497
18498
18499
18500
.....
18506
18507
18508
18509
18510
18511
18512
18513
18514
18515
18516
18517
18518
18519
18520
18521
18522
18523
18524
18525
18526
18527
18528
18529
18530
.....
20760
20761
20762
20763
20764
20765
20766
20767
20768
20769
20770
20771
20772
20773
20774
20775
20776
20777
20778
20779
20780
20781
20782
20783
20784
20785
20786
.....
30174
30175
30176
30177
30178
30179
30180
30181
30182
30183
30184
30185
30186
30187
30188
30189
30190
30191
30192
30193
30194
.....
32213
32214
32215
32216
32217
32218
32219
32220
32221
32222
32223
32224
32225
32226
32227
32228
32229
32230
32231
32232
32233
32234
32235
32236
32237
.....
33815
33816
33817
33818
33819
33820
33821
33822
33823
33824
33825
33826
33827
33828
33829
33830
33831
33832
33833
33834
33835
33836
33837
33838
33839
33840
33841
33842
33843
33844
33845
33846
33847
33848
33849
33850
33851
33852
33853
33854
33855
33856
33857
33858
33859
33860
33861
33862
33863
33864
33865


































33866
33867
33868
33869
33870
33871
33872
33873
33874
33875
33876
33877
33878
33879
33880
33881
33882
.....
34508
34509
34510
34511
34512
34513
34514
34515
34516
34517
34518
34519
34520
34521
34522
34523
34524
34525
34526
34527
34528
34529
34530
34531
34532
34533
34534
34535
34536
34537
34538
34539
34540
34541
34542
34543
34544
34545
34546
34547
34548
34549
34550
34551
34552
34553
34554
34555
34556
34557
34558
34559
34560
34561
34562
34563
.....
35112
35113
35114
35115
35116
35117
35118
35119
35120
35121
35122
35123
35124
35125
35126
.....
37623
37624
37625
37626
37627
37628
37629
37630
37631
37632
37633
37634
37635
37636
37637
37638
37639
37640
37641
37642
37643
37644
37645
37646
37647
37648
37649
37650
37651
37652
37653
.....
42063
42064
42065
42066
42067
42068
42069
42070
42071
42072
42073
42074
42075
42076
42077
.....
42600
42601
42602
42603
42604
42605
42606
42607
42608
42609
42610
42611
42612
42613
42614
42615
42616
42617
42618
42619
42620
  - BREAKING CHANGE of TTextWriter.WriteObject() method: serialization is now
    defined with a new TTextWriterWriteObjectOptions set
  - BREAKING CHANGE rename of Iso8601 low-level structure as TTimeLogBits, to use
    explicitly the TTimeLog type and name for all Int64 bit-oriented functions -
    now "Iso8601" naming will be only for standard ISO-8601 text, not Int64 value
  - BREAKING CHANGE: TTextWriter.Add(Format) won't handle the alternate $ % tags
    any more, unless you define the OLDTEXTWRITERFORMAT conditional
  - BREAKING CHANGE: FormatUTF8() and TTextWriter.Add(Format) PUTF8Char type for
    constant text parameter has been changed into RawUTF8, to let the compiler
    handle any Unicode content as expected
  - Delphi XE4/XE5/XE6/XE7/XE8 compatibility (Win32/Win64 target platform only
    for the SynCommons and mORMot* units, but see SynCrossPlatform* units for
    clients on all other targets, including OSX and the NextGen compilers)
  - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
  - now all variants created within our units will create string instances of
    kind varString and type RawUTF8 - prior to Delphi 2009, ensure you call
    UTF8ToString(aVariant) if you want to use the value with the VCL
................................................................................
/// fast Format() function replacement, optimized for RawUTF8
// - only supported token is %, which will be inlined in the resulting string
// according to each Args[] supplied item
// - resulting string has no length limit and uses fast concatenation
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
function FormatUTF8(const Format: RawUTF8; const Args: array of const): RawUTF8; overload;

/// fast Format() function replacement, handling % and ? parameters
// - will include Args[] for every % in Format
// - will inline Params[] for every ? in Format, handling special "inlined"
// parameters, as exected by mORMot.pas unit, i.e. :(1234): for numerical
// values, and :('quoted '' string'): for textual values
// - if optional JSONFormat parameter is TRUE, ? parameters will be written
// as JSON quoted strings, without :(...): tokens, e.g. "quoted "" string"
// - resulting string has no length limit and uses fast concatenation
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
function FormatUTF8(const Format: RawUTF8; const Args, Params: array of const;
  JSONFormat: boolean=false): RawUTF8; overload;

/// convert an open array (const Args: array of const) argument to an UTF-8
// encoded text
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
procedure VarRecToUTF8(const V: TVarRec; var result: RawUTF8;
  wasString: PBoolean=nil);

/// convert an open array (const Args: array of const) argument to an Int64
// - returns TRUE and set Value if the supplied argument is a vtInteger or vtInt64
// - returns FALSE if the argument is not an integer 
function VarRecToInt64(const V: TVarRec; out value: Int64): boolean;

/// convert an open array (const Args: array of const) argument to a value
// encoded as with :(..:) inlined parameters in FormatUTF8(Format,Args,Params)
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - any supplied TObject instance will be written as their class name
procedure VarRecToInlineValue(const V: TVarRec; var result: RawUTF8);
................................................................................
    // - expected element layout is to have a RawUTF8 field at first position
    // - the aName is searched (using hashing) to be unique, and if not the case,
    // an ESynException.CreateUTF8() is raised with the supplied arguments 
    // - use internaly FindHashedForAdding method
    // - this version will set the field content with the unique value
    // - returns a pointer to the newly added element (to set other fields)
    function AddUniqueName(const aName: RawUTF8;
      const ExceptionMsg: RawUTF8; const ExceptionArgs: array of const): pointer;
    /// search for a given element name, make it unique, and add it to the array
    // - expected element layout is to have a RawUTF8 field at first position
    // - the aName is searched (using hashing) to be unique, and if not the case,
    // some suffix is added to make it unique
    // - use internaly FindHashedForAdding method
    // - this version will set the field content with the unique value
    // - returns a pointer to the newly added element (to set other fields)
................................................................................
    // - CR = #13 indicates CR+LF chars
    // - � = #167 indicates to trim last comma
    // - | = #124 will write the next char e.g. Add('%|$',[10]) will write '10$'
    // - since some of this characters above are > #127, they are not UTF-8
    // ready, so we expect the input format to be WinAnsi, i.e. mostly English
    // text (with chars < #128) with some values to be inserted inside
    {$endif}
    procedure Add(const Format: RawUTF8; const Values: array of const;
      Escape: TTextWriterKind=twNone); overload;
{$endif DELPHI5OROLDER}
    /// append some values at once
    // - text values (e.g. RawUTF8) will be escaped as JSON
    procedure Add(const Values: array of const); overload;
    /// append CR+LF (#13#10) chars
    // - this method won't call EchoAdd() registered events - use AddEndOfLine()
................................................................................
    // versions of Delphi will retrieve the code page from string
    // - if CodePage is defined to a >= 0 value, the encoding will take place
    procedure AddAnyAnsiString(const s: RawByteString; Escape: TTextWriterKind;
      CodePage: Integer=-1);
    /// append some chars to the buffer
    // - if Len is 0, Len is calculated from zero-ended char
    // - don't escapes chars according to the JSON RFC
    procedure AddNoJSONEscape(P: Pointer; Len: integer=0); overload;
    /// append some chars, quoting all " chars
    // - same algorithm than AddString(QuotedStr()) - without memory allocation
    // - this function implements what is specified in the official SQLite3
    // documentation: "A string constant is formed by enclosing the string in single
    // quotes ('). A single quote within the string can be encoded by putting two
    // single quotes in a row - as in Pascal."
    procedure AddQuotedStr(Text: PUTF8Char; Quote: AnsiChar; TextLen: integer=0); 
................................................................................
    // - escapes chars according to the JSON RFC
    procedure AddJSONEscapeW(P: PWord; Len: PtrInt=0);
    /// append an open array constant value to the buffer
    // - "" will be added if necessary
    // - escapes chars according to the JSON RFC
    // - very fast (avoid most temporary storage)
    procedure AddJSONEscape(const V: TVarRec); overload;
    /// append an open array constant value to the buffer
    // - "" won't be added for string values
    // - string values may be escaped, depending on the supplied parameter
    // - very fast (avoid most temporary storage)
    procedure Add(const V: TVarRec; Escape: TTextWriterKind=twNone); overload;
    /// encode the supplied data as an UTF-8 valid JSON object content
    // - data must be supplied two by two, as Name,Value pairs, e.g.
    // ! aWriter.AddJSONEscape(['name','John','year',1972]);
    // will append to the buffer:
    // ! '{"name":"John","year":1972}'
    // - or you can specify nested arrays or objects with '['..']' or '{'..'}':
    // ! aWriter.AddJSONEscape(['doc','{','name','John','ab','[','a','b']','}','id',123]);
................................................................................
    // ! new Date()   ObjectId()   MinKey   MaxKey  /<jRegex>/<jOptions>
    // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
    // !  aWriter.AddJSON('{name:?,field:/%/i}',['acme.*corp'],['John']))
    // ! // will write
    // ! '{"name":"John","field":{"$regex":"acme.*corp","$options":"i"}}'
    // - will call internally _JSONFastFmt() to create a temporary TDocVariant
    // with all its features - so is slightly slower than other AddJSON* methods
    procedure AddJSON(const Format: RawUTF8; const Args,Params: array of const);
{$endif}
    /// append a dynamic array content as UTF-8 encoded JSON array
    // - expect a dynamic array TDynArray wrapper as incoming parameter
    // - TIntegerDynArray, TInt64DynArray, TCardinalDynArray, TDoubleDynArray,
    // TCurrencyDynArray, TWordDynArray and TByteDynArray will be written as
    // numerical JSON values
    // - TRawUTF8DynArray, TWinAnsiDynArray, TRawByteStringDynArray,
................................................................................
// ! new Date()   ObjectId()   MinKey   MaxKey  /<jRegex>/<jOptions>
// see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
// !  aJSON := JSONEncode('{name:?,field:/%/i}',['acme.*corp'],['John']))
// ! // will return
// ! '{"name":"John","field":{"$regex":"acme.*corp","$options":"i"}}'
// - will call internally _JSONFastFmt() to create a temporary TDocVariant with
// all its features - so is slightly slower than other JSONEncode* functions
function JSONEncode(const Format: RawUTF8; const Args,Params: array of const): RawUTF8; overload;
{$endif}

/// encode the supplied RawUTF8 array data as an UTF-8 valid JSON array content
function JSONEncodeArray(const Values: array of RawUTF8): RawUTF8; overload;

/// encode the supplied integer array data as a valid JSON array
function JSONEncodeArray(const Values: array of integer): RawUTF8; overload;
................................................................................
  ESynException = class(Exception)
  public
    /// constructor which will use FormatUTF8() instead of Format()
    // - expect % as delimitor, so is less error prone than %s %d %g
    // - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments,
    // appending class name for any class or object, the hexa value for a
    // pointer, or the JSON representation of the supplied variant
    constructor CreateUTF8(const Format: RawUTF8; const Args: array of const);
    {$ifndef NOEXCEPTIONINTERCEPT}
    /// can be used to customize how the exception is logged
    // - this default implementation will call the DefaultSynLogExceptionToStr()
    // function or the TSynLogExceptionToStrCustom global callback, if defined
    // - override this method to provide a custom logging content
    // - should return TRUE if Context.EAddr and Stack trace is not to be
    // written (i.e. as for any TSynLogExceptionToStr callback)
................................................................................
function SearchFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer;

/// convert an array of field indexes into a TSQLFieldBits set of bits
function FieldIndexToBits(const Index: TSQLFieldIndexDynArray): TSQLFieldBits; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// name the current thread so that it would be easily identified in the IDE debugger
procedure SetCurrentThreadName(const Format: RawUTF8; const Args: array of const);

/// name a thread so that it would be easily identified in the IDE debugger
// - you can force this function to do nothing by setting the NOSETTHREADNAME
// conditional, if you have issues with this feature when debugging your app
procedure SetThreadName(ThreadID: cardinal; const Format: RawUTF8; const Args: array of const);

type
  TSynBackgroundThreadAbstract = class;
  TSynBackgroundThreadEvent = class;

  /// idle method called by TSynBackgroundThreadAbstract in the caller thread
  // during remote blocking process in a background thread
................................................................................
// ! assert(u='{"type":{"$in":["food","snack"]}}');
// ! u := VariantSaveMongoJSON(aVariant,modMongoShell);
// ! assert(u='{type:{$in:["food","snack"]}}');
// - by default, every internal value will be copied, so access of nested
// properties can be slow - if you expect the data to be read-only or not
// propagated into another place, add dvoValueCopiedByReference in Options
// will increase the process speed a lot, or use _JsonFast()
function _JsonFmt(const Format: RawUTF8; const Args,Params: array of const;
  Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): variant;

/// initialize a variant instance to store some document-based content
// from a supplied (extended) JSON content
// - this global function is an alias to TDocVariant.NewJSON(), and
// will return TRUE if JSON content was correctly converted into a variant
// - in addition to the JSON RFC specification strict mode, this method will
................................................................................
// from a supplied (extended) JSON content, with parameters formating
// - this global function is an handy alias e.g. to:
// ! aVariant := _JSONFmt('{%:{$in:[?,?]}}',['type'],['food','snack'],JSON_OPTIONS[true]);
// - so all created objects and arrays will be handled by reference, for best
// speed - but you should better write on the resulting variant tree with caution
// - in addition to the JSON RFC specification strict mode, this method will
// handle some BSON-like extensions, e.g. unquoted field names or ObjectID():
function _JsonFastFmt(const Format: RawUTF8; const Args,Params: array of const): variant;

/// ensure a document-based variant instance will have only per-value nested
// objects or array documents
// - is just a wrapper around:
// ! TDocVariantData(DocVariant).InitCopy(DocVariant,JSON_OPTIONS[false])
// - you can use this function to ensure that all internal properties of this
// variant will be copied per-value whatever options the nested objects or
................................................................................
begin
  case V.VType of
    vtChar:     result := ord(V.VChar);
    vtWideChar: result := ord(V.VWideChar);
    else        result := 0;
  end;
end;

function VarRecToInt64(const V: TVarRec; out value: Int64): boolean;
begin
  case V.VType of
    vtInteger: value := V.VInteger;
    vtInt64:   value := V.VInt64^;
    else begin
      result := false;
      exit;
    end;
  end;
  result := true;
end;

procedure VarRecToUTF8(const V: TVarRec; var result: RawUTF8; wasString: PBoolean=nil);
var isString: boolean;
begin
  isString := not (V.VType in [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended]);
  with V do
  case V.VType of
................................................................................
  DigitChars = ['-','+','0'..'9'];
  DigitFirstChars = ['-','1'..'9']; // 0/- excluded by JSON!
  DigitFloatChars = ['-','+','0'..'9','.','E','e'];


function SQLParamContent(P: PUTF8Char; out ParamType: TSQLParamType; out ParamValue: RawUTF8;
  out wasNull: boolean): PUTF8Char;
var PBeg: PAnsiChar;
    L: integer;
    c: cardinal;
begin
  ParamType := sptUnknown;
  wasNull := false;
  result := nil;
  if P=nil then
................................................................................
        ParamValue := copy(ParamValue,4,L); // return ISO-8601 text
        ParamType := sptDateTime;           // identified as Date/Time
      end;
    end;
  end;
  '-','+','0'..'9': begin // allow 0 or + in SQL
    // check if P^ is a true numerical value
    PBeg := pointer(P);
    ParamType := sptInteger;
    repeat inc(P) until not (P^ in ['0'..'9']); // check digits
    if P^='.' then begin
      inc(P);
      if P^ in ['0'..'9'] then begin
        ParamType := sptFloat;
        repeat inc(P) until not (P^ in ['0'..'9']); // check fractional digits
................................................................................
    if byte(P^) and $DF=ord('E') then begin
      ParamType := sptFloat;
      inc(P);
      if P^='+' then inc(P) else
      if P^='-' then inc(P);
      while P^ in ['0'..'9'] do inc(P);
    end;
    SetRawUTF8(ParamValue,PBeg,P-PBeg);
  end;
  'n':
  if PInteger(P)^=NULL_LOW then begin
    inc(P,4);
    wasNull := true;
  end else
    exit; // invalid content (only :(null): expected)
................................................................................
var tmp: ShortString;
begin
  if Value=0 then
    result := '0' else
    SetString(result,PAnsiChar(@tmp[1]),ExtendedToString(tmp,Value,DOUBLE_PRECISION));
end;

function FormatUTF8(const Format: RawUTF8; const Args: array of const): RawUTF8;
// only supported token is %, with any const arguments
var i, blocksN, L, argN: PtrInt;
    blocks: array of record
      Text: PUTF8Char;
      Len: integer;
    end;
    Arg: TRawUTF8DynArray;
    F,FDeb: PUTF8Char;
procedure Add(aText: PUTF8Char; aLen: Integer);
begin
  if aLen>0 then begin
    inc(L,aLen);
    assert(blocksN<length(blocks));
    with blocks[blocksN] do begin // add inbetween text
      Text := aText;
      Len := aLen;
    end;
    inc(blocksN);
  end;
end;
begin
  if (Format='') or (high(Args)<0) then begin
    result := Format; // no formatting to process
    exit;
  end;
  if Format='%' then begin
    VarRecToUTF8(Args[0],result); // optimize raw conversion
    exit;
  end;
  result := '';
  SetLength(Arg,length(Args));
  SetLength(blocks,length(Args)*2+1);
  blocksN := 0;
  argN := 0;
  L := 0;
  F := pointer(Format);
  while F^<>#0 do begin
    if F^<>'%' then begin
      FDeb := F;

      while (F^<>'%') and (F^<>#0) do inc(F);
      Add(FDeb,F-FDeb);
    end;
    if F^=#0 then break;
    inc(F); // jump '%'
    if argN<=high(Args) then begin
      VarRecToUTF8(Args[argN],arg[argN]);
      Add(pointer(arg[argN]),length(arg[argN]));
      inc(argN);
    end else
    if F^<>#0 then begin // no more available Args -> add all remaining text
      Add(F,StrLen(F));
      break;
    end;
  end;
  if L=0 then
    exit;
  SetLength(result,L);
  F := pointer(result);
  for i := 0 to blocksN-1 do
  with blocks[i] do begin
    move(Text^,F^,Len);
    inc(F,Len);
  end;
end;

function FormatUTF8(const Format: RawUTF8; const Args, Params: array of const; JSONFormat: boolean): RawUTF8; overload;
// supports both % and ? tokens
var i, tmpN, L, A, P, len: PtrInt;
    isParam: AnsiChar;
    tmp: TRawUTF8DynArray; 
    inlin: set of 0..255; 
    F,FDeb: PUTF8Char;
    wasString: Boolean;
const QUOTECHAR: array[boolean] of AnsiChar = ('''','"');
      NOTTOQUOTE: array[boolean] of set of 0..31 = (
        [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended],
        [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended,vtVariant]);
label Txt;
begin
  if (Format='') or ((high(Args)<0)and(high(Params)<0)) then begin
    result := Format; // no formatting to process
    exit;
  end;
  if Format='%' then begin
    VarRecToUTF8(Args[0],result); // optimize raw conversion
    exit;
  end;
  result := '';
  tmpN := 0;
  FillChar(inlin,SizeOf(inlin),0);
  L := 0;
  A := 0;
  P := 0;
  F := pointer(Format);
  while F^<>#0 do begin
    if F^<>'%' then begin
      FDeb := F;
      while not (F^ in [#0,'%','?']) do inc(F);
Txt:  len := F-FDeb;
      if len>0 then begin
        inc(L,len);
        if tmpN=length(tmp) then
          SetLength(tmp,tmpN+8);
        SetString(tmp[tmpN],FDeb,len); // add inbetween text
        inc(tmpN);
      end;
    end;
    if F^=#0 then
      break;
    isParam := F^;
    inc(F); // jump '%' or '?'
    if (isParam='%') and (A<=high(Args)) then begin // handle % substitution
      if tmpN=length(tmp) then
        SetLength(tmp,tmpN+8);
      VarRecToUTF8(Args[A],tmp[tmpN]);
      inc(A);
      if tmp[tmpN]<>'' then begin
        inc(L,length(tmp[tmpN]));
................................................................................
          include(inlin,tmpN);
        end;
      end;
      inc(P);
      inc(L,length(tmp[tmpN]));
      inc(tmpN);
    end else
    if F^<>#0 then begin // no more available Args -> add all remaining text
      FDeb := F;
      repeat inc(F) until (F^=#0);
      goto Txt;
    end;
  end;
  if L=0 then
    exit;
  if (not JSONFormat) and (tmpN>SizeOf(inlin)shl 3) then
    raise ESynException.CreateUTF8(
      'Too many parameters for FormatUTF8(): %>%',[tmpN,SizeOf(inlin)shl 3]);
  SetLength(result,L);
  F := pointer(result);
  for i := 0 to tmpN-1 do
  if tmp[i]<>'' then begin
    if i in inlin then begin
      PWord(F)^ := ord(':')+ord('(')shl 8;
      inc(F,2);
    end;
    L := PInteger(PtrInt(tmp[i])-sizeof(integer))^;
    move(pointer(tmp[i])^,F^,L);
    inc(F,L);
    if i in inlin then begin
      PWord(F)^ := ord(')')+ord(':')shl 8;
      inc(F,2);
    end;
  end;
end;

function RawByteStringArrayConcat(const Values: array of RawByteString): RawByteString;
var i, L: integer;
    P: PAnsiChar;
................................................................................
@z: pop edx       // ignore source var, result := false
end;
{$endif}

/// find the Value of UpperName in P, till end of current section
// - expect UpperName as 'NAME='
function FindIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8;
var PBeg: PUTF8Char;
    L: integer;
begin
  while (P<>nil) and (P^<>'[') do begin
    PBeg := GetNextLineBegin(P,P); // since PBeg=P, we have PBeg<>nil
    if PBeg^=' ' then repeat inc(PBeg) until PBeg^<>' ';   // trim left ' '
    if IdemPChar(PBeg,UpperName) then begin
      inc(PBeg,StrLen(PUTF8Char(UpperName)));
      L := 0; while PBeg[L]>=' ' do inc(L); // get line length
      SetString(result,PBeg,L);
      exit;
    end;
  end;
  result := '';
end;

function ExistsIniName(P: PUTF8Char; UpperName: PAnsiChar): boolean;
var PBeg: PUTF8Char;
begin
  result := true;
  while (P<>nil) and (P^<>'[') do begin
    PBeg := GetNextLineBegin(P,P); // since PBeg=P, we have PBeg<>nil
    if PBeg^=' ' then repeat inc(PBeg) until PBeg^<>' ';   // trim left ' '
    if IdemPChar(PBeg,UpperName) then
      exit;
  end;
  result := false;
end;

function ExistsIniNameValue(P: PUTF8Char; const UpperName: RawUTF8;
  const UpperValues: array of RawUTF8): boolean;
var PBeg: PUTF8Char;
    i: integer;
begin
  result := true;
  if high(UpperValues)>=0 then
    while (P<>nil) and (P^<>'[') do begin
      PBeg := GetNextLineBegin(P,P); // since PBeg=P, we have PBeg<>nil
      if PBeg^=' ' then repeat inc(PBeg) until PBeg^<>' ';   // trim left ' '
      if IdemPChar(PBeg,pointer(UpperName)) then begin
        inc(PBeg,length(UpperName));
        for i := 0 to high(UpperValues) do
          if IdemPChar(PBeg,pointer(UpperValues[i])) then
            exit; // found one value
        break;
      end;
    end;
  result := false;
end;

function FindWinAnsiIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8;
begin
  result := WinAnsiToUtf8(RawByteString(FindIniNameValue(P,UpperName)));
end;

function GetSectionContent(SectionFirstLine: PUTF8Char): RawUTF8;
var PBeg: PUTF8Char;
begin
  PBeg := SectionFirstLine;
  while (SectionFirstLine<>nil) and (SectionFirstLine^<>'[') do
    GetNextLineBegin(SectionFirstLine,SectionFirstLine);
  if SectionFirstLine=nil then
    result := PBeg else
    SetString(result,PBeg,SectionFirstLine-PBeg);
end;

function GetSectionContent(const Content, SectionName: RawUTF8): RawUTF8; overload;
var P: PUTF8Char;
    UpperSection: array[byte] of AnsiChar;
begin
  P := pointer(Content);
................................................................................
    result := '' else
    result := FindIniEntry(Content,Section,Name);
end;

procedure UpdateIniEntry(var Content: RawUTF8; const Section,Name,Value: RawUTF8);
const CRLF = #13#10;
var P: PUTF8Char;
    PBeg: PUTF8Char;
    SectionFound: boolean;
    i, UpperNameLength: PtrInt;
    V: RawUTF8;
    UpperSection, UpperName: array[byte] of AnsiChar;
    // possible GPF if length(Section/Name)>255, but should be short const in code
label Sec;
begin
................................................................................
  if Section='' then
    goto Sec; // find the Name= entry before any [Section]
  SectionFound := false;
  PWord(UpperCopy255(UpperSection,Section))^ := ord(']');
  if FindSectionFirstLine(P,UpperSection) then begin 
Sec:SectionFound := true;
    while (P<>nil) and (P^<>'[') do begin
      PBeg := GetNextLineBegin(P,P); // since PBeg=P, we have PBeg<>nil
      while PBeg^=' ' do inc(PBeg);   // trim left ' '
      if IdemPChar(PBeg,UpperName) then begin
        // update Name=Value entry
        inc(PBeg,UpperNameLength);
        i := (PBeg-pointer(Content))+1;
        if (i=length(Value)) and CompareMem(PBeg,pointer(Value),i) then
          exit; // new Value is identical to the old one -> no change
        if P=nil then // avoid last line (P-PBeg) calculation error
          SetLength(Content,i-1) else
          delete(Content,i,P-PBeg); // delete old Value
        insert(V,Content,i); // set new value
        exit;
      end;
    end;
    // we reached next [Section] without having found Name=
   end;
  // 2. section or Name= entry not found: add Name=Value
................................................................................
  if source^=#10 then inc(source);
  if source^=#0 then
    next := nil else
    next := source;
end;

function FindIniNameValueW(P: PWideChar; UpperName: PUTF8Char): string;
var PBeg: PWideChar;
    L: PtrInt;
begin
  while (P<>nil) and (P^<>'[') do begin
    PBeg := P;
    while not (cardinal(P^) in [0,10,13]) do inc(P);
    while cardinal(P^) in [10,13] do inc(P);
    if P^=#0 then P := nil;
    if PBeg^=' ' then repeat inc(PBeg) until PBeg^<>' ';   // trim left ' '
    if IdemPCharW(PBeg,UpperName) then begin
      inc(PBeg,StrLen(UpperName));
      L := 0; while PBeg[L]>=' ' do inc(L); // get line length
      SetString(result,PBeg,L);
      exit;
    end;
  end;
  result := '';
end;

function FindIniEntryW(const Content: string; const Section, Name: RawUTF8): string;
................................................................................
end;

function _JsonFast(const JSON: RawUTF8): variant;
begin
  _Json(JSON,result,JSON_OPTIONS[true]);
end;

function _JsonFmt(const Format: RawUTF8; const Args,Params: array of const;
  Options: TDocVariantOptions): variant;
begin
  _Json(FormatUTF8(Format,Args,Params,true),result,Options);
end;

function _JsonFastFmt(const Format: RawUTF8; const Args,Params: array of const): variant;
begin
  _Json(FormatUTF8(Format,Args,Params,true),result,JSON_OPTIONS[true]);
end;

function _Json(const JSON: RawUTF8; var Value: variant;
  Options: TDocVariantOptions): boolean;
begin
................................................................................
  end;
  assert(ndx=Count-1);
  result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
  PRawUTF8(result)^ := aName; // store unique name at 1st elem position
end;

function TDynArrayHashed.AddUniqueName(const aName: RawUTF8;
  const ExceptionMsg: RawUTF8; const ExceptionArgs: array of const): pointer;
var ndx: integer;
    added: boolean;
begin
  ndx := FindHashedForAdding(aName,added);
  if added then begin
    assert(ndx=Count-1);
    result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
    PRawUTF8(result)^ := aName; // store unique name at 1st elem position
  end else
    if ExceptionMsg='' then
      raise ESynException.CreateUTF8('Duplicated "%" name',[aName]) else
      raise ESynException.CreateUTF8(ExceptionMsg,ExceptionArgs);
end;

function TDynArrayHashed.FindHashedAndFill(var Elem): integer;
var P: PAnsiChar;
begin
................................................................................
    end;
    end;
  CancelLastComma;
  Add(']');
end;

{$ifndef DELPHI5OROLDER}
procedure TTextWriter.Add(const Format: RawUTF8; const Values: array of const;
  Escape: TTextWriterKind=twNone);
var ValuesIndex: integer;
    F: PUTF8Char;
label write;
begin // we put const char > #127 as #??? -> asiatic MBCS codepage OK
  if Format='' then
    exit;
  if (Format='%') and (high(Values)>=0) then begin
    Add(Values[0],Escape);
    exit;
  end;
  ValuesIndex := 0;
  F := pointer(Format);
  repeat
    repeat
      case ord(F^) of
      0: exit;
      13: AddCR;
      ord('%'): break;
      {$ifdef OLDTEXTWRITERFORMAT}
      164: AddCR; // � -> add CR,LF
      167: if B^=',' then dec(B); // �
      ord('|'): begin
        inc(F); // |% -> %
        goto write;
      end;
      ord('$'),163,181: // $,�,�
        break; // process command value
      {$endif}
      else begin
write:  if B>=BEnd then
          Flush;
        B[1] := F^;
        inc(B);
      end;
      end;
      inc(F);
    until false;
    // add next value as text
    if ValuesIndex<=high(Values) then // missing value will display nothing
    case ord(F^) of
    ord('%'):
      Add(Values[ValuesIndex],Escape);


































    {$ifdef OLDTEXTWRITERFORMAT}
    ord('$'): with Values[ValuesIndex] do
           if Vtype=vtInteger then Add2(VInteger);
    163: with Values[ValuesIndex] do // �
           if Vtype=vtInteger then Add4(VInteger);
    181: with Values[ValuesIndex] do // �
           if Vtype=vtInteger then Add3(VInteger);
    {$endif}
    end;
    inc(F);
    inc(ValuesIndex);
  until false;
end;
{$endif}

procedure TTextWriter.AddLine(const Text: shortstring);
begin
................................................................................
    vtExtended: Add(VExtended^);
    vtCurrency: AddCurr64(VInt64^);
    {$ifndef NOVARIANTS}
    vtVariant:  AddVariantJSON(VVariant^,twJSONEscape);
    {$endif}
  end;
end;

procedure TTextWriter.Add(const V: TVarRec; Escape: TTextWriterKind);
begin
  with V do
  case Vtype of
  vtInteger:      Add(VInteger);
  vtBoolean:      AddU(byte(VBoolean));
  vtChar:         Add(@VChar,1,Escape);
  vtExtended:     Add(VExtended^);
  vtString:       Add(@VString^[1],ord(VString^[0]),Escape);
  vtPointer:      AddPointer(PtrUInt(VPointer));
  vtPChar:        Add(PUTF8Char(VPChar),Escape);
  vtObject:       WriteObject(VObject,[woFullExpand]);
  vtClass:
   if VClass<>nil then
     AddShort(PShortString(PPointer(PtrInt(VClass)+vmtClassName)^)^);
  vtWideChar:
   AddW(@VWideChar,1,Escape);
  vtPWideChar:
   AddW(pointer(VPWideChar),StrLenW(VPWideChar),Escape);
  vtAnsiString:
   Add(VAnsiString,Escape); // expect RawUTF8
  vtCurrency:
   AddCurr64(VInt64^);
  vtWideString:
   if VWideString<>nil then
     AddW(VWideString,length(WideString(VWideString)),Escape);
  vtInt64:
   Add(VInt64^);
  {$ifndef NOVARIANTS}
  vtVariant:
   AddVariantJSON(VVariant^,Escape);
  {$endif}
  {$ifdef UNICODE}
  vtUnicodeString:
   if VUnicodeString<>nil then // convert to UTF-8
     AddW(VUnicodeString,length(UnicodeString(VUnicodeString)),Escape);
  {$endif} end;
end;

{$ifndef NOVARIANTS}
procedure TTextWriter.AddJSON(const Format: RawUTF8; const Args,Params: array of const);
begin
  AddVariantJSON(_JsonFastFmt(Format,Args,Params),twJSONEscape);
end;
{$endif}

procedure TTextWriter.AddJSONEscape(const NameValuePairs: array of const);
var a: integer;
................................................................................
      SetText(result);
    finally
      Free
    end;
end;

{$ifndef NOVARIANTS}
function JSONEncode(const Format: RawUTF8; const Args,Params: array of const): RawUTF8; overload;
begin
  with DefaultTextWriterJSONClass.CreateOwnedStream do
  try
    AddJSON(Format,Args,Params);
    SetText(result);
  finally
    Free
................................................................................
    Map.UnMap;
  end;
end;

procedure TRawUTF8List.SetTextPtr(P: PUTF8Char; const Delimiter: RawUTF8);
var DelimLen: PtrInt;
    DelimFirst: AnsiChar;
    PBeg, DelimNext: PUTF8Char;
    Line: RawUTF8;
begin
  DelimLen := length(Delimiter);
  BeginUpdate;
  Clear;
  if (P<>nil) and (DelimLen>0) then begin
    DelimFirst := Delimiter[1];
    DelimNext := PUTF8Char(pointer(Delimiter))+1;
    repeat
      PBeg := P;
      while P^<>#0 do begin
        if (P^=DelimFirst) and CompareMem(P+1,DelimNext,DelimLen-1) then
          break;
        inc(P);
      end;
      SetString(Line,PBeg,P-PBeg);
      AddObject(Line,nil);
      if P^=#0 then
        break;
      inc(P,DelimLen);
    until P^=#0;
  end;
  EndUpdate;
................................................................................
begin
  SynLZDecompress(pointer(Data),length(Data),result);
end;


{ ESynException }

constructor ESynException.CreateUTF8(const Format: RawUTF8; const Args: array of const);
begin
  Create(UTF8ToString(FormatUTF8(Format,Args)));
end;

{$ifndef NOEXCEPTIONINTERCEPT}
function ESynException.CustomLog(WR: TTextWriter;
  const Context: TSynLogExceptionContext): boolean;
................................................................................

{ TSynBackgroundThreadAbstract }

{$ifdef MSWINDOWS}
function IsDebuggerPresent: BOOL; stdcall; external kernel32; // since XP
{$endif}

procedure SetCurrentThreadName(const Format: RawUTF8; const Args: array of const);
begin
  SetThreadName(GetCurrentThreadId,Format,Args);
end;

procedure SetThreadName(ThreadID: cardinal; const Format: RawUTF8;
  const Args: array of const);
var name: RawByteString;
{$ifndef ISDELPHIXE2}
{$ifdef MSWINDOWS}
    info: record
      FType: LongWord;     // must be 0x1000
      FName: PAnsiChar;    // pointer to name (in user address space)
      FThreadID: LongWord; // thread ID (-1 indicates caller thread)

Changes to SynDB.pas.

5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
  end;
end;

function TSQLDBConnectionProperties.SQLFieldCreate(const aField: TSQLDBColumnCreate;
  var aAddPrimaryKey: RawUTF8): RawUTF8;
begin
  if (aField.DBType=ftUTF8) and (aField.Width-1<fSQLCreateFieldMax) then
    result := FormatUTF8(pointer(fSQLCreateField[ftNull]),[aField.Width]) else
    result := fSQLCreateField[aField.DBType];
  if aField.NonNullable or aField.Unique or aField.PrimaryKey then
    result := result+' NOT NULL';
  if aField.Unique and not aField.PrimaryKey then
    result := result+' UNIQUE'; // see http://www.w3schools.com/sql/sql_unique.asp
  if aField.PrimaryKey then
    case DBMS of






|







5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
  end;
end;

function TSQLDBConnectionProperties.SQLFieldCreate(const aField: TSQLDBColumnCreate;
  var aAddPrimaryKey: RawUTF8): RawUTF8;
begin
  if (aField.DBType=ftUTF8) and (aField.Width-1<fSQLCreateFieldMax) then
    result := FormatUTF8(fSQLCreateField[ftNull],[aField.Width]) else
    result := fSQLCreateField[aField.DBType];
  if aField.NonNullable or aField.Unique or aField.PrimaryKey then
    result := result+' NOT NULL';
  if aField.Unique and not aField.PrimaryKey then
    result := result+' UNIQUE'; // see http://www.w3schools.com/sql/sql_unique.asp
  if aField.PrimaryKey then
    case DBMS of

Changes to SynLog.pas.

42
43
44
45
46
47
48


49
50
51
52
53
54
55
...
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
...
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
...
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
...
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
....
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
....
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
....
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
....
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
....
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

  Version 1.18
  - first public release, extracted from SynCommons.pas unit


  - Delphi XE4/XE5/XE6/XE7 compatibility (Windows target platform only)
  - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
  - Exception logging and Stack trace do work now on Linux with Kylix/CrossKylix
  - added TSynLogFile.Freq read-only property
  - added DefaultSynLogExceptionToStr() function and TSynLogExceptionToStrCustom
    variable, and ESynException.CustomLog() method to customize how raised
    exception are logged when intercepted - feature request [495720e0b9]
................................................................................
    /// call this method to add some information to the log at a specified level
    // - see the format in TSynLog.Log() method description
    // (not compatible with default SysUtils.Format function)
    // - if Instance is set, it will log the corresponding class name and address
    // (to be used if you didn't call TSynLog.Enter() method first)
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
      Instance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at a specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first)
    // - if Instance is set and Text is '', will behave the same as
................................................................................
    // - TSynLog will write the class and hexa address - TSQLLog will write the
    // object JSON content
    procedure Log(Level: TSynLogInfo; Instance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - TSynLog will handle enumerations and dynamic array; TSQLLog will be
    // able to write TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo=sllTrace); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
................................................................................
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    {$endif}
    class function FamilyCreate: TSynLogFamily;
    procedure DoEnterLeave(aLevel: TSynLogInfo);
    procedure CreateLogWriter; virtual;
{$ifndef DELPHI5OROLDER}
    procedure LogInternal(Level: TSynLogInfo; TextFmt: PWinAnsiChar;
      const TextArgs: array of const; Instance: TObject); overload; 
{$endif}
    procedure LogInternal(Level: TSynLogInfo; const Text: RawUTF8;
      Instance: TObject; TextTruncateAtLength: integer); overload;
    procedure LogInternal(Level: TSynLogInfo; aName: PWinAnsiChar;
     aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload; 
    // any call to this method MUST call UnLock
    function LogHeaderLock(Level: TSynLogInfo): boolean;
    procedure LogTrailerUnLock(Level: TSynLogInfo); {$ifdef HASINLINE}inline;{$endif}
    procedure LogCurrentTime;
    procedure LogFileHeader; virtual;
{$ifndef DELPHI5OROLDER}
................................................................................
    // - i.e. a TSynLog sub-class with Family.Level := []
    class function Void: TSynLogClass;
{$ifndef DELPHI5OROLDER}
    /// low-level method helper which can be called to make debugging easier
    // - log some warning message to the TSynLog family
    // - will force a manual breakpoint if tests are run from the IDE
    class procedure DebuggerNotify(Level: TSynLogInfo;
      const Args: array of const; Format: PWinAnsiChar=nil);
    /// call this method to add some information to the log at the specified level
    // - % = #37 indicates a string, integer, floating-point, or class parameter
    // to be appended as text (e.g. class name)
    // - $ = #36 indicates an integer to be written with 2 digits and a comma
    // - � = #163 indicates an integer to be written with 4 digits and a comma
    // - � = #181 indicates an integer to be written with 3 digits without any comma
    // - � = #164 indicates CR+LF chars
................................................................................
    // - CR = #13 indicates CR+LF chars
    // - � = #167 indicates to trim last comma
    // - since some of this characters above are > #127, they are not UTF-8
    // ready, so we expect the input format to be WinAnsi, i.e. mostly English
    // text (with chars < #128) with some values to be inserted inside
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one RawUTF8 parameter
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: RawUTF8;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one Int64 parameter
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: Int64;
      aInstance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at the specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first) - for instance
    // ! TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector);
................................................................................
    procedure Log(Level: TSynLogInfo; aInstance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - this overridden implementation will write the value content,
    // written as human readable JSON: handle dynamic arrays and enumerations
    // - TSQLLog from mORMot.pas unit will be able to write
    // TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
................................................................................
  TextColor(LOGCOLORS[Level]);
  writeln(#13#10,Text,#13#10);
  TextColor(ccLightGray);
  {$endif}
end;

{$ifndef DELPHI5OROLDER}
procedure TSynLog.Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
  aInstance: TObject);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,TextArgs,aInstance);
end;

procedure TSynLog.Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: RawUTF8;
  aInstance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,[TextArg],aInstance);
end;

procedure TSynLog.Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: Int64;
  aInstance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,[TextArg],aInstance);
end;
{$endif}

................................................................................
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    if aInstance<>nil then
      LogInternal(Level,'',aInstance,maxInt) else
      LogInternal(Level,'Instance=nil',nil,maxInt);
end;

procedure TSynLog.Log(Level: TSynLogInfo; aName: PWinAnsiChar;
  aTypeInfo: pointer; var aValue; Instance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,aName,aTypeInfo,aValue,Instance);
end;

{$STACKFRAMES ON}
................................................................................
      SetLastError(LastError);
  end;
end;
{$STACKFRAMES OFF}

{$ifndef DELPHI5OROLDER}
class procedure TSynLog.DebuggerNotify(Level: TSynLogInfo;
  const Args: array of const; Format: PWinAnsiChar=nil);
var Msg: RawUTF8;
begin
  if Format<>nil then begin
    Msg := FormatUTF8(PUTF8Char(Format),Args);
    Add.LogInternal(Level,Msg,nil,maxInt);
    {$ifdef MSWINDOWS}
    OutputDebugStringA(pointer(Msg));
    {$endif}
    {$ifdef LINUX}
    //write(Msg);
    {$endif}
  end;
  {$ifndef FPC_OR_PUREPASCAL}
  if DebugHook<>0 then
    asm int 3 end; // force manual breakpoint if tests are run from the IDE
................................................................................
      PerformRotation;
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

{$ifndef DELPHI5OROLDER}
procedure TSynLog.LogInternal(Level: TSynLogInfo; TextFmt: PWinAnsiChar;
  const TextArgs: array of const; Instance: TObject);
var LastError: cardinal;
begin
  if Level=sllLastError then
    LastError := GetLastError else
    LastError := 0;
  if LogHeaderLock(Level) then
................................................................................
  finally
    LogTrailerUnLock(Level);
    if LastError<>0 then
      SetLastError(LastError);
  end;
end;

procedure TSynLog.LogInternal(Level: TSynLogInfo; aName: PWinAnsiChar;
   aTypeInfo: pointer; var aValue; Instance: TObject=nil);
begin
  if LogHeaderLock(Level) then
  try
    if Instance<>nil then
      fWriter.AddInstancePointer(Instance,' ');
    fWriter.AddNoJSONEscape(aName);
    fWriter.Add('=');
    fWriter.AddTypedJSON(aTypeInfo,aValue);
  finally
    LogTrailerUnLock(Level);
  end;
end;







>
>







 







|







 







|







 







|




|







 







|







 







|


|


|







 







|







 







|






|






|







 







|







 







|


|
|


|
|







 







|







 







|






|







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
...
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
...
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
...
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
...
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
....
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
....
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
....
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
....
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
....
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

  Version 1.18
  - first public release, extracted from SynCommons.pas unit
  - BREAKING CHANGE: PWinAnsiChar type for constant text parameters has been
    changed into RawUTF8, to allow logging of any Unicode text 
  - Delphi XE4/XE5/XE6/XE7 compatibility (Windows target platform only)
  - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
  - Exception logging and Stack trace do work now on Linux with Kylix/CrossKylix
  - added TSynLogFile.Freq read-only property
  - added DefaultSynLogExceptionToStr() function and TSynLogExceptionToStrCustom
    variable, and ESynException.CustomLog() method to customize how raised
    exception are logged when intercepted - feature request [495720e0b9]
................................................................................
    /// call this method to add some information to the log at a specified level
    // - see the format in TSynLog.Log() method description
    // (not compatible with default SysUtils.Format function)
    // - if Instance is set, it will log the corresponding class name and address
    // (to be used if you didn't call TSynLog.Enter() method first)
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArgs: array of const;
      Instance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at a specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first)
    // - if Instance is set and Text is '', will behave the same as
................................................................................
    // - TSynLog will write the class and hexa address - TSQLLog will write the
    // object JSON content
    procedure Log(Level: TSynLogInfo; Instance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - TSynLog will handle enumerations and dynamic array; TSQLLog will be
    // able to write TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; const aName: RawUTF8;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo=sllTrace); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
................................................................................
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    {$endif}
    class function FamilyCreate: TSynLogFamily;
    procedure DoEnterLeave(aLevel: TSynLogInfo);
    procedure CreateLogWriter; virtual;
{$ifndef DELPHI5OROLDER}
    procedure LogInternal(Level: TSynLogInfo; const TextFmt: RawUTF8;
      const TextArgs: array of const; Instance: TObject); overload; 
{$endif}
    procedure LogInternal(Level: TSynLogInfo; const Text: RawUTF8;
      Instance: TObject; TextTruncateAtLength: integer); overload;
    procedure LogInternal(Level: TSynLogInfo; const aName: RawUTF8;
     aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload; 
    // any call to this method MUST call UnLock
    function LogHeaderLock(Level: TSynLogInfo): boolean;
    procedure LogTrailerUnLock(Level: TSynLogInfo); {$ifdef HASINLINE}inline;{$endif}
    procedure LogCurrentTime;
    procedure LogFileHeader; virtual;
{$ifndef DELPHI5OROLDER}
................................................................................
    // - i.e. a TSynLog sub-class with Family.Level := []
    class function Void: TSynLogClass;
{$ifndef DELPHI5OROLDER}
    /// low-level method helper which can be called to make debugging easier
    // - log some warning message to the TSynLog family
    // - will force a manual breakpoint if tests are run from the IDE
    class procedure DebuggerNotify(Level: TSynLogInfo;
      const Format: RawUTF8; const Args: array of const);
    /// call this method to add some information to the log at the specified level
    // - % = #37 indicates a string, integer, floating-point, or class parameter
    // to be appended as text (e.g. class name)
    // - $ = #36 indicates an integer to be written with 2 digits and a comma
    // - � = #163 indicates an integer to be written with 4 digits and a comma
    // - � = #181 indicates an integer to be written with 3 digits without any comma
    // - � = #164 indicates CR+LF chars
................................................................................
    // - CR = #13 indicates CR+LF chars
    // - � = #167 indicates to trim last comma
    // - since some of this characters above are > #127, they are not UTF-8
    // ready, so we expect the input format to be WinAnsi, i.e. mostly English
    // text (with chars < #128) with some values to be inserted inside
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArgs: array of const;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one RawUTF8 parameter
    procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArg: RawUTF8;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one Int64 parameter
    procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArg: Int64;
      aInstance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at the specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first) - for instance
    // ! TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector);
................................................................................
    procedure Log(Level: TSynLogInfo; aInstance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - this overridden implementation will write the value content,
    // written as human readable JSON: handle dynamic arrays and enumerations
    // - TSQLLog from mORMot.pas unit will be able to write
    // TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; const aName: RawUTF8;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
................................................................................
  TextColor(LOGCOLORS[Level]);
  writeln(#13#10,Text,#13#10);
  TextColor(ccLightGray);
  {$endif}
end;

{$ifndef DELPHI5OROLDER}
procedure TSynLog.Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArgs: array of const;
  aInstance: TObject);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,TextArgs,aInstance);
end;

procedure TSynLog.Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArg: RawUTF8;
  aInstance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,[TextArg],aInstance);
end;

procedure TSynLog.Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArg: Int64;
  aInstance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,[TextArg],aInstance);
end;
{$endif}

................................................................................
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    if aInstance<>nil then
      LogInternal(Level,'',aInstance,maxInt) else
      LogInternal(Level,'Instance=nil',nil,maxInt);
end;

procedure TSynLog.Log(Level: TSynLogInfo; const aName: RawUTF8;
  aTypeInfo: pointer; var aValue; Instance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,aName,aTypeInfo,aValue,Instance);
end;

{$STACKFRAMES ON}
................................................................................
      SetLastError(LastError);
  end;
end;
{$STACKFRAMES OFF}

{$ifndef DELPHI5OROLDER}
class procedure TSynLog.DebuggerNotify(Level: TSynLogInfo;
  const Format: RawUTF8; const Args: array of const);
var Msg: RawUTF8;
begin
  if Format<>''then begin
    Msg := FormatUTF8(Format,Args);
    Add.LogInternal(Level,Msg,nil,maxInt);
    {$ifdef MSWINDOWS}
    OutputDebugStringA(pointer(CurrentAnsiConvert.UTF8ToAnsi(Msg)));
    {$endif}                                                      
    {$ifdef LINUX}
    //write(Msg);
    {$endif}
  end;
  {$ifndef FPC_OR_PUREPASCAL}
  if DebugHook<>0 then
    asm int 3 end; // force manual breakpoint if tests are run from the IDE
................................................................................
      PerformRotation;
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

{$ifndef DELPHI5OROLDER}
procedure TSynLog.LogInternal(Level: TSynLogInfo; const TextFmt: RawUTF8;
  const TextArgs: array of const; Instance: TObject);
var LastError: cardinal;
begin
  if Level=sllLastError then
    LastError := GetLastError else
    LastError := 0;
  if LogHeaderLock(Level) then
................................................................................
  finally
    LogTrailerUnLock(Level);
    if LastError<>0 then
      SetLastError(LastError);
  end;
end;

procedure TSynLog.LogInternal(Level: TSynLogInfo; const aName: RawUTF8;
   aTypeInfo: pointer; var aValue; Instance: TObject=nil);
begin
  if LogHeaderLock(Level) then
  try
    if Instance<>nil then
      fWriter.AddInstancePointer(Instance,' ');
    fWriter.AddString(aName);
    fWriter.Add('=');
    fWriter.AddTypedJSON(aTypeInfo,aValue);
  finally
    LogTrailerUnLock(Level);
  end;
end;

Changes to SynMongoDB.pas.

775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
...
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
....
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
....
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
....
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
....
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
....
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
....
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254

4255
4256
4257
4258
4259
4260
4261
....
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
....
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129
5130
....
5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
....
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
....
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
....
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809
5810
// - will create the BSON binary without any temporary TDocVariant storage,
// by calling JSONBufferToBSONDocument() on the generated JSON content
// - since all content will be transformed into JSON internally, use this
// method only if the supplied parameters are simple types, and identified
// explicitely via BSON-like extensions: any complex value (e.g. a TDateTime
// or a BSONVariant binary) won't be handled as expected - use the overloaded
// BSON() with explicit BSONVariant() name/value pairs instead
function BSON(Format: PUTF8Char; const Args,Params: array of const): TBSONDocument; overload;

/// store some TDocVariant custom variant content into BSON encoded binary
// - will write either a BSON object or array, depending of the internal
// layout of this TDocVariantData instance (i.e. Kind property value)
// - if supplied variant is not a TDocVariant, raise an EBSONException
function BSON(const doc: TDocVariantData): TBSONDocument; overload;

................................................................................
// instead if you do not want to modify the input buffer content
procedure BSONVariant(JSON: PUTF8Char; var result: variant); overload;

/// store some object content, supplied as (extended) JSON and parameters,
// into a TBSONVariant betDoc type instance
// - in addition to the JSON RFC specification strict mode, this method will
// handle some BSON-like extensions, as with the overloaded BSON() function
function BSONVariant(Format: PUTF8Char; const Args,Params: array of const): variant; overload;

/// convert a TDocVariant variant into a TBSONVariant betDoc type instance
function BSONVariant(doc: TDocVariantData): variant; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// store an array of integer into a TBSONVariant betArray type instance
// - object will be initialized with data supplied e.g. as a TIntegerDynArray
................................................................................
    // !   products.insert('{ item: ?, qty: ? }',[1,'card',15]);
    // !   // here the _id will be created on the client side as an ObjectID
    // - you can retrieve the client-side computed ObjectID, as such:
    // ! var oid: TBSONObjectID;
    // ! ...
    // !   products.insert('{ item: ?, qty: ? }',['card',15],@oid);
    // !   writeln(oid.ToText);
    procedure Insert(Document: PUTF8Char; const Params: array of const;
      CreatedObjectID: PBSONObjectID=nil); overload;
    /// insert one or more documents in the collection
    // - Documents is an array of TDocVariant (i.e. created via _JsonFast()
    // or _JsonFastFmt()) - or of TBSONVariant (created via BSONVariant())
    // - by default, it will follow Client.WriteConcern pattern - but you can
    // set NoAcknowledge = TRUE to avoid calling the getLastError command and
    // increase the execution speed, at the expense of a unsafe process
................................................................................
    // document with the fields from the document - and the method returns TRUE
    function Save(var Document: variant; CreatedObjectID: PBSONObjectID=nil): boolean; overload;
    /// updates an existing document or inserts a new document, depending on
    // its document parameter, supplied as (extended) JSON and parameters
    // - supplied JSON could be either strict or in MongoDB Shell syntax:
    // - will perform either an insert or an update, depending of the
    // presence of the _id field, as overloaded Save(const Document: variant)
    procedure Save(Document: PUTF8Char; const Params: array of const;
      CreatedObjectID: PBSONObjectID=nil); overload;

    /// modifies an existing document or several documents in a collection
    // - the method can modify specific fields of existing document or documents
    // or replace an existing document entirely, depending on the update parameter
    // - Query and Update parameters should be TDocVariant (i.e. created via
    // _JsonFast() or _JsonFastFmt()) or TBSONVariant (created via BSONVariant())
................................................................................
    // - if Update contains update operators (like $set), it will update the
    // corresponding fields in the document:
    // ! book.insert('{_id:?,item:?,stock:?}',[11,'Divine Comedy',2]);
    // ! book.update('{item:?},['Divine Comedy'],'{$set:{price:?},$inc:{stock:?}},[18,5]);
    // ! // the updated document is now:
    // ! { "_id" : 11, "item" : "Divine Comedy", "price" : 18, "stock" : 7 }
    procedure Update(Query: PUTF8Char; const QueryParams: array of const;
      Update: PUTF8Char; const UpdateParams: array of const;
      Flags: TMongoUpdateFlags=[]); overload;
    /// modifies some fields of an existing document in a collection
    // - by default, Update() or Save() will replace the whole document
    // - this method will expect the identifier to be supplied as a variant -
    // may be via the ObjectID() function
    // - and will replace the specified fields, i.e. it will execute a $set:
    // with the supplied UpdatedFields value
................................................................................
  EMongoConnectionException = class(EMongoException)
  protected
    fConnection: TMongoConnection;
  public
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor CreateUTF8(Format: PUTF8Char; const Args: array of const;
      aConnection: TMongoConnection); reintroduce;
  published
    /// the associated connection
    property Connection: TMongoConnection read fConnection;
  end;

  EMongoDatabaseException = class(EMongoConnectionException)
  protected
    fDatabase: TMongoDatabase;
  public
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aDatabase: TMongoDatabase); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor CreateUTF8(Format: PUTF8Char; const Args: array of const;
      aDatabase: TMongoDatabase); reintroduce;
    {$ifndef NOEXCEPTIONINTERCEPT}
    /// used to customize the exception log to contain information about the Query
    // - it will log the database parameters
    function CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; override;
    {$endif}
  published
................................................................................
    fErrorDoc: variant;
    function GetErrorDoc: variant;
  public
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection;
      aRequest: TMongoRequest=nil); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor CreateUTF8(Format: PUTF8Char; const Args: array of const;
      aConnection: TMongoConnection; aRequest: TMongoRequest); reintroduce;
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection;
      aRequest: TMongoRequest; const aError: TMongoReplyCursor); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection;
      aRequest: TMongoRequest; const aErrorDoc: TDocVariantData); reintroduce; overload;
................................................................................
  finally
    W.Free;
  end;
  SetLength(docs,n);
  result := true;
end;

function BSON(Format: PUTF8Char; const Args,Params: array of const): TBSONDocument;
var JSON: RawUTF8;
    v: variant;
begin
  if (Format<>nil) and (PWord(Format)^=ord('?')) and (high(Params)>=0) then begin
    VarRecToVariant(Params[0],v);
    if DocVariantType.IsOfType(v) then begin
      result := BSON(TDocVariantData(v));
      exit;
    end;
  end;
  JSON := FormatUTF8(Format,Args,Params,true);

  JSONBufferToBSONDocument(pointer(JSON),result);
end;

function BSON(const JSON: RawUTF8): TBSONDocument;
var tmp: RawUTF8; // make a private copy
begin
  SetString(tmp,PAnsiChar(pointer(JSON)),length(JSON));
................................................................................
procedure BSONVariant(JSON: PUTF8Char; var result: variant);
var tmp: TBSONDocument;
begin
  JSONBufferToBSONDocument(JSON,tmp);
  BSONVariantType.FromBSONDocument(tmp,result);
end;

function BSONVariant(Format: PUTF8Char; const Args,Params: array of const): variant; overload;
begin
  BSONVariantType.FromBSONDocument(BSON(Format,Args,Params),result);
end;

function BSONVariant(doc: TDocVariantData): variant; overload;
begin
  BSONVariantType.FromBSONDocument(BSON(Doc),result);
................................................................................
constructor EMongoConnectionException.Create(const aMsg: string;
  aConnection: TMongoConnection);
begin
  inherited Create(aMsg);
  fConnection := aConnection;
end;

constructor EMongoConnectionException.CreateUTF8(Format: PUTF8Char; const Args: array of const;
  aConnection: TMongoConnection);
begin
  inherited CreateUTF8(Format,Args);
  fConnection := aConnection;
end;


................................................................................
constructor EMongoRequestException.Create(const aMsg: string;
  aConnection: TMongoConnection; aRequest: TMongoRequest);
begin
  inherited Create(aMsg,aConnection);
  fRequest := aRequest;
end;

constructor EMongoRequestException.CreateUTF8(Format: PUTF8Char;
  const Args: array of const; aConnection: TMongoConnection;
  aRequest: TMongoRequest);
begin
  inherited CreateUTF8(Format,Args,aConnection);
  fRequest := aRequest;
end;

................................................................................
constructor EMongoDatabaseException.Create(const aMsg: string;
  aDatabase: TMongoDatabase);
begin
  inherited Create(aMsg,aDatabase.Client.Connections[0]);
  fDatabase := aDatabase;
end;

constructor EMongoDatabaseException.CreateUTF8(Format: PUTF8Char;
  const Args: array of const; aDatabase: TMongoDatabase);
begin
  inherited CreateUTF8(Format,Args,aDatabase.Client.Connections[0]);
  fDatabase := aDatabase;
end;

{$ifndef NOEXCEPTIONINTERCEPT}
................................................................................
    oid := doc.Values[ndx];
    result := false;
  end;
  if CreatedObjectID<>nil then
    CreatedObjectID^.FromVariant(oid)
end;

procedure TMongoCollection.Insert(Document: PUTF8Char;
  const Params: array of const; CreatedObjectID: PBSONObjectID);
var doc: variant;
    oid: variant;
begin
  doc := _JsonFastFmt(Document,[],Params);
  EnsureDocumentHasID(TDocVariantData(doc),oid,CreatedObjectID);
  Insert([doc]);
................................................................................
    Document := _JsonFast(VariantSaveMongoJSON(Document,modMongoShell));
  result := EnsureDocumentHasID(TDocVariantData(Document),oid,CreatedObjectID);
  if result then
    Insert([Document]) else
    Update(BSONVariant(['_id',oid]),Document,[mufUpsert])
end;

procedure TMongoCollection.Save(Document: PUTF8Char;
  const Params: array of const; CreatedObjectID: PBSONObjectID);
var doc: variant;
begin
  doc := _JsonFastFmt(Document,[],Params);
  Save(doc,CreatedObjectID);
end;

procedure TMongoCollection.Update(Query: PUTF8Char;
  const QueryParams: array of const; Update: PUTF8Char;
  const UpdateParams: array of const; Flags: TMongoUpdateFlags);
var quer,upd: variant;
begin
  quer := BSONVariant(Query,[],QueryParams);
  upd := BSONVariant(Update,[],UpdateParams);
  self.Update(quer,upd,Flags);
end;






|







 







|







 







|







 







|







 







|







 







|













|







 







|







 







|



|







>







 







|







 







|







 







|







 







|







 







|







 







|








|







775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
...
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
....
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
....
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
....
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
....
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
....
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
....
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
....
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
....
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129
5130
5131
....
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
....
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
....
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
....
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809
5810
5811
// - will create the BSON binary without any temporary TDocVariant storage,
// by calling JSONBufferToBSONDocument() on the generated JSON content
// - since all content will be transformed into JSON internally, use this
// method only if the supplied parameters are simple types, and identified
// explicitely via BSON-like extensions: any complex value (e.g. a TDateTime
// or a BSONVariant binary) won't be handled as expected - use the overloaded
// BSON() with explicit BSONVariant() name/value pairs instead
function BSON(const Format: RawUTF8; const Args,Params: array of const): TBSONDocument; overload;

/// store some TDocVariant custom variant content into BSON encoded binary
// - will write either a BSON object or array, depending of the internal
// layout of this TDocVariantData instance (i.e. Kind property value)
// - if supplied variant is not a TDocVariant, raise an EBSONException
function BSON(const doc: TDocVariantData): TBSONDocument; overload;

................................................................................
// instead if you do not want to modify the input buffer content
procedure BSONVariant(JSON: PUTF8Char; var result: variant); overload;

/// store some object content, supplied as (extended) JSON and parameters,
// into a TBSONVariant betDoc type instance
// - in addition to the JSON RFC specification strict mode, this method will
// handle some BSON-like extensions, as with the overloaded BSON() function
function BSONVariant(const Format: RawUTF8; const Args,Params: array of const): variant; overload;

/// convert a TDocVariant variant into a TBSONVariant betDoc type instance
function BSONVariant(doc: TDocVariantData): variant; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// store an array of integer into a TBSONVariant betArray type instance
// - object will be initialized with data supplied e.g. as a TIntegerDynArray
................................................................................
    // !   products.insert('{ item: ?, qty: ? }',[1,'card',15]);
    // !   // here the _id will be created on the client side as an ObjectID
    // - you can retrieve the client-side computed ObjectID, as such:
    // ! var oid: TBSONObjectID;
    // ! ...
    // !   products.insert('{ item: ?, qty: ? }',['card',15],@oid);
    // !   writeln(oid.ToText);
    procedure Insert(const Document: RawUTF8; const Params: array of const;
      CreatedObjectID: PBSONObjectID=nil); overload;
    /// insert one or more documents in the collection
    // - Documents is an array of TDocVariant (i.e. created via _JsonFast()
    // or _JsonFastFmt()) - or of TBSONVariant (created via BSONVariant())
    // - by default, it will follow Client.WriteConcern pattern - but you can
    // set NoAcknowledge = TRUE to avoid calling the getLastError command and
    // increase the execution speed, at the expense of a unsafe process
................................................................................
    // document with the fields from the document - and the method returns TRUE
    function Save(var Document: variant; CreatedObjectID: PBSONObjectID=nil): boolean; overload;
    /// updates an existing document or inserts a new document, depending on
    // its document parameter, supplied as (extended) JSON and parameters
    // - supplied JSON could be either strict or in MongoDB Shell syntax:
    // - will perform either an insert or an update, depending of the
    // presence of the _id field, as overloaded Save(const Document: variant)
    procedure Save(const Document: RawUTF8; const Params: array of const;
      CreatedObjectID: PBSONObjectID=nil); overload;

    /// modifies an existing document or several documents in a collection
    // - the method can modify specific fields of existing document or documents
    // or replace an existing document entirely, depending on the update parameter
    // - Query and Update parameters should be TDocVariant (i.e. created via
    // _JsonFast() or _JsonFastFmt()) or TBSONVariant (created via BSONVariant())
................................................................................
    // - if Update contains update operators (like $set), it will update the
    // corresponding fields in the document:
    // ! book.insert('{_id:?,item:?,stock:?}',[11,'Divine Comedy',2]);
    // ! book.update('{item:?},['Divine Comedy'],'{$set:{price:?},$inc:{stock:?}},[18,5]);
    // ! // the updated document is now:
    // ! { "_id" : 11, "item" : "Divine Comedy", "price" : 18, "stock" : 7 }
    procedure Update(Query: PUTF8Char; const QueryParams: array of const;
      const Update: RawUTF8; const UpdateParams: array of const;
      Flags: TMongoUpdateFlags=[]); overload;
    /// modifies some fields of an existing document in a collection
    // - by default, Update() or Save() will replace the whole document
    // - this method will expect the identifier to be supplied as a variant -
    // may be via the ObjectID() function
    // - and will replace the specified fields, i.e. it will execute a $set:
    // with the supplied UpdatedFields value
................................................................................
  EMongoConnectionException = class(EMongoException)
  protected
    fConnection: TMongoConnection;
  public
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor CreateUTF8(const Format: RawUTF8; const Args: array of const;
      aConnection: TMongoConnection); reintroduce;
  published
    /// the associated connection
    property Connection: TMongoConnection read fConnection;
  end;

  EMongoDatabaseException = class(EMongoConnectionException)
  protected
    fDatabase: TMongoDatabase;
  public
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aDatabase: TMongoDatabase); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor CreateUTF8(const Format: RawUTF8; const Args: array of const;
      aDatabase: TMongoDatabase); reintroduce;
    {$ifndef NOEXCEPTIONINTERCEPT}
    /// used to customize the exception log to contain information about the Query
    // - it will log the database parameters
    function CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; override;
    {$endif}
  published
................................................................................
    fErrorDoc: variant;
    function GetErrorDoc: variant;
  public
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection;
      aRequest: TMongoRequest=nil); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor CreateUTF8(const Format: RawUTF8; const Args: array of const;
      aConnection: TMongoConnection; aRequest: TMongoRequest); reintroduce;
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection;
      aRequest: TMongoRequest; const aError: TMongoReplyCursor); reintroduce; overload;
    /// initialize the Exception for a given request
    constructor Create(const aMsg: string; aConnection: TMongoConnection;
      aRequest: TMongoRequest; const aErrorDoc: TDocVariantData); reintroduce; overload;
................................................................................
  finally
    W.Free;
  end;
  SetLength(docs,n);
  result := true;
end;

function BSON(const Format: RawUTF8; const Args,Params: array of const): TBSONDocument;
var JSON: RawUTF8;
    v: variant;
begin
  if (Format='?') and (high(Params)>=0) then begin
    VarRecToVariant(Params[0],v);
    if DocVariantType.IsOfType(v) then begin
      result := BSON(TDocVariantData(v));
      exit;
    end;
  end;
  JSON := FormatUTF8(Format,Args,Params,true);
  UniqueRawUTF8(JSON);
  JSONBufferToBSONDocument(pointer(JSON),result);
end;

function BSON(const JSON: RawUTF8): TBSONDocument;
var tmp: RawUTF8; // make a private copy
begin
  SetString(tmp,PAnsiChar(pointer(JSON)),length(JSON));
................................................................................
procedure BSONVariant(JSON: PUTF8Char; var result: variant);
var tmp: TBSONDocument;
begin
  JSONBufferToBSONDocument(JSON,tmp);
  BSONVariantType.FromBSONDocument(tmp,result);
end;

function BSONVariant(const Format: RawUTF8; const Args,Params: array of const): variant; overload;
begin
  BSONVariantType.FromBSONDocument(BSON(Format,Args,Params),result);
end;

function BSONVariant(doc: TDocVariantData): variant; overload;
begin
  BSONVariantType.FromBSONDocument(BSON(Doc),result);
................................................................................
constructor EMongoConnectionException.Create(const aMsg: string;
  aConnection: TMongoConnection);
begin
  inherited Create(aMsg);
  fConnection := aConnection;
end;

constructor EMongoConnectionException.CreateUTF8(const Format: RawUTF8; const Args: array of const;
  aConnection: TMongoConnection);
begin
  inherited CreateUTF8(Format,Args);
  fConnection := aConnection;
end;


................................................................................
constructor EMongoRequestException.Create(const aMsg: string;
  aConnection: TMongoConnection; aRequest: TMongoRequest);
begin
  inherited Create(aMsg,aConnection);
  fRequest := aRequest;
end;

constructor EMongoRequestException.CreateUTF8(const Format: RawUTF8;
  const Args: array of const; aConnection: TMongoConnection;
  aRequest: TMongoRequest);
begin
  inherited CreateUTF8(Format,Args,aConnection);
  fRequest := aRequest;
end;

................................................................................
constructor EMongoDatabaseException.Create(const aMsg: string;
  aDatabase: TMongoDatabase);
begin
  inherited Create(aMsg,aDatabase.Client.Connections[0]);
  fDatabase := aDatabase;
end;

constructor EMongoDatabaseException.CreateUTF8(const Format: RawUTF8;
  const Args: array of const; aDatabase: TMongoDatabase);
begin
  inherited CreateUTF8(Format,Args,aDatabase.Client.Connections[0]);
  fDatabase := aDatabase;
end;

{$ifndef NOEXCEPTIONINTERCEPT}
................................................................................
    oid := doc.Values[ndx];
    result := false;
  end;
  if CreatedObjectID<>nil then
    CreatedObjectID^.FromVariant(oid)
end;

procedure TMongoCollection.Insert(const Document: RawUTF8;
  const Params: array of const; CreatedObjectID: PBSONObjectID);
var doc: variant;
    oid: variant;
begin
  doc := _JsonFastFmt(Document,[],Params);
  EnsureDocumentHasID(TDocVariantData(doc),oid,CreatedObjectID);
  Insert([doc]);
................................................................................
    Document := _JsonFast(VariantSaveMongoJSON(Document,modMongoShell));
  result := EnsureDocumentHasID(TDocVariantData(Document),oid,CreatedObjectID);
  if result then
    Insert([Document]) else
    Update(BSONVariant(['_id',oid]),Document,[mufUpsert])
end;

procedure TMongoCollection.Save(const Document: RawUTF8;
  const Params: array of const; CreatedObjectID: PBSONObjectID);
var doc: variant;
begin
  doc := _JsonFastFmt(Document,[],Params);
  Save(doc,CreatedObjectID);
end;

procedure TMongoCollection.Update(Query: PUTF8Char;
  const QueryParams: array of const; const Update: RawUTF8;
  const UpdateParams: array of const; Flags: TMongoUpdateFlags);
var quer,upd: variant;
begin
  quer := BSONVariant(Query,[],QueryParams);
  upd := BSONVariant(Update,[],UpdateParams);
  self.Update(quer,upd,Flags);
end;

Changes to SynSelfTests.pas.

2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
....
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
....
8571
8572
8573
8574
8575
8576
8577
8578
8579
8580
8581
8582
8583
8584
8585
    if U='' then
      continue;
    Check(UnQuoteSQLStringVar(pointer(QuotedStr(U,'"')),res)<>nil);
    Check(res=U);
    Check(not IsZero(pointer(W),length(W)));
    fillchar(pointer(W)^,length(W),0);
    Check(IsZero(pointer(W),length(W)));
    Check(FormatUTF8(pointer(U),[])=U);
{$ifndef DELPHI5OROLDER}
    res := FormatUTF8(pointer(U),[],[]); // Delphi 5 bug with high([])>0 :( 
    Check(length(res)=Length(u));
    Check(res=u);
    Check(FormatUTF8('%',[U])=U);
    Check(FormatUTF8('%',[U],[])=U);
    q := ':('+QuotedStr(U)+'):';
    Check(FormatUTF8('?',[],[U])=q);
    res := 'ab'+U;
................................................................................
  u := VariantSaveJSON(o.Field);
  u2 := VariantSaveJSON(o2.Field);
  Check(u=u2);
  Check(u=REGEX);
  o := _Json('{ tags: { $in: [ /^be/, /^st/ ] } }');
  u := VariantSaveMongoJSON(o,modMongoStrict);
  Check(u='{"tags":{"$in":[{"$regex":"^be","$options":""},{"$regex":"^st","$options":""}]}}');
  b := pointer(BSON(Pointer(u),[],[]));
  u2 := VariantSaveMongoJSON(o,modMongoShell);
  Check(u2='{tags:{$in:[/^be/,/^st/]}}');
  u := VariantSaveMongoJSON(_Json(u),modMongoShell);
  Check(u=u2);
  u2 := BSONToJSON(b,betDoc,0,modMongoShell);
  Check(u=u2);
  b := pointer(BSON('{id:ObjectId(),doc:{name:?,date:ISODate(?)}}',[],['John',NowUTC]));
................................................................................
        Check(MD2.FillOne);
        Check(MD2.ID=dID[i]);
        Check(MD2.Signature=FormatUTF8('% %',[aClient.ClassName,i]));
      finally
        MD2.Free;
      end;
    end;
    Check(MS.FillPrepareMany(aClient,nil,[],[]));
    CheckOK;
    Check(MS.FillPrepareMany(aClient,'DestList.Dest.SignatureTime<>?',[],[0]));
    CheckOK;
    Check(MS.FillPrepareMany(aClient,
      'DestList.Dest.SignatureTime<>% and RowID>=? and DestList.AssociationTime<>0 '+
      'and SignatureTime=DestList.Dest.SignatureTime '+
      'and DestList.Dest.Signature<>"DestList.AssociationTime"',[0],[sID[1]]));






|

|







 







|







 







|







2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
....
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
....
8571
8572
8573
8574
8575
8576
8577
8578
8579
8580
8581
8582
8583
8584
8585
    if U='' then
      continue;
    Check(UnQuoteSQLStringVar(pointer(QuotedStr(U,'"')),res)<>nil);
    Check(res=U);
    Check(not IsZero(pointer(W),length(W)));
    fillchar(pointer(W)^,length(W),0);
    Check(IsZero(pointer(W),length(W)));
    Check(FormatUTF8(U,[])=U);
{$ifndef DELPHI5OROLDER}
    res := FormatUTF8(U,[],[]); // Delphi 5 bug with high([])>0 :( 
    Check(length(res)=Length(u));
    Check(res=u);
    Check(FormatUTF8('%',[U])=U);
    Check(FormatUTF8('%',[U],[])=U);
    q := ':('+QuotedStr(U)+'):';
    Check(FormatUTF8('?',[],[U])=q);
    res := 'ab'+U;
................................................................................
  u := VariantSaveJSON(o.Field);
  u2 := VariantSaveJSON(o2.Field);
  Check(u=u2);
  Check(u=REGEX);
  o := _Json('{ tags: { $in: [ /^be/, /^st/ ] } }');
  u := VariantSaveMongoJSON(o,modMongoStrict);
  Check(u='{"tags":{"$in":[{"$regex":"^be","$options":""},{"$regex":"^st","$options":""}]}}');
  b := pointer(BSON(u,[],[]));
  u2 := VariantSaveMongoJSON(o,modMongoShell);
  Check(u2='{tags:{$in:[/^be/,/^st/]}}');
  u := VariantSaveMongoJSON(_Json(u),modMongoShell);
  Check(u=u2);
  u2 := BSONToJSON(b,betDoc,0,modMongoShell);
  Check(u=u2);
  b := pointer(BSON('{id:ObjectId(),doc:{name:?,date:ISODate(?)}}',[],['John',NowUTC]));
................................................................................
        Check(MD2.FillOne);
        Check(MD2.ID=dID[i]);
        Check(MD2.Signature=FormatUTF8('% %',[aClient.ClassName,i]));
      finally
        MD2.Free;
      end;
    end;
    Check(MS.FillPrepareMany(aClient,'', [],[]));
    CheckOK;
    Check(MS.FillPrepareMany(aClient,'DestList.Dest.SignatureTime<>?',[],[0]));
    CheckOK;
    Check(MS.FillPrepareMany(aClient,
      'DestList.Dest.SignatureTime<>% and RowID>=? and DestList.AssociationTime<>0 '+
      'and SignatureTime=DestList.Dest.SignatureTime '+
      'and DestList.Dest.Signature<>"DestList.AssociationTime"',[0],[sID[1]]));

Changes to SynTests.pas.

706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
....
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
    WR.Free;
  end;
end;

procedure TSynTestCase.TestFailed(const msg: string);
begin
  {$ifndef DELPHI5OROLDER}
  TSynLogTestLog.DebuggerNotify(sllFail,[fAssertions-fAssertionsBeforeRun,msg],'#% %');
  {$endif}
  if Owner<>nil then // avoid GPF
    Owner.Failed(msg,self);
  Inc(fAssertionsFailed);
end;

procedure TSynTestCase.NotifyTestSpeed(const ItemName: string;
................................................................................
    fConsoleDup.Add('"','}');
    fLogFile.Log(sllCustom1,fConsoleDup.Text);
  end;
  inherited Destroy;
  fConsoleDup.Free;
end;

const
 sFailed: PWinAnsiChar = '%: % "%"';

procedure TSynTestsLogged.Failed(const msg: string; aTest: TSynTestCase);
{$ifdef DELPHI5OROLDER}
var tmp: RawUTF8;
{$endif}
begin
  inherited;
  with TestCase[fCurrentMethod] do begin
    {$ifdef DELPHI5OROLDER}
    tmp := Ident+': '+TestName[fCurrentMethodIndex];
    if msg<>'' then
      tmp := tmp+' "'+msg+'"';
    fLogFile.Log(sllFail,tmp);
    {$else}
    fLogFile.Log(sllFail,sFailed,[Ident,TestName[fCurrentMethodIndex],msg],aTest);
    {$endif}
    {$ifdef KYLIX3}
    fLogFile.Flush(true);
    // we do not have a debugger for CrossKylix -> stop here!
    TextColor(ccLightRed);
    writeln('!!! ',Ident,' - ',TestName[fCurrentMethodIndex],' "',msg,'" failed !!!');
    write('Press [Enter] to continue, or Ctrl+C to abort ');






|







 







<
<
<













|







706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
....
1100
1101
1102
1103
1104
1105
1106



1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
    WR.Free;
  end;
end;

procedure TSynTestCase.TestFailed(const msg: string);
begin
  {$ifndef DELPHI5OROLDER}
  TSynLogTestLog.DebuggerNotify(sllFail,'#% %',[fAssertions-fAssertionsBeforeRun,msg]);
  {$endif}
  if Owner<>nil then // avoid GPF
    Owner.Failed(msg,self);
  Inc(fAssertionsFailed);
end;

procedure TSynTestCase.NotifyTestSpeed(const ItemName: string;
................................................................................
    fConsoleDup.Add('"','}');
    fLogFile.Log(sllCustom1,fConsoleDup.Text);
  end;
  inherited Destroy;
  fConsoleDup.Free;
end;




procedure TSynTestsLogged.Failed(const msg: string; aTest: TSynTestCase);
{$ifdef DELPHI5OROLDER}
var tmp: RawUTF8;
{$endif}
begin
  inherited;
  with TestCase[fCurrentMethod] do begin
    {$ifdef DELPHI5OROLDER}
    tmp := Ident+': '+TestName[fCurrentMethodIndex];
    if msg<>'' then
      tmp := tmp+' "'+msg+'"';
    fLogFile.Log(sllFail,tmp);
    {$else}
    fLogFile.Log(sllFail,'%: % "%"',[Ident,TestName[fCurrentMethodIndex],msg],aTest);
    {$endif}
    {$ifdef KYLIX3}
    fLogFile.Flush(true);
    // we do not have a debugger for CrossKylix -> stop here!
    TextColor(ccLightRed);
    writeln('!!! ',Ident,' - ',TestName[fCurrentMethodIndex],' "',msg,'" failed !!!');
    write('Press [Enter] to continue, or Ctrl+C to abort ');

Changes to SynopseCommit.inc.

1
'1.18.897'
|
1
'1.18.898'