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

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

Overview
Comment:
  • added dedicated Exception classes (ESynException, ETableDataException) in SynCommons
  • added dedicated Exception classes (EORMException, EParsingException, ESecurityException, ECommunicationException, EBusinessLayerException, EServiceException) all inheriting from SynCommons.ESynException
  • renamed ESQLException into ESQLite3Exception
  • fixed potential GPF issue in Hash32() function
  • Client-side implementation of remote Interface access now is able to create "fake" classes in order to emulate native interfaces - the stubbing code (i.e. asm generated on the fly) is IMHO simple and great: it can be proudly compared to Embarcadero's Rio or Rtti unreadable units :)
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: be458e5fb558b965ce80d15a7d02f6260996ef32
User & Date: User 2012-02-26 00:26:43
Context
2012-02-26
00:38
small fixes check-in: 7fc613f8b2 user: User tags: trunk
00:26
  • added dedicated Exception classes (ESynException, ETableDataException) in SynCommons
  • added dedicated Exception classes (EORMException, EParsingException, ESecurityException, ECommunicationException, EBusinessLayerException, EServiceException) all inheriting from SynCommons.ESynException
  • renamed ESQLException into ESQLite3Exception
  • fixed potential GPF issue in Hash32() function
  • Client-side implementation of remote Interface access now is able to create "fake" classes in order to emulate native interfaces - the stubbing code (i.e. asm generated on the fly) is IMHO simple and great: it can be proudly compared to Embarcadero's Rio or Rtti unreadable units :)
check-in: be458e5fb5 user: User tags: trunk
2012-02-24
13:08
server-side implementation of the interface-based services completed - simple provided regression tests passed with success - beware: some nice code within in order to call an interface from and to JSON encoded remote requests! check-in: 6b39ccefe8 user: G018869 tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/SQLite3.pas.

211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
...
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
...
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
...
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
...
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
....
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
....
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
....
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
....
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
....
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
....
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
  SQLite3Commons;

{$define INCLUDE_FTS3}
{ define this if you want to include the FTS3/FTS4 feature into the library
  - FTS3 is an SQLite module implementing full-text search
  - will include also FTS4 extension module since 3.7.4
  - see http://www.sqlite.org/fts3.html for documentation
  - not defined by default, to save about 50 KB of code size
  - should be defined for both SynSQLite3 and SQLite3 units }

{.$define USEFASTCALL}
{ use the fastcall calling convention to access the SQLite3 library
  - BCC32 -pr fastcall (=Delphi resgister) is buggy, don't know why
   (because of issues with BCC32 itself, or some obfuscated calls in source?)
  - should be defined for both SynSQLite3 and SQLite3 units }
................................................................................
procedure TSQLRestServerDB.CreateMissingTables(user_version: cardinal);
var t,f: integer;
    TableNamesAtCreation, aFields: TRawUTF8DynArray;
    TableJustCreated: TSQLFieldTables;
    aSQL: RawUTF8;
begin
  if DB.TransactionActive then
    raise ESQLException.Create('CreateMissingTables: Transaction');
  fDB.GetTableNames(TableNamesAtCreation);
  fillchar(TableJustCreated,sizeof(TSQLFieldTables),0);
  try
    // create not static and not existing tables
    for t := 0 to high(Model.Tables) do
      if ((fStaticData=nil) or (fStaticData[t]=nil)) then
      // this table is not static -> check if already existing, create if necessary
................................................................................
        if Req=@fStaticStatement then
          Close;
      end;
    finally
      DB.UnLock;
    end;
  except
    on E: ESQLException do begin
      {$ifdef WITHLOG}
      DB.Log.Log(sllError,'% for %',[E,aSQL],self);
      {$else}
      LogToTextFile('TSQLRestServerDB.EngineExecute: '+RawUTF8(E.Message)+#13#10+aSQL);
      {$endif}
      result := false;
    end;
................................................................................
      finally
        R.Close; // always release statement
      end;
    finally
      DB.UnLock;
    end;
  except
    on E: ESQLException do begin
      {$ifdef WITHLOG}
      DB.Log.Log(sllError,'% for %',[E,aSQL],self);
      {$else}
      LogToTextFile('TSQLRestServerDB.EngineExecute Error: '+RawUTF8(E.Message)+#13#10+aSQL);
      {$endif}
      result := false;
    end;
................................................................................

function TSQLRestServerDB.EngineExecuteAll(const aSQL: RawUTF8): boolean;
begin
  try
    DB.ExecuteAll(aSQL); // Execute all statements (don't use fStatementCache[])
    result := true;
  except
    on E: ESQLException do begin
      {$ifdef WITHLOG}
      DB.Log.Log(sllError,'% for %',[E,aSQL],self);
      {$else}
      LogToTextFile('TSQLRestServerDB.EngineExecuteAll Error: '+RawUTF8(E.Message)+#13#10+aSQL);
      {$endif}
      result := false;
    end;
................................................................................
                Req^.Close;
            end;
          finally
            MS.Free;
          end;
        end;
      except
        on ESQLException do
          result := '';
      end;
    finally
      DB.UnLockJSON(result,RowCount);
    end;
  end;
  if ReturnedRowCount<>nil then
................................................................................
        if Req=@fStaticStatement then
          Close;
      end;
    finally
      DB.UnLock;
    end;
  except
    on ESQLException do
      result := false;
  end;
end;

procedure TSQLRestServerDB.SetNoAJAXJSON(const Value: boolean);
begin
  inherited;
................................................................................
        until Step<>SQLITE_ROW; // Execute all steps of the first statement
        result := true;
      end;
    finally
      DB.UnLock;
    end;
  except
    on ESQLException do
      result := false;
  end;
end;

function TSQLRestServerDB.UpdateField(Table: TSQLRecordClass; Where: integer;
  const FieldName: shortstring; FieldValue: integer; ByID: boolean): boolean;
var Field: PPropInfo;
................................................................................

procedure TSQLRestServerDB.Commit(SessionID: cardinal=1);
begin
  inherited Commit(SessionID); // reset fTransactionActive + write all TSQLVirtualTableJSON
  try
    DB.Commit;
  except
    on ESQLException do
      ; // just catch exception
  end;
end;

procedure TSQLRestServerDB.RollBack(SessionID: cardinal=1);
begin
  inherited; // reset TSQLRestServerDB.fTransactionActive flag
  try
    DB.RollBack; // reset TSQLDataBase.RollBack
  except
    on ESQLException do
      ; // just catch exception
  end;
end;

function TSQLRestServerDB.TransactionBegin(aTable: TSQLRecordClass; SessionID: cardinal=1): boolean;
begin
  result := inherited TransactionBegin(aTable,SessionID);
  if result then
    // fTransactionActive flag was not already set
    try
      DB.TransactionBegin;
    except
      on ESQLException do
        result := false;
    end;
end;

function TSQLRestServerDB.Backup(Dest: TStream): boolean;
var Source: TFileStream;
    Closed: boolean;
................................................................................
      result := fServer.InternalListJSON(TSQLRecordClass(Tables[0]),aSQL) else
      // we access localy the DB -> TSQLTableDB handle Tables parameter
      result := TSQLTableDB.Create(fServer.DB,
        RecordClassesToClasses(Tables),aSQL,not fServer.NoAJAXJSON);
    if fServer.DB.InternalState<>nil then
      result.InternalState := fServer.DB.InternalState^;
  except
    on ESQLException do
      result := nil;
  end;
end;

function TSQLRestClientDB.InternalURI(const url, method: RawUTF8;
  Resp, Head, SendData: PRawUTF8): Int64Rec;
var R,H,S: RawUTF8; // temp '' string to be used when no PString is provided
................................................................................

{ TSQLVirtualTableModuleServerDB }

constructor TSQLVirtualTableModuleServerDB.Create(
  aClass: TSQLVirtualTableClass; aServer: TSQLRestServer);
begin
  if not aServer.InheritsFrom(TSQLRestServerDB) then
    raise Exception.CreateFmt('%.Create expects a DB Server',[ClassName]);
  inherited Create(aClass,aServer);
  DB := TSQLRestServerDB(aServer).DB; // SetDB setter will do the work
end;


{ ************ Unit-Testing classes and functions }

................................................................................
  VO := TSQLRecordPeopleObject.Create;
{$endif}
  V2 := nil;
  if not IsMemory then begin
    DeleteFile('dali1.json');
    DeleteFile('dali2.data');
  end;
  Demo.RegisterSQLFunction(TypeInfo(TIntegerDynArray),SortDynArrayInteger,
    'MyIntegerDynArrayContains');
  ModelC := TSQLModel.Create(
    [TSQLRecordPeople, {$ifdef INCLUDE_FTS3} TSQLFTSTest, {$endif}
     TSQLASource, TSQLADest, TSQLADests, TSQLRecordPeopleArray
     {$ifndef LVCL}, TSQLRecordPeopleObject{$endif},
     TSQLRecordDali1,TSQLRecordDali2],'root');
  ModelC.VirtualTableRegister(TSQLRecordDali1,TSQLVirtualTableJSON);






|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|










|












|







 







|







 







|







 







|







211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
...
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
...
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
...
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
...
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
....
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
....
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
....
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
....
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
....
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
....
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
  SQLite3Commons;

{$define INCLUDE_FTS3}
{ define this if you want to include the FTS3/FTS4 feature into the library
  - FTS3 is an SQLite module implementing full-text search
  - will include also FTS4 extension module since 3.7.4
  - see http://www.sqlite.org/fts3.html for documentation
  - is defined by default, but can be unset to save about 50 KB of code size
  - should be defined for both SynSQLite3 and SQLite3 units }

{.$define USEFASTCALL}
{ use the fastcall calling convention to access the SQLite3 library
  - BCC32 -pr fastcall (=Delphi resgister) is buggy, don't know why
   (because of issues with BCC32 itself, or some obfuscated calls in source?)
  - should be defined for both SynSQLite3 and SQLite3 units }
................................................................................
procedure TSQLRestServerDB.CreateMissingTables(user_version: cardinal);
var t,f: integer;
    TableNamesAtCreation, aFields: TRawUTF8DynArray;
    TableJustCreated: TSQLFieldTables;
    aSQL: RawUTF8;
begin
  if DB.TransactionActive then
    raise EBusinessLayerException.Create('CreateMissingTables: Transaction');
  fDB.GetTableNames(TableNamesAtCreation);
  fillchar(TableJustCreated,sizeof(TSQLFieldTables),0);
  try
    // create not static and not existing tables
    for t := 0 to high(Model.Tables) do
      if ((fStaticData=nil) or (fStaticData[t]=nil)) then
      // this table is not static -> check if already existing, create if necessary
................................................................................
        if Req=@fStaticStatement then
          Close;
      end;
    finally
      DB.UnLock;
    end;
  except
    on E: ESQLite3Exception do begin
      {$ifdef WITHLOG}
      DB.Log.Log(sllError,'% for %',[E,aSQL],self);
      {$else}
      LogToTextFile('TSQLRestServerDB.EngineExecute: '+RawUTF8(E.Message)+#13#10+aSQL);
      {$endif}
      result := false;
    end;
................................................................................
      finally
        R.Close; // always release statement
      end;
    finally
      DB.UnLock;
    end;
  except
    on E: ESQLite3Exception do begin
      {$ifdef WITHLOG}
      DB.Log.Log(sllError,'% for %',[E,aSQL],self);
      {$else}
      LogToTextFile('TSQLRestServerDB.EngineExecute Error: '+RawUTF8(E.Message)+#13#10+aSQL);
      {$endif}
      result := false;
    end;
................................................................................

function TSQLRestServerDB.EngineExecuteAll(const aSQL: RawUTF8): boolean;
begin
  try
    DB.ExecuteAll(aSQL); // Execute all statements (don't use fStatementCache[])
    result := true;
  except
    on E: ESQLite3Exception do begin
      {$ifdef WITHLOG}
      DB.Log.Log(sllError,'% for %',[E,aSQL],self);
      {$else}
      LogToTextFile('TSQLRestServerDB.EngineExecuteAll Error: '+RawUTF8(E.Message)+#13#10+aSQL);
      {$endif}
      result := false;
    end;
................................................................................
                Req^.Close;
            end;
          finally
            MS.Free;
          end;
        end;
      except
        on ESQLite3Exception do
          result := '';
      end;
    finally
      DB.UnLockJSON(result,RowCount);
    end;
  end;
  if ReturnedRowCount<>nil then
................................................................................
        if Req=@fStaticStatement then
          Close;
      end;
    finally
      DB.UnLock;
    end;
  except
    on ESQLite3Exception do
      result := false;
  end;
end;

procedure TSQLRestServerDB.SetNoAJAXJSON(const Value: boolean);
begin
  inherited;
................................................................................
        until Step<>SQLITE_ROW; // Execute all steps of the first statement
        result := true;
      end;
    finally
      DB.UnLock;
    end;
  except
    on ESQLite3Exception do
      result := false;
  end;
end;

function TSQLRestServerDB.UpdateField(Table: TSQLRecordClass; Where: integer;
  const FieldName: shortstring; FieldValue: integer; ByID: boolean): boolean;
var Field: PPropInfo;
................................................................................

procedure TSQLRestServerDB.Commit(SessionID: cardinal=1);
begin
  inherited Commit(SessionID); // reset fTransactionActive + write all TSQLVirtualTableJSON
  try
    DB.Commit;
  except
    on ESQLite3Exception do
      ; // just catch exception
  end;
end;

procedure TSQLRestServerDB.RollBack(SessionID: cardinal=1);
begin
  inherited; // reset TSQLRestServerDB.fTransactionActive flag
  try
    DB.RollBack; // reset TSQLDataBase.RollBack
  except
    on ESQLite3Exception do
      ; // just catch exception
  end;
end;

function TSQLRestServerDB.TransactionBegin(aTable: TSQLRecordClass; SessionID: cardinal=1): boolean;
begin
  result := inherited TransactionBegin(aTable,SessionID);
  if result then
    // fTransactionActive flag was not already set
    try
      DB.TransactionBegin;
    except
      on ESQLite3Exception do
        result := false;
    end;
end;

function TSQLRestServerDB.Backup(Dest: TStream): boolean;
var Source: TFileStream;
    Closed: boolean;
................................................................................
      result := fServer.InternalListJSON(TSQLRecordClass(Tables[0]),aSQL) else
      // we access localy the DB -> TSQLTableDB handle Tables parameter
      result := TSQLTableDB.Create(fServer.DB,
        RecordClassesToClasses(Tables),aSQL,not fServer.NoAJAXJSON);
    if fServer.DB.InternalState<>nil then
      result.InternalState := fServer.DB.InternalState^;
  except
    on ESQLite3Exception do
      result := nil;
  end;
end;

function TSQLRestClientDB.InternalURI(const url, method: RawUTF8;
  Resp, Head, SendData: PRawUTF8): Int64Rec;
var R,H,S: RawUTF8; // temp '' string to be used when no PString is provided
................................................................................

{ TSQLVirtualTableModuleServerDB }

constructor TSQLVirtualTableModuleServerDB.Create(
  aClass: TSQLVirtualTableClass; aServer: TSQLRestServer);
begin
  if not aServer.InheritsFrom(TSQLRestServerDB) then
    raise EBusinessLayerException.CreateFmt('%.Create expects a DB Server',[ClassName]);
  inherited Create(aClass,aServer);
  DB := TSQLRestServerDB(aServer).DB; // SetDB setter will do the work
end;


{ ************ Unit-Testing classes and functions }

................................................................................
  VO := TSQLRecordPeopleObject.Create;
{$endif}
  V2 := nil;
  if not IsMemory then begin
    DeleteFile('dali1.json');
    DeleteFile('dali2.data');
  end;
  Demo.RegisterSQLFunction(TypeInfo(TIntegerDynArray),@SortDynArrayInteger,
    'MyIntegerDynArrayContains');
  ModelC := TSQLModel.Create(
    [TSQLRecordPeople, {$ifdef INCLUDE_FTS3} TSQLFTSTest, {$endif}
     TSQLASource, TSQLADest, TSQLADests, TSQLRecordPeopleArray
     {$ifndef LVCL}, TSQLRecordPeopleObject{$endif},
     TSQLRecordDali1,TSQLRecordDali2],'root');
  ModelC.VirtualTableRegister(TSQLRecordDali1,TSQLVirtualTableJSON);

Changes to SQLite3/SQLite3Commons.pas.

438
439
440
441
442
443
444



445
446
447
448
449
450
451
....
1998
1999
2000
2001
2002
2003
2004





















2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015

2016
2017
2018
2019
2020
2021
2022
....
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
....
4039
4040
4041
4042
4043
4044
4045
4046






4047
4048
4049
4050
4051
4052
4053
4054
4055
....
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
....
4102
4103
4104
4105
4106
4107
4108
4109

4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120

4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133



4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144



4145
4146
4147
4148
4149
4150
4151
....
4159
4160
4161
4162
4163
4164
4165






















4166
4167
4168
4169
4170
4171
4172
4173



4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186











4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204































4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
....
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
....
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
....
6313
6314
6315
6316
6317
6318
6319











6320
6321
6322
6323
6324
6325
6326
....
8229
8230
8231
8232
8233
8234
8235
8236
8237
8238
8239
8240
8241
8242
8243
8244
8245
8246
8247
....
9779
9780
9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
9791
9792
9793
9794
9795
9796
9797
9798
9799
.....
11740
11741
11742
11743
11744
11745
11746
11747
11748
11749
11750
11751
11752
11753
11754
.....
12018
12019
12020
12021
12022
12023
12024
12025
12026
12027
12028
12029
12030
12031
12032
.....
12393
12394
12395
12396
12397
12398
12399
12400
12401
12402
12403
12404
12405
12406
12407
12408
12409
12410
12411
12412
12413
12414
12415
12416
12417
12418
12419
12420
12421
12422
12423
12424
12425
12426
12427
12428
12429
.....
13179
13180
13181
13182
13183
13184
13185
13186
13187
13188
13189
13190
13191
13192
13193
.....
13211
13212
13213
13214
13215
13216
13217
13218
13219
13220
13221
13222
13223
13224
13225
13226
13227
13228
13229
13230
13231
13232
13233
13234
13235
13236
13237
13238
.....
13240
13241
13242
13243
13244
13245
13246
13247
13248
13249
13250
13251
13252
13253
13254
.....
14369
14370
14371
14372
14373
14374
14375
14376
14377
14378
14379
14380
14381
14382
14383
.....
15093
15094
15095
15096
15097
15098
15099












15100
15101
15102
15103
15104
15105
15106
.....
15306
15307
15308
15309
15310
15311
15312
15313
15314
15315
15316
15317
15318
15319
15320
.....
15456
15457
15458
15459
15460
15461
15462
15463
15464
15465
15466
15467
15468
15469
15470
.....
15575
15576
15577
15578
15579
15580
15581
15582
15583
15584
15585
15586
15587
15588
15589
.....
15595
15596
15597
15598
15599
15600
15601
15602
15603
15604
15605
15606
15607
15608
15609
15610
15611
.....
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
.....
15969
15970
15971
15972
15973
15974
15975








15976
15977
15978
15979
15980
15981
15982
.....
16708
16709
16710
16711
16712
16713
16714
16715
16716
16717
16718
16719
16720
16721
16722
16723
16724
16725
16726
.....
17097
17098
17099
17100
17101
17102
17103
17104
17105
17106
17107
17108
17109
17110
17111
.....
17376
17377
17378
17379
17380
17381
17382
17383
17384
17385
17386
17387
17388
17389
17390
.....
18101
18102
18103
18104
18105
18106
18107
18108
18109
18110
18111
18112
18113
18114
18115
.....
19057
19058
19059
19060
19061
19062
19063
19064
19065
19066
19067
19068
19069
19070
19071
19072
19073
19074
19075
19076
19077
19078
19079
19080
19081
19082
19083
.....
20099
20100
20101
20102
20103
20104
20105
20106
20107
20108
20109
20110
20111
20112
20113
20114
20115
20116
20117
20118
20119
20120
20121
.....
20142
20143
20144
20145
20146
20147
20148
20149
20150
20151
20152
20153
20154
20155
20156
.....
20175
20176
20177
20178
20179
20180
20181
20182
20183
20184
20185
20186
20187
20188
20189
.....
20789
20790
20791
20792
20793
20794
20795
20796
20797
20798
20799
20800
20801
20802
20803
20804
20805
20806
20807
20808
20809
.....
21283
21284
21285
21286
21287
21288
21289
21290
21291
21292
21293
21294
21295
21296
21297
.....
21453
21454
21455
21456
21457
21458
21459
21460
21461
21462
21463
21464
21465
21466
21467
.....
21548
21549
21550
21551
21552
21553
21554
21555
21556
21557
21558
21559
21560
21561
21562
21563
21564
21565
21566
21567
21568
21569
21570
21571
21572
21573
21574
21575
21576
21577
21578
21579
21580

21581
21582
21583
21584
21585
21586
21587
21588
21589
21590
21591
21592
21593
.....
21598
21599
21600
21601
21602
21603
21604
21605
21606
21607
21608
21609
21610
21611
21612
21613
21614
21615
21616
21617
21618
21619
21620
21621
21622
21623
21624
21625
21626
21627
21628
21629
21630
21631
21632
21633
21634
21635
21636
.....
21664
21665
21666
21667
21668
21669
21670
21671
21672
21673
21674
21675
21676
21677
21678
21679
21680
21681
21682
21683
21684
21685
21686
21687
21688
21689
21690
21691
21692
21693
21694
21695
21696
21697
21698
21699
21700
21701
21702
21703
21704
21705
21706
21707
21708
21709
21710
21711
21712
21713
21714
21715
21716
21717
21718
21719
21720
21721
21722
21723
21724
21725
21726
21727
21728
21729
21730
21731

21732
21733
21734
21735
21736
21737
21738
21739
21740
21741
21742
21743
21744
21745
21746
21747
21748
21749
21750
.....
21753
21754
21755
21756
21757
21758
21759
21760
21761
21762
21763
21764
21765
21766
21767
21768



21769
21770
21771
21772
21773
21774
21775
.....
21781
21782
21783
21784
21785
21786
21787














21788
21789
21790
21791
21792
21793
21794
21795
.....
21802
21803
21804
21805
21806
21807
21808













21809
21810
21811
21812
21813
21814
21815











21816
21817
21818
21819
21820
21821
21822





21823

21824







21825


21826
21827
21828
21829
21830

21831
21832
21833
21834
21835
21836
21837
21838
21839
21840

21841
21842
21843
21844
21845
21846

21847









21848











21849



























21850


21851




















21852
21853
21854
21855
21856
21857
21858
21859
21860
21861
21862

21863
21864
21865
21866
21867
21868
21869
21870
21871
21872
21873
21874
21875
21876
21877
21878
21879
21880
21881
21882
21883
21884
21885
21886
21887
21888
21889
21890
21891
21892
21893
21894
21895
21896
21897
21898
21899
21900
21901
21902
21903
21904
21905
21906
21907
21908
21909
21910
21911
21912
21913
21914
21915
21916
21917
21918
21919
21920
21921
21922
21923
21924
21925
21926
21927
21928
21929
21930
21931
21932
21933
21934
21935

21936
21937
21938
21939
21940
21941
21942
21943
21944
21945
21946
21947
21948
21949
21950
21951
21952
21953
21954
21955
21956
21957
21958
21959
21960
21961
21962
21963
21964



21965

21966









21967
21968
21969
21970
21971
21972
21973
21974
21975
21976
21977
21978
21979
21980
21981
21982
21983
21984
21985
21986
21987
21988
21989
21990
21991
21992

21993
21994
21995
21996
21997
21998
21999
22000
22001
22002
22003
22004
22005
22006
22007
22008
22009
22010
22011
22012
22013
22014
22015
22016
22017
22018
.....
22076
22077
22078
22079
22080
22081
22082

22083
22084
22085
22086
22087
22088
22089
.....
22108
22109
22110
22111
22112
22113
22114
22115
22116
22117
22118
22119
22120
22121
22122
.....
22129
22130
22131
22132
22133
22134
22135
22136
22137
22138
22139
22140
22141
22142
22143
22144
22145







































































































































































































22146
22147
22148
22149
22150
22151
22152
22153
      by TSQLRestServerStaticInMemory.SaveToBinary)
    - fixed issue with TAuthSession.IDCardinal=0 after 76 connections
    - fixed issue in SetInt64Prop() with a setter method
    - fixed potential issue in TSQLTable.SearchValue in case of invalid Client
      supplied parameter (now checks TSQLRest class type)

  Version 1.16



    - added a generic JSON error message mechanism within the framework
      (including error code as integer and text, with custom error messages
      in RecordCanBeUpdated method and also in TSQLRestServerCallBackParams)
    - the TSQLRestServerCallBack method prototype has been modified to supply
      "var aParams: TSQLRestServerCallBackParams: cardinal" as unique parameter:
      this is a CODE BREAK change and you shall refresh ALL your server-side
      code to match the new signature (using a record passed by value as
................................................................................

  /// maximum handled dimension for TSQLRecordRTree
  // - this value is the one used by SQLite3 R-Tree virtual table
  RTREE_MAX_DIMENSION = 5;


type





















  TSQLModel = class;
  TSQLRest = class;
  TSQLRestClient = class;

{$M+} { we need the RTTI information to be compiled for the published
        properties of these classes and their children (like TPersistent),
        to enable ORM - must be defined at the forward definition level }
  TSQLRecord = class;      // published properties = ORM fields/columns
  TSQLAuthUser = class;

  TSQLRestServer = class;  // published events = RESTful callbacks handlers


{$M-}

  /// class-reference type (metaclass) of TSQLRecord
  TSQLRecordClass = class of TSQLRecord;

  PSQLRecordClass = ^TSQLRecordClass;
................................................................................

  /// all our services shall inherit from this interface
  // - in the current implementation, we rely on a single inheritance from
  // IService, in order to simplify the implementation
  IService = interface(IInvokable)
  end;


  /// the possible Server-side instance implementation patterns for Services
  // - each interface-based service will be implemented by a corresponding
  // class instance on the server: this parameter is used to define how
  // class instances are created and managed
  // - on the Client-side, each instance will be handled depending on the
  // server side implementation (i.e. with sicClientDriven behavior if necessary)
  // - sicSingle: one object instance is created per call - this is the
................................................................................
    Args: TServiceMethodArgumentDynArray;
    /// needed CPU stack size (in bytes) for all arguments
    ArgsSize: cardinal;
    /// contains all used kind of arguments
    ArgsUsed: set of TServiceMethodValueType;
    /// contains the count of variables for all used kind of arguments
    ArgsUsedCount: array[TServiceMethodValueVar] of integer;
    /// execute a method






    procedure InternalExecute(Instance, Method: pointer;
      Par: PUTF8Char; Res: TTextWriter; var Error: RawUTF8);
  end;

  /// describe a service provider methods
  TServiceMethodDynArray = array of TServiceMethod;

  /// an abstract service provider, as registered in TServiceContainer
  // - this will be either implemented by a TInterfacedObjectClass on the server,
................................................................................
  protected
    fInterfaceTypeInfo: PTypeInfo;
    fInterfaceIID: TGUID;
    fInterfaceURI: RawUTF8;
    fInterfaceMangledURI: RawUTF8;
    fInstanceCreation: TServiceInstanceImplementation;
    fRest: TSQLRest;
    fSharedInstance: TInterfacedObject;
    fSharedInterface: IInterface;
    fMethodsCount: integer;
    fMethods: TServiceMethodDynArray;
    /// return one instance of this interface - here is the magic
    function InternalProduce: TInterfacedObject; virtual; abstract;
  public
    /// initialize the service provider parameters
    // - it will check and retrieve all methods of the supplied interface,
    // and prepare all internal structures for its serialized execution
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation);
    /// release the service provider memory and structures
    destructor Destroy; override;
    /// retrieve an instance of this interface
    // - will call InternalProduce virtual method to retrieve a class
    // - sicClientDriven kind of instance creation will behave the same as sicSingle
    function FromFactory(out Obj): Boolean;
    /// get the index in Methods[] of a given method name
    // - returns -1 if the method name is unknown
    function MethodIndex(const aMethod: RawUTF8): integer;
    /// the associated RESTful instance
    property Rest: TSQLRest read fRest;
    /// the registered Interface low-level Delphi RTTI type
    property InterfaceTypeInfo: PTypeInfo read fInterfaceTypeInfo;
................................................................................
    // - only relevant on the server side; on the client side, this class will
    // be accessed only to retrieve a remote access instance, i.e. sicSingle
    property InstanceCreation: TServiceInstanceImplementation read fInstanceCreation;
    /// the declared methods
    property Methods: TServiceMethodDynArray read fMethods;
  end;

  /// service provider use this to store one internal instance 

  TServiceFactoryServerInstance = record
    /// the internal Instance ID, as remotely sent in "id":1
    // - is set to 0 when an entry in the array is free
    InstanceID: Cardinal;
    /// last time stamp access of this instance
    LastAccess: Cardinal;
    /// the implementation instance itself
    Instance: TInterfacedObject;
  end;

  /// service provider use this to store its internal instances 

  TServiceFactoryServerInstanceDynArray = array of TServiceFactoryServerInstance;

  /// a service provider implemented on the server side
  TServiceFactoryServer = class(TServiceFactory)
  protected
    fImplementationClass: TInterfacedObjectClass;
    fInstances: TServiceFactoryServerInstanceDynArray;
    fInstance: TDynArray;
    fInstancesCount: integer;
    fInstanceCurrentID: integer;
    fInstanceTimeOut: cardinal;
    fInstanceLock: TRTLCriticalSection;
    function InternalProduce: TInterfacedObject; override;



  public
    /// initialize the service provider on the server side
    // - expect an implementation class
    // - for sicClientDriven, a time out (in seconds) can be defined
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation;
      aImplementationClass: TInterfacedObjectClass;
      aTimeOutSec: cardinal=30*60); reintroduce;
    /// release all used memory
    // - e.g. any internal implementation instances
    destructor Destroy; override;



    /// call a given method of this service provider
    // - aMethodIndex is the index in Methods[]
    // - if aMethodIndex=-1, then it will free/release corresponding aInstanceID
    // (is called  e.g. from {"method":"free", "params":[], "id":1234} )
    // - aParamsJSONArray is e.g. '[1,2]' i.e. a true JSON array, which will
    // contain the incoming parameters in the same exact order than the
    // corresponding implemented interface method
................................................................................
    // instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    function ExecuteMethod(aSession: cardinal; aMethodIndex, aInstanceID: Integer;
      aParamsJSONArray: PUTF8Char; var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
  end;























  /// a global services provider class
  // - used to maintain a list of interfaces implementation
  TServiceContainer = class
  protected
    fRest: TSQLRest;
    fList: TObjectList;
    fExpectMangledURI: boolean;



  public
    /// initialize the list
    constructor Create(aRest: TSQLRest);
    /// release all registered services
    destructor Destroy; override;
    /// method called on the server side to register a service
    // - will raise an exception on error
    // - will return true if some interfaces have been added
    function AddImplementation(aImplementationClass: TInterfacedObjectClass;
      const aInterfaces: array of PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation): boolean;
    /// return the number of registered service interfaces
    function Count: integer;











    /// retrieve a service provider from its URI
    // - it expects the supplied URI variable  to be e.g. '00amyWGct0y_ze4lIsj2Mw'
    // or 'Calculator', depending on the ExpectMangledURI property
    // - on match, it  will return the service the corresponding interface factory
    // - returns nil if the URI does not match any registered interface
    function Service(const aURI: RawUTF8): TServiceFactory; overload;
    /// retrieve a service provider from its index in the list
    // - returns nil if out of range index
    function Service(aIndex: integer): TServiceFactory; overload; {$ifdef HASINLINE}inline;{$endif}
    /// the associated RESTful instance
    property Rest: TSQLRest read fRest;
    /// set if the URI is expected to be mangled from the GUID
    // - by default (FALSE), the clear service name is expected to be supplied at
    // the URI level (e.g. 'Calculator')
    // - if this property is set to TRUE, the mangled URI value will be expected
    // instead (may enhance security) - e.g. '00amyWGct0y_ze4lIsj2Mw'
    property ExpectMangledURI: boolean read fExpectMangledURI write fExpectMangledURI;
  end;
































  /// for TSQLRestCache, stores a table values
  TSQLRestCacheEntryValue = record
    /// corresponding ID
    ID: integer;
    /// GetTickCount value when this cached value was stored
    // - equals 0 when there is no JSON value cached
    TimeStamp: cardinal; 
    /// JSON encoded UTF-8 serialization of the record
    JSON: RawUTF8;
  end;

  /// for TSQLRestCache, stores all tables values
  TSQLRestCacheEntryValueDynArray = array of TSQLRestCacheEntryValue;
................................................................................
  TURIMapRequest = function(url, method, SendData: PUTF8Char; Resp, Head: PPUTF8Char): Int64Rec; cdecl;

{$ifdef MSWINDOWS}
  {$define WITHSTATPROCESS}
  // if defined, the server statistics will contain precise working time process
{$endif}

{$ifdef FPC}
{$PACKRECORDS 1}
{$else}
{$A-} { compiler use packed storage here, not aligned data }
{$endif}
  /// used for statistics update in TSQLRestServer.URI()
  TSQLRestServerStats = class(TPersistent)
  private
    /// used to determine if something changed
    fLastIncomingBytes: cardinal;
    /// current count of connected clients
    fClientsCurrent,
................................................................................
    property ServiceCalls: cardinal read fServices;
{$ifdef WITHSTATPROCESS}
    /// the global time spent in the server process
    property ProcessTime: RawUTF8 read GetProcessTimeString;
{$endif}
  end;

{$ifdef FPC}
{$PACKRECORDS 8}
{$else}
{$A+}
{$endif}

  ///  used to define how to trigger Events on record update
  // - see TSQLRestServer.OnUpdateEvent property
  // - returns true on success, false if an error occured (but action must continue)
  // - to be used only server-side, not to synchronize some clients: the framework
  // is designed around a stateless RESTful architecture (like HTTP/1.1), in which
  // clients ask the server for refresh (see TSQLRestClientURI.UpdateFromServer)
  TNotifySQLEvent = function(Sender: TSQLRestServer; Event: TSQLEvent;
................................................................................
    {{ wrapper to the protected URI method to call a method on the server, using
      a ModelRoot/[TableName/[ID/]]MethodName RESTful PUT request
      - returns the HTTP error code (e.g. 200 on success)
      - this version will use a PUT with the supplied raw UTF-8 data }
    function CallBackPut(const aMethodName, aSentData: RawUTF8;
      out aResponse: RawUTF8; aTable: TSQLRecordClass=nil; aID: integer=0;
      aResponseHead: PRawUTF8=nil): integer;











    /// is set to TRUE, all BLOB fields are transferred between the Client and
    // the remote Server
    // - i.e. Add() Update() will use Blob-related RESTful PUT/POST request
    // - i.e. Retrieve() will use Blob-related RESTful GET request
    // - note that the Refresh method won't handle BLOB fields, even if this
    // property setting is set to TRUE
    // - by default, this property is set to FALSE, which setting will spare
................................................................................
    // fastest common call with one TSQLRecordClass
    result := TSQLRecordClass(Tables[0]).RecordProps.
      SQLFromSelectWhere(SQLSelect,SQLWhere);
    exit;
  end;
  // 'SELECT T1.F1,T1.F2,T1.F3,T2.F1,T2.F2 FROM T1,T2 WHERE ..' e.g.
  if PtrUInt(high(Tables))>high(Props) then
    raise Exception.Create('SQLFromSelectWhere');
  for i := 0 to high(Tables) do begin
    Props[i] := TSQLRecordClass(Tables[i]).RecordProps;
    if Props[i]=nil then
      raise Exception.CreateFmt('SQLFromSelectWhere: not in Model: %s',
        [Tables[i].ClassName]);
  end;
  if SQLSelect='*' then
     // don't send BLOB values to query: retrieve all other fields
    if high(Tables)=0 then
      result := 'SELECT '+Props[0].SQLTableSimpleFields[true,false] else begin
      result := 'SELECT '+Props[0].SQLTableSimpleFields[true,true];
................................................................................
      FU := GetJSONField(P,P);
      inc(Len,length(FU));
      if P=nil then break;
      Fields2[FieldsCount] := FU;
      Values[FieldsCount] := GetSQLValue; // update EndOfObject
      inc(FieldsCount);
      if FieldsCount=MAX_SQLFIELDS then
        raise Exception.Create('Too many inlines in GetJSONObjectAsSQL');
    until EndOfObject in [#0,'}',']'];
    Return(@Fields2,@Values,InlinedParams);
  end else begin
    // get "VAL1","VAL2"...
    if RowID>0 then
      raise Exception.Create('GetJSONObjectAsSQL(expanded) won''t handle RowID');
    FieldsCount := length(Fields);
    for F := 0 to FieldsCount-1 do begin
      inc(Len,length(Fields[F]));
      Values[F] := GetSQLValue; // update EndOfObject
    end;
    Return(@Fields[0],@Values,InlinedParams);
  end;
................................................................................
procedure TSQLRecordFill.SetMappedFieldsExpandedJSONWriter(aClass: TSQLRecordClass;
  W: TJSONWriter);
var i,n: integer;
begin // update ID, TModTime and FillPrepare mapped fields
  if (self=nil) or (W=nil) or (aClass=nil) then
    exit;
  if fTableMapRecordManyInstances<>nil then
    raise Exception.Create('SetMappedFieldsExpandedJSONWriter after FillPrepareMany');
  if fTableMapFieldsColNames=nil then
  with aClass.RecordProps do begin // init fTableMapFields[Max,ColNames] from mapping
    n := 1;
    for i := 0 to high(Fields) do
      if FieldType[i]=sftModTime then begin
        Include(fTableMapFields,i);
        inc(n);
................................................................................
procedure TSQLRecord.FillRow(aRow: integer; aDest: TSQLRecord=nil);
begin
  if self<>nil then
    if aDest=nil then
      fFill.Fill(aRow) else
      if fFill.fTableMapRecordManyInstances=nil then
        fFill.Fill(aRow,aDest) else
        raise Exception.Create('FillRow() forbidden after FillPrepareMany');
end;

function TSQLRecord.FillOne: boolean;
begin
  if (self=nil) or (fFill=nil) or (fFill.Table=nil) or
     (fFill.Table.RowCount=0) or // also check if FillTable is emtpy
     (cardinal(fFill.FillCurrentRow)>cardinal(fFill.Table.RowCount)) then
................................................................................
      case Kind of
      rFTS3:  result := result+'fts3(';
      rFTS4:  result := result+'fts4(';
      rRTree: result := result+'rtree(ID,';
      rCustomForcedID, rCustomAutoID: begin
        M := aModel.VirtualTableModule(self);
        if M=nil then
          raise Exception.CreateFmt('No registered module for %s',[ClassName]);
        result := result+M.ModuleName+'('+GetVirtualTableSQLCreate(RecordProps);
      end;
      end;
      case Kind of
      rFTS3, rFTS4: begin
        for i := 0 to n-1 do
          if FieldType[i]<>sftUTF8Text then
            raise Exception.CreateFmt('%s.%s: FTS3/FTS4 field must be RawUTF8',
              [SQLTableName,FieldsName[i]]) else
            Fields[i]^.AppendName(result,',');
        if InheritsFrom(TSQLRecordFTS3Porter) or
           InheritsFrom(TSQLRecordFTS4Porter) then
          result := result+' tokenize=porter)' else
          result := result+' tokenize=simple)';
      end;
      rRTree: begin
        if (n<3) or (n>RTREE_MAX_DIMENSION*2+1) or (n and 2<>1) then
          raise Exception.CreateFmt('%d: %s RTREE expects an odd 3..%d column number',
            [n,SQLTableName,RTREE_MAX_DIMENSION*2+1]);
        for i := 0 to n-1 do
          if FieldType[i]<>sftFloat then
            raise Exception.CreateFmt('%s.%s: RTREE field must be double',
              [SQLTableName,FieldsName[i]]) else
            Fields[i]^.AppendName(result,',');
        result[length(result)] := ')';
      end;
      end;
    end else begin
      // inherits from TSQLRecord: create a "normal" SQLite3 table
................................................................................
        FieldType := Props.FieldType[f];
        FieldName := @Props.Fields[f]^.ShortName;
        FieldRecordClass := TSQLRecordClass(PT^.ClassType^.ClassType);
      end;
    end;
    sftMany:
      if GetTableIndex(TSQLRecordClass(PT^.ClassType^.ClassType))<0 then
        raise Exception.CreateFmt('%s must include %s for %s.%s',
          [ClassName,TSQLRecordClass(PT^.ClassType^.ClassType).ClassName,
           Tables[aIndex].ClassName,Props.FieldsName[f]]);
    end;
  end;
  if Last then
    QuickSortRawUTF8(fTablesName,length(fTablesName),@fTablesNameIndex,StrIComp);
end;
................................................................................
    aTableIndexCreated^ := n;
  result := true;
end;

constructor TSQLModel.Create(CloneFrom: TSQLModel);
begin
  if CloneFrom=nil then
    raise Exception.Create('TSQLModel.Create(CloneFrom=nil)');
  Create(CloneFrom.fTables,CloneFrom.fRoot);
  fVirtualTableModule := CloneFrom.fVirtualTableModule;
end;

constructor TSQLModel.Create(TabParameters: PSQLRibbonTabParameters;
  TabParametersCount, TabParametersSize: integer;
  const NonVisibleTables: array of TSQLRecordClass; const aRoot: RawUTF8);
var i: integer;
    Tables: array of TSQLRecordClass;
begin
  if (TabParameters=nil) or (TabParametersCount<=0) or
     (cardinal(TabParametersSize)<sizeof(TSQLRibbonTabParameters)) then
    raise Exception.Create('TSQLModel.Create(TabParameters?)');
  SetLength(Tables,TabParametersCount+length(NonVisibleTables));
  for i := 0 to TabParametersCount-1 do begin
    Tables[i] := TabParameters^.Table;
    inc(PtrUInt(TabParameters),TabParametersSize);
  end;
  for i := 0 to high(NonVisibleTables) do
    Tables[i+TabParametersCount] := NonVisibleTables[i];
................................................................................
end;

constructor TSQLModel.Create(const Tables: array of TSQLRecordClass; const aRoot: RawUTF8='root');
var N, i: integer;
begin
  N := length(Tables);
  if N>sizeof(SUPERVISOR_ACCESS_RIGHTS.Get)*8 then // TSQLAccessRights bits size
    raise Exception.CreateFmt('%s has too many Tables: %d>%d',
      [ClassName,N,sizeof(SUPERVISOR_ACCESS_RIGHTS.Get)*8]); // e.g. N>64
  // set the Tables to be associated with this Model, as TSQLRecord classes
  SetLength(fTables,N);
  move(Tables[0],fTables[0],N*Sizeof(Tables[0]));
  SetLength(fTablesName,N);
  SetLength(fTablesNameIndex,N);
  SetLength(TableProps,N);
................................................................................
    result := SetCache(PSQLRecordClass(aRecord)^,aRecord.fID);
end;

constructor TSQLRestCache.Create(aRest: TSQLRest);
var i: integer;
begin
  if aRest=nil then
    Exception.CreateFmt('%s.Create',[ClassName]);
  fRest := aRest;
  SetLength(fCache,length(fRest.Model.Tables));
  for i := 0 to high(fCache) do
    with fCache[i] do begin
      Value.Init(TypeInfo(TSQLRestCacheEntryValueDynArray),Values,@Count);
      Value.Compare := SortDynArrayInteger; // will search/sort by ID
      InitializeCriticalSection(Mutex);
................................................................................
    result := URI(Model.getURICallBack(aMethodName,aTable,aID),
      'PUT',nil,aResponseHead,@aSentData).Lo;
{$ifdef WITHLOG}
    SQLite3Log.Add.Log(sllServiceReturn,'Result=%',result);
{$endif}
  end;
end;













procedure TSQLRestClientURI.BatchAbort;
begin
  if self<>nil then begin
    fBatchCount := 0;
    fBatchTable := nil;
    fBatch.CancelAll;
................................................................................
    P := pointer(result);
    repeat
      Read := FileRead(Handle,P^,L);
      if Read=0 then begin
        sleep(100); // nothing available -> wait a little and retry
        Read := FileRead(Handle,P^,L);
        if Read=0 then begin // server may be down -> abort
          raise Exception.Create('ReadString');
          exit;
        end;
      end;
      inc(P,Read);
      dec(L,Read);
    until L=0; // loop until received all expected data 
  end;
................................................................................
end;

destructor TSQLRestServer.Destroy;
var i: integer;
begin
{$ifdef WITHLOG}
  if not InheritsFrom(TSQLRestServerStatic) then
    SQLite3Log.Add.Log(sllInfo,StringToUTF8(Stats.DebugMessage),self);
{$endif}
{$ifdef MSWINDOWS}
  if GlobalURIRequestServer=self then begin
    GlobalURIRequestServer := nil;
    sleep(200); // way some time any request is finished in another thread
  end;
  // close any opened server
................................................................................
  if (ppBeg=0) or (PosEx(RawUTF8('):'),SQL,ppBeg+2)=0) then
    // SQL code with no valid :(...): internal parameters -> leave maxParam=0
    exit;
  // compute GenericSQL from SQL, converting :(...): into ?
  Gen := PUTF8Char(pointer(result))+ppBeg-1; // Gen^ just before :(
  P := PUTF8Char(pointer(SQL))+ppBeg+1; // P^ just after :(
  repeat
    Gen^ := '?'; // replace :(...): into ?
    inc(Gen);
    if length(Values)<=maxParam then
      SetLength(Values,maxParam+8);
    P := SQLParamContent(P,Types[maxParam],Values[maxParam]);
    if P=nil then begin
      maxParam := 0;
      result := SQL;
................................................................................
      inc(P);
    end;
    if P^=#0 then
      Break;
    inc(P,2);
    inc(maxParam);
    if maxParam>high(Types) then
      raise Exception.Create('Too many :(): params');
  until false;
  // get statement from cache, or create new one
  SetLength(result,Gen-pointer(result));
  inc(maxParam);
end;

procedure TSQLRestServer.SetNoAJAXJSON(const Value: boolean);
var i: integer;
begin
................................................................................
  const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation): boolean;
begin
  result := False;
  if (self=nil) or (aImplementationClass=nil) or (high(aInterfaces)<0) then
    exit;
  if fServices=nil then
    fServices := TServiceContainer.Create(self);

  result := fServices.AddImplementation(aImplementationClass,aInterfaces,aInstanceCreation);
end;

function TSQLRestServer.ServiceCall(aSession: cardinal; const aURI, aSentData: RawUTF8;
  var aResp, aHead, aErrorMsg: RawUTF8; var aResult: cardinal): boolean;
var Service: TServiceFactory;
    method, JSON: RawUTF8;
    Values: TPUtf8CharDynArray;
    m, id: integer;
begin
  result := false;
  if aSentData='' then
    exit;
  Service := Services.Service(aURI);
  if Service=nil then
    exit;
  inc(fStats.fServices);
  SetString(JSON,PAnsiChar(aSentData),length(aSentData)); // in-place modif.
  JSONDecode(JSON,['METHOD','PARAMS','ID'],Values,True);
  if Values[0]=nil then begin
    aErrorMsg := 'Method name required';
................................................................................
  end;
  if Values[1]=nil then begin
    aErrorMsg := 'Parameters required';
    exit;
  end;
  method := Values[0];
  id := GetCardinal(Values[2]);








  if (id<>0) and IdemPropNameU(method,'free') then
    // "method":"free" to release the sicClientDriven server-side instance
    m := -1 else begin
    // regular method
    m := Service.MethodIndex(method);
    if m<0 then begin
      aErrorMsg := 'Unknown method';
................................................................................

constructor TSQLRestClientURIDll.Create(aModel: TSQLModel; const DllName: TFileName);
var aRequest: TURIMapRequest;
    aDLL: cardinal;
begin
  aDLL := LoadLibrary(pointer(DllName));
  if aDLL=0 then
    raise Exception.Create(DllName);
  aRequest := GetProcAddress(aDLL,'URIRequest');
  if (@aRequest=nil) or (aRequest(nil,nil,nil,nil,nil).Lo<>404) then begin
    FreeLibrary(aDLL);
    raise Exception.CreateFmt('%s doesn''t export a valid URIRequest function',[DllName]);
  end;
  Create(aModel,aRequest);
  fLibraryHandle := aDLL;
end;

constructor TSQLRestClientURIDll.Create(aModel: TSQLModel; aRequest: TURIMapRequest);
begin
................................................................................
    if WaitNamedPipe(pointer(fPipeName),1000) then
      // 1000 since we have sleep(128) in TSQLRestServerNamedPipe.EngineExecute
      CreatePipe;
  end;
  if Pipe=Invalid_Handle_Value then begin // server must exist
    if (aModel<>nil) and (aModel.Owner=self) then
      aModel.Free; // avoid memory leak
    raise Exception.CreateFmt(
      '%s can''t connect to server "%s"'#13'via "%s":'#13'%s',
      [ClassName,ApplicationName,fPipeName,SysErrorMessage(GetLastError)]);
  end;
{$ifdef ANONYMOUSNAMEDPIPE}
  RevertToSelf; // we just needed to be anonymous during pipe connection
{$endif}
  inherited Create(aModel);
................................................................................
constructor TSQLRestServerStaticInMemory.Create(aClass: TSQLRecordClass; aServer: TSQLRestServer;
  const aFileName: TFileName = ''; aBinaryFile: boolean=false);
var JSON: RawUTF8;
    Stream: TStream;
begin
  inherited Create(aClass,aServer,aFileName,aBinaryFile);
  if fStoredClassProps.Kind in INSERT_WITH_ID then
    raise Exception.CreateFmt('%s: %s virtual table can''t be static',
      [fStoredClassProps.SQLTableName,aClass.ClassName]);
  fBinaryFile := aBinaryFile;
  fValue := TObjectList.Create;
  if (fFileName<>'') and FileExists(fFileName) then begin
    if aBinaryFile then begin
      Stream := TSynMemoryStreamMapped.Create(fFileName);
      try
................................................................................
{ TSQLRestServerStatic }

constructor TSQLRestServerStatic.Create(aClass: TSQLRecordClass;
  aServer: TSQLRestServer; const aFileName: TFileName; aBinaryFile: boolean);
begin
  inherited Create(nil,false);
  if aClass=nil then
    raise Exception.CreateFmt('%s.Create expect a class',[ClassName]);
  fStoredClass := aClass;
  fStoredClassProps := aClass.RecordProps;
  fIsUnique := fStoredClassProps.IsUniqueFieldsBits;
  if aServer<>nil then begin
    fOwner := aServer;
    fModel := aServer.Model;
    fNoAJAXJSON := aServer.fNoAJAXJSON; // expanded as main Server
................................................................................

constructor TSQLRestClientURIMessage.Create(aModel: TSQLModel;
  const ServerWindowName: string; ClientWindow: HWND; TimeOutMS: cardinal);
begin
  inherited Create(aModel);
  fServerWindow := FindWindow(pointer(ServerWindowName),nil);
  if fServerWindow=0 then
    raise Exception.CreateFmt('No "%s" window available - server may be down',
      [ServerWindowName]);
  fClientWindow := ClientWindow;
  fTimeOutMS := TimeOutMS;
end;

constructor TSQLRestClientURIMessage.Create(aModel: TSQLModel;
  const ServerWindowName, ClientWindowName: string; TimeOutMS: cardinal);
var H: HWND;
begin
  H := CreateInternalWindow(ClientWindowName,self);
  if H=0 then
    raise Exception.CreateFmt('Impossible to create "%s" client window',
      [ClientWindowName]);
  fClientWindowName := ClientWindowName;
  Create(aModel,ServerWindowName,H,TimeOutMS);
end;

destructor TSQLRestClientURIMessage.Destroy;
begin
................................................................................
    CP := InternalClassProp(aClassType);
    if CP=nil then
      exit;
    P := @CP^.PropList;
    for pi := 0 to CP^.PropCount-1 do begin
      // 0. check that this property is not an ID/RowID (handled separately)
      if IsRowID(P^.ShortName) then
        raise Exception.CreateFmt('%s should not include a %s published property',
          [aTable.ClassName,P^.ShortName]);
      // 1. store RTTI for this property
      FieldType[f] := P^.PropType^^.SQLFieldType;
      Fields[f] := P;
      FieldName := RawUTF8(P^.ShortName);
      for j := 0 to f-1 do
        if IdemPropNameU(FieldsName[j],FieldName) then
          raise Exception.CreateFmt('dup property name %s in %s',
            [FieldName,aTable.ClassName]);
      FieldsName[f] := FieldName;
      fFieldsNameSorted[f] := FieldName;
      fFieldsNameIndex[f] := f; 
      // 2. handle unique fields, i.e. if marked as "stored false"
      Unique := not P^.IsStored;
      if Unique then begin
................................................................................
          ManyFields[nMany] := P;
          inc(nMany);
        end;
        sftBlobDynArray: begin
          if P^.Index<>0 then
            for j := 0 to nDynArray-1 do
            if DynArrayFields[j]^.Index=P^.Index then
              raise Exception.CreateFmt('dup index %d for %s.%s and %s properties',
                [P^.Index,aTable.ClassName,P^.ShortName,DynArrayFields[j]^.ShortName]);
          DynArrayFields[nDynArray] := P;
          inc(nDynArray);
          goto Simple;
        end;
        else begin
          // this code follows NOT_SIMPLE_FIELDS const 
................................................................................
  SQLTableName := GetDisplayNameFromClass(aTable);
  ExternalTableName := SQLTableName;
  SQLTableNameUpperWithDot := UpperCase(SQLTableName)+'.';
  ClassProp := InternalClassProp(aTable);
  assert(ClassProp<>nil);
  nProps := PClassProp(aTable)^.FieldCountWithParents;
  if nProps>MAX_SQLFIELDS then
    raise Exception.CreateFmt('%s has too many fields: %d>%d',
      [SQLTableName,nProps,MAX_SQLFIELDS]);
  SetLength(FieldType,nProps);
  SetLength(Fields,nProps);
  SetLength(FieldsName,nProps);
  SetLength(fFieldsNameSorted,nProps);
  SetLength(fFieldsNameIndex,nProps);
  SetLength(ManyFields,nProps);
................................................................................

constructor TSQLVirtualTable.Create(aModule: TSQLVirtualTableModule;
  const aTableName: RawUTF8; FieldCount: integer; Fields: PPUTF8CharArray);
var aTable: TSQLRecordClass;
    aTableIndex: integer;
begin
  if (aModule=nil) or (aTableName='') then
    raise Exception.CreateFmt('Invalid parameters to %s.Create',[ClassName]);
  fModule := aModule;
  fTableName := aTableName;
  if fModule.fFeatures.StaticClass<>nil then
    // create no fStatic instance e.g. for TSQLVirtualTableLog
    if fModule.Server=nil then
      raise Exception.CreateFmt('Missing aModule.Server for %s.Create',[ClassName]) else
    with fModule.Server do begin
      aTableIndex := Model.GetTableIndex(aTableName);
      if aTableIndex>=0 then begin
        aTable := Model.Tables[aTableIndex];
        fStatic := fModule.fFeatures.StaticClass.Create(aTable,fModule.Server,
          fModule.FileName(aTableName),self.InheritsFrom(TSQLVirtualTableBinary));
        if length(fStaticVirtualTable)<>length(Model.Tables) then
................................................................................
      {$endif}
      Access;
      exit; // create successfull
    end;
    User.GroupRights.Free;
    User.GroupRights := GID;
  end;
  raise Exception.Create('TAuthSession.Create');
end;

destructor TAuthSession.Destroy;
begin
  if User<>nil then begin
    User.GroupRights.Free;
    fUser.Free;
................................................................................
{ TServiceFactory }

type
  PInterfaceTypeData = ^TInterfaceTypeData;
  TInterfaceTypeData = packed record
    IntfParent : PPTypeInfo; // ancestor
    IntfFlags : set of (ifHasGuid, ifDispInterface, ifDispatch);
    Guid : TGUID;
    IntfUnit : ShortString;
  end;
  TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor,
    mkClassProcedure, mkClassFunction, { Obsolete } mkSafeProcedure, mkSafeFunction);
  TIntfMethodEntryTail = packed record
    Kind: TMethodKind;
    CC: TCallingConvention;
................................................................................
    PP: ^PPTypeInfo absolute P;
    Ancestor: PTypeInfo;
    Kind: TMethodKind;
    i,j, n, m,a,reg,offs: integer;
begin
  // check supplied interface
  if (aRest=nil) or (aInterface=nil) then
    raise Exception.Create('Invalid call');
  fRest := aRest;
  fInstanceCreation := aInstanceCreation;
  fInterfaceTypeInfo := aInterface;
  fInterfaceURI := aInterface^.ShortName;
  if fInterfaceURI[1] in ['I','i'] then
    Delete(fInterfaceURI,1,1);
  P := fInterfaceTypeInfo.ClassType;
  if PI^.IntfParent<>nil then
    Ancestor := PI^.IntfParent^ else
    Ancestor := nil;
  if (Ancestor<>nil) and (Ancestor<>TypeInfo(IService)) then
    raise Exception.CreateFmt('%s interface should not have %s as parent but IService',
      [fInterfaceTypeInfo^.ShortName,Ancestor^.ShortName]);
  fInterfaceIID := PI^.Guid;
  P := @PI^.IntfUnit[ord(PI^.IntfUnit[0])+1];
  fInterfaceMangledURI := BinToBase64URI(@fInterfaceIID,sizeof(TGUID));
  // retrieve methods (not from ancestors since we inherit from IService)
  n := PW^; inc(PW);
  if PW^=$ffff then
    raise Exception.CreateFmt('%s interface has no RTTI',[fInterfaceTypeInfo^.ShortName]);
  inc(PW);
  fMethodsCount := n;
  SetLength(fMethods,n); // QueryInterface, _AddRef, _Release are hard-coded
  for i := 0 to n-1 do
  with fMethods[i] do begin

    SetString(URI,PAnsiChar(@PS^[1]),ord(PS^[0]));
    PS := @PS^[ord(PS^[0])+1];
    Kind := PME^.Kind;
    CallingConvention := PME^.CC;
    if CallingConvention<>ccRegister then
      raise Exception.CreateFmt('%s.%s method shall use register calling convention',
        [fInterfaceTypeInfo^.ShortName,URI]);
    n := PME^.ParamCount;
    inc(PME);
    if Kind=mkFunction then
      SetLength(Args,n+1) else
      SetLength(Args,n);
    for j := 0 to n-1 do
................................................................................
        ValueDirection := smdOut;
      inc(PF);
      ParamName := PS;
      PS := @PS^[ord(PS^[0])+1];
      TypeName := PS;
      PS := @PS^[ord(PS^[0])+1];
      if PP^=nil then
        raise Exception.CreateFmt('%s.%s method %s parameter has no information',
          [fInterfaceTypeInfo^.ShortName,URI,ParamName^]);
      TypeInfo := PP^^;
      inc(PP);
      {$ifdef ISDELPHIXE2}
      inc(PW); // skip attributes
      {$endif}
      if j=0 then
        ValueType := smvSelf else
        ValueType := TypeInfoToMethodValueType(TypeInfo);
      if ValueType=smvNone then
        raise Exception.CreateFmt('%s.%s method %s parameter has unexpected type %s',
          [fInterfaceTypeInfo^.ShortName,URI,ParamName^,TypeName^]);
    end;
    if Kind=mkFunction then
    with Args[n] do begin
      ParamName := @CONST_RESULT_NAME;
      ValueDirection := smdResult;
      TypeName := PS;
      PS := @PS^[ord(PS^[0])+1];
      TypeInfo := PP^^;
      inc(PP);
      ValueType := TypeInfoToMethodValueType(TypeInfo);
      if ValueType=smvNone then
        raise Exception.CreateFmt('%s.%s method has unexpected result type %s',
          [fInterfaceTypeInfo^.ShortName,URI,TypeName^]);
    end;
    {$ifdef ISDELPHIXE2}
    inc(PW); // skip attributes
    {$endif}
  end;
  // compute asm low-level layout of the parameters for each method
................................................................................
    with Args[a] do
      if OffsetInStack>=0 then begin
        dec(offs,SizeInStack);
        OffsetInStack := offs;
      end;
    assert(offs=0);
  end;
  // initialize the shared instance
  if InstanceCreation=sicShared then begin
    fSharedInstance := InternalProduce;
    if (fSharedInstance=nil) or
       not fSharedInstance.GetInterface(fInterfaceIID,fSharedInterface) then
      raise Exception.CreateFmt('%s internal class does not implement "%s" interface',
        [ClassName,fInterfaceURI]);
  end;
end;

destructor TServiceFactory.Destroy;
begin
  inherited;
end;

function TServiceFactory.FromFactory(out Obj): Boolean;
begin
  result := false;
  if Self<>nil then
    case fInstanceCreation of
    sicShared:
    if fSharedInterface<>nil then begin
      IInterface(Obj) := fSharedInterface; // copy implementation interface
      result := true;
    end;
    sicSingle, sicClientDriven:
      result := InternalProduce.GetInterface(fInterfaceIID,Obj);
    end;
end;

function TServiceFactory.MethodIndex(const aMethod: RawUTF8): integer;
begin
  for result := 0 to fMethodsCount-1 do
    if IdemPropNameU(aMethod,fMethods[result].URI) then
      exit;
  result := -1;
end;


{ TServiceContainer }

function IsEqualGUID(const guid1, guid2: TGUID): Boolean; {$ifdef HASINLINE}inline;{$endif}
var a: array[0..3] of integer absolute guid1;
    b: array[0..3] of integer absolute guid2;
begin // faster implementation than in SysUtils.pas
  Result := (a[0]=b[0]) and (a[1]=b[1]) and (a[2]=b[2]) and (a[3]=b[3]);
end;

function TServiceContainer.AddImplementation(
  aImplementationClass: TInterfacedObjectClass;
  const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation): boolean;
var C: TClass;
    T: PInterfaceTable;
    i, j: integer;
    UID: array of ^TGUID;
begin
  result := false;
  // check input parameters
  if (self=nil) or (aImplementationClass=nil) or (high(aInterfaces)<0) then
    exit;

  SetLength(UID,length(aInterfaces));
  for j := 0 to high(aInterfaces) do
    with aInterfaces[j]^, PInterfaceTypeData(ClassType)^ do
    if Kind<>tkInterface then
      raise Exception.CreateFmt('%s is not an interface',[ShortName]) else
    if not (ifHasGuid in IntfFlags) then
      raise Exception.CreateFmt('%s interface has no GUID',[ShortName]) else begin
      UID[j] := @Guid;
      for i := 0 to fList.Count-1 do
        if IsEqualGUID(TServiceFactory(fList.List[i]).InterfaceIID,Guid) then
          raise Exception.CreateFmt('%s GUID already registered',[ShortName]);
    end;
  // check that all interfaces are implemented by this class
  C := aImplementationClass;
  repeat
    T := C.GetInterfaceTable;
    if T<>nil then
      for i := 0 to T^.EntryCount-1 do
        with T^.Entries[i] do
................................................................................
            UID[j] := nil;
            break;
          end;
    C := C.ClassParent;
  until C=nil;
  for j := 0 to high(aInterfaces) do
    if UID[j]<>nil then
      raise Exception.CreateFmt('Interface %s not found in %s implementation',
        [aInterfaces[j]^.ShortName,aImplementationClass.ClassName]);
  // register this implementation class
  for j := 0 to high(aInterfaces) do
    fList.Add(TServiceFactoryServer.Create(Rest,aInterfaces[j],aInstanceCreation,
      aImplementationClass));
  result := true;
end;




function TServiceContainer.Count: integer;
begin
  if self=nil then
    result := 0 else
    result := fList.Count;
end;

................................................................................

destructor TServiceContainer.Destroy;
begin
  fList.Free;
  inherited;
end;















function TServiceContainer.Service(const aURI: RawUTF8): TServiceFactory;
var i: Integer;
begin
  if (self<>nil) and (aURI<>'') then
  with fList do begin
    if ExpectMangledURI then begin
      for i := 0 to Count-1 do begin
        result := List[i];
................................................................................
        if IdemPropNameU(aURI,result.fInterfaceURI) then
          exit;
      end;
  end;
  result := nil;
end;














function TServiceContainer.Service(aIndex: integer): TServiceFactory;
begin
  if (Self=nil) or (Cardinal(aIndex)>=Cardinal(fList.Count)) then
    result := nil else
    result := fList.List[aIndex];
end;













{ TServiceFactoryServer }

constructor TServiceFactoryServer.Create(aRest: TSQLRest; aInterface: PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  aImplementationClass: TInterfacedObjectClass; aTimeOutSec: cardinal);
begin





  fImplementationClass := aImplementationClass; // inherited Create needs itpc

  inherited Create(aRest,aInterface,aInstanceCreation);







  if InstanceCreation=sicClientDriven then begin


    InitializeCriticalSection(fInstanceLock);
    fInstanceTimeOut := aTimeOutSec;
    fInstance.Init(TypeInfo(TServiceFactoryServerInstanceDynArray),
      fInstances,@fInstancesCount);
    fInstance.Compare := SortDynArrayInteger;

  end;
end;

destructor TServiceFactoryServer.Destroy;
var i: integer;
begin
  try
    for i := 0 to fInstancesCount-1 do
      fInstances[i].Instance.Free;
  except

  end;
  DeleteCriticalSection(fInstanceLock);
  inherited;
end;

function TServiceFactoryServer.InternalProduce: TInterfacedObject;

begin









  if (Self=nil) or (fImplementationClass=nil) then











    result := nil else



























    result := fImplementationClass.Create;


end;





















function TServiceFactoryServer.ExecuteMethod(aSession: cardinal;
  aMethodIndex, aInstanceID: Integer; aParamsJSONArray: PUTF8Char;
  var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
var i: integer;
    Inst: TServiceFactoryServerInstance;
    TimeOutTimeStamp: cardinal;
    WR: TTextWriter;
    entry: PInterfaceEntry;
    method: pointer;
begin // returns 200 + {"result":[3],"id":0} on success

  result := 400;
  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit else
        Inst.Instance := InternalProduce;
    sicShared:
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven: begin
      Inst.InstanceID := aInstanceID;
      Inst.LastAccess := GetTickCount;
      TimeOutTimeStamp := Inst.LastAccess+fInstanceTimeOut;
      EnterCriticalSection(fInstanceLock);
      try
        // first release any deprecated instances
        for i := 0 to fInstancesCount-1 do
          with fInstances[i] do
          if (LastAccess<Inst.LastAccess) or
             (LastAccess>TimeOutTimeStamp) then begin
            InstanceID := 0; // mark this entry is empty
            FreeAndNil(Instance);
          end;
        // retrieve or initialize the instance
        if Inst.InstanceID=0 then begin
          if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
            exit;
          // initialize the new instance
          inc(fInstanceCurrentID);
          Inst.InstanceID := fInstanceCurrentID;
          for i := 0 to fInstancesCount-1 do
            if fInstances[i].InstanceID=0 then begin
              Inst.Instance := InternalProduce; // found an empty entry
              if Inst.Instance<>nil then
                fInstances[i] := Inst;
              break;
            end;
          if Inst.Instance=nil then begin
            Inst.Instance := InternalProduce; // append a new entry
            if Inst.Instance<>nil then
             fInstance.Add(Inst);
          end;
        end else
          // search the given instance
          for i := 0 to fInstancesCount-1 do
            with fInstances[i] do
            if InstanceID=Inst.InstanceID then begin
              if aMethodIndex<0 then begin
                // aMethodIndex=-1 fpr {"method":"free", "params":[], "id":1234}
                InstanceID := 0;
                FreeAndNil(Instance);
                result := 200; // successfully released instance
                exit;
              end;
              LastAccess := Inst.LastAccess;
              Inst.Instance := Instance;
              break;
            end;
      finally
        LeaveCriticalSection(fInstanceLock);
      end;
    end;
  end;
  if Inst.Instance=nil then begin
    aErrorMsg := FormatUTF8('Implementation instance %d not found or deprecated',
      [Inst.InstanceID]);
    exit;
  end;
  // 2. call method implementation

  entry := Inst.Instance.GetInterfaceEntry(fInterfaceIID);
  if entry=nil then
    exit;
  method := PPointerArray(PPointer(PtrInt(Inst.Instance)+entry^.IOffset)^)^
    [aMethodIndex+3]; // +3 for hardcoded QueryInterface+_AddRef+_Release
  WR := TTextWriter.CreateOwnedStream;
  try
    // root/calculator {"method":"add","params":[1,2]} -> {"result":[3],"id":0}
    try
      WR.AddShort('{"result":[');
      fMethods[aMethodIndex].InternalExecute(
        Inst.Instance,method,aParamsJSONArray,WR,aErrorMsg);
      if aErrorMsg<>'' then
        exit; // wrong request
      WR.AddShort('],"id":');
      WR.Add(Inst.InstanceID);
      WR.AddShort('}');
      aResp := WR.Text;
      result := 200; // success
    except
      on E: Exception do begin
        result := 500;
        aErrorMsg := FormatUTF8('%s: %s',[E.ClassName,E.Message]);
        exit;
      end;
    end;
  finally
    WR.Free;
  end;



end;











// we already have the value on the FPU ST(0) register -> do nothing functions 
function LoadDouble: double; asm end;
function LoadCurrency: currency; asm end;

procedure TServiceMethod.InternalExecute(Instance, Method: pointer;
  Par: PUTF8Char; Res: TTextWriter; var Error: RawUTF8);
var Stack: TByteDynArray;
    StackSize: integer;
    Int64s: TInt64DynArray;
    RawUTF8s: TRawUTF8DynArray;
    Strings: TStringDynArray;
    WideStrings: TWideStringDynArray;
    Objects: array of TObject;
    DynArrays: array of record
      Wrapper: TDynArray;
      Value: pointer;
    end;
    Value: pointer;
    i,a: integer;
    wasString, valid: boolean;
    EndOfObject: AnsiChar;
    Val: PUTF8Char;
    cla: TClass;
    obj: TJSONObject;
    r: packed record EAX, EDX, ECX, EAX2, EDX2: integer; end;
begin

  StackSize := ArgsSize;
  SetLength(Stack,StackSize);
  SetLength(Int64s,ArgsUsedCount[smvv64]);
  SetLength(RawUTF8s,ArgsUsedCount[smvvRawUTF8]);
  SetLength(Strings,ArgsUsedCount[smvvString]);
  SetLength(WideStrings,ArgsUsedCount[smvvWideString]);
  SetLength(Objects,ArgsUsedCount[smvvObject]);
  SetLength(DynArrays,ArgsUsedCount[smvvDynArray]);
  try
    // 1. read the parameters
    while (Par^<>#0) and (Par^<=' ') do inc(Par);
    if Par^<>'[' then begin
      Error := 'Array expected';
      Exit;
    end;
    inc(Par);
    for a:= 0 to high(Args) do
    with Args[a] do begin
      Error := FormatUTF8('Invalid argument %',[ParamName^]);
      case ValueType of
      smvSelf:
        continue; // self parameter is never transmitted
      smvObject: begin
        if TypeInfo^.Kind<>tkClass then
          Exit;
        cla := TypeInfo^.ClassType^.ClassType;
................................................................................
        REGEAX: r.EAX := PInteger(Value)^;
        REGEDX: r.EDX := PInteger(Value)^;
        REGECX: r.ECX := PInteger(Value)^;
        else    move(Value^,Stack[OffsetInStack],SizeInStack);
        end;
    end;
    // 3. execute the method

    asm
      mov eax,StackSize
      mov edx,dword ptr Stack
      add edx,eax // pascal/register convention = left-to-right
      shr eax,2
      jz @z
  @n: sub edx,4
................................................................................
      if (ValueVar=smvv64) and (ValueDirection=smdResult) then begin
        case ValueType of // ordinal/real result values from CPU/FPU registers
        smvDouble, smvDateTime: Res.Add(LoadDouble);
        smvCurrency:            Res.Add(LoadCurrency);
        smvBoolean:             Res.Add(PByte(@r.EAX2)^);
        smvInteger:             Res.Add(r.EAX2);
        smvInt64:               Res.Add(PInt64(@r.EAX2)^);
        else raise Exception.CreateFmt('Invalid result type %d',[ord(ValueType)]);
        end;
      end else
      case ValueType of
      smvObject:     Res.WriteObject(Objects[IndexVar],False,False,true);
      smvDynArray:   Res.AddDynArrayJSON(DynArrays[IndexVar].Wrapper);
      smvBoolean:    Res.Add(PByte(@Int64s[IndexVar])^);
      smvInteger:    Res.Add(PInteger(@Int64s[IndexVar])^);
................................................................................
      smvWideString: Res.AddJSONEscapeW(pointer(WideStrings[IndexVar]));
      end;
      if ValueIsString then
        Res.Add('"',',') else
        Res.Add(',');
    end;
    Res.CancelLastComma;
    Error := ''; // mark success
  finally // manual release memory for Objects[] and DynArrays[]
    for i := 0 to high(Objects) do
      Objects[i].Free;
    for i := 0 to high(DynArrays) do
      DynArrays[i].Wrapper.Clear;
  end;
end;









































































































































































































initialization
  pointer(@SQLFieldTypeComp[sftAnsiText]) := @AnsiIComp;
{$ifndef USENORMTOUPPER}
  pointer(@SQLFieldTypeComp[sftUTF8Text]) := @AnsiIComp;
{$endif}
  DefaultHasher := @crc32; // faster and more accurate than kr32()

end.






>
>
>







 







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











>







 







<







 







|
>
>
>
>
>
>
|
|







 







<
<


<
|






|
|
|
|
<
<







 







|
>










|
>





<






|
>
>
>









|

>
>
>







 







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








>
>
>





<
<
<
<
<
<


>
>
>
>
>
>
>
>
>
>
>





|
<
<
<









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






|







 







<
<
<
<
<







 







<
<
<
<
<
<







 







>
>
>
>
>
>
>
>
>
>
>







 







|



|







 







|





|







 







|







 







|







 







|







|









|



|







 







|







 







|












|







 







|







 







|







 







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







 







|







 







|







 







|







 







|

|







 







|
>
|












|







 







>
>
>
>
>
>
>
>







 







|



|







 







|







 







|







 







|







 







|











|







 







|







|







 







|







 







|







 







|





|







 







|







 







|







 







|











|

|





|





>





|







 







|










|












|







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<











|








|












>


<
|
<
<
<
<
<
<
<
<







 







|








>
>
>







 







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







 







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






>
>
>
>
>
>
>
>
>
>
>







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










>





|
>

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




<
|
<


<
<
>








|






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









>
|
|
|
<
<
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
|
>

>
>
>
>
>
>
>
>
>
|



|
|







|
<
|
<
<








>











|
<

<



<







 







>







 







|







 







|









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








438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
....
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
....
3950
3951
3952
3953
3954
3955
3956

3957
3958
3959
3960
3961
3962
3963
....
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
....
4088
4089
4090
4091
4092
4093
4094


4095
4096

4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107


4108
4109
4110
4111
4112
4113
4114
....
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152

4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
....
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235






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
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
....
4964
4965
4966
4967
4968
4969
4970





4971
4972
4973
4974
4975
4976
4977
....
5030
5031
5032
5033
5034
5035
5036






5037
5038
5039
5040
5041
5042
5043
....
6392
6393
6394
6395
6396
6397
6398
6399
6400
6401
6402
6403
6404
6405
6406
6407
6408
6409
6410
6411
6412
6413
6414
6415
6416
....
8319
8320
8321
8322
8323
8324
8325
8326
8327
8328
8329
8330
8331
8332
8333
8334
8335
8336
8337
....
9869
9870
9871
9872
9873
9874
9875
9876
9877
9878
9879
9880
9881
9882
9883
9884
9885
9886
9887
9888
9889
.....
11830
11831
11832
11833
11834
11835
11836
11837
11838
11839
11840
11841
11842
11843
11844
.....
12108
12109
12110
12111
12112
12113
12114
12115
12116
12117
12118
12119
12120
12121
12122
.....
12483
12484
12485
12486
12487
12488
12489
12490
12491
12492
12493
12494
12495
12496
12497
12498
12499
12500
12501
12502
12503
12504
12505
12506
12507
12508
12509
12510
12511
12512
12513
12514
12515
12516
12517
12518
12519
.....
13269
13270
13271
13272
13273
13274
13275
13276
13277
13278
13279
13280
13281
13282
13283
.....
13301
13302
13303
13304
13305
13306
13307
13308
13309
13310
13311
13312
13313
13314
13315
13316
13317
13318
13319
13320
13321
13322
13323
13324
13325
13326
13327
13328
.....
13330
13331
13332
13333
13334
13335
13336
13337
13338
13339
13340
13341
13342
13343
13344
.....
14459
14460
14461
14462
14463
14464
14465
14466
14467
14468
14469
14470
14471
14472
14473
.....
15183
15184
15185
15186
15187
15188
15189
15190
15191
15192
15193
15194
15195
15196
15197
15198
15199
15200
15201
15202
15203
15204
15205
15206
15207
15208
.....
15408
15409
15410
15411
15412
15413
15414
15415
15416
15417
15418
15419
15420
15421
15422
.....
15558
15559
15560
15561
15562
15563
15564
15565
15566
15567
15568
15569
15570
15571
15572
.....
15677
15678
15679
15680
15681
15682
15683
15684
15685
15686
15687
15688
15689
15690
15691
.....
15697
15698
15699
15700
15701
15702
15703
15704
15705
15706
15707
15708
15709
15710
15711
15712
15713
.....
16041
16042
16043
16044
16045
16046
16047
16048
16049
16050
16051
16052
16053
16054
16055
16056
16057
16058
16059
16060
16061
16062
16063
16064
16065
16066
16067
16068
16069
16070
.....
16072
16073
16074
16075
16076
16077
16078
16079
16080
16081
16082
16083
16084
16085
16086
16087
16088
16089
16090
16091
16092
16093
.....
16819
16820
16821
16822
16823
16824
16825
16826
16827
16828
16829
16830
16831
16832
16833
16834
16835
16836
16837
.....
17208
17209
17210
17211
17212
17213
17214
17215
17216
17217
17218
17219
17220
17221
17222
.....
17487
17488
17489
17490
17491
17492
17493
17494
17495
17496
17497
17498
17499
17500
17501
.....
18212
18213
18214
18215
18216
18217
18218
18219
18220
18221
18222
18223
18224
18225
18226
.....
19168
19169
19170
19171
19172
19173
19174
19175
19176
19177
19178
19179
19180
19181
19182
19183
19184
19185
19186
19187
19188
19189
19190
19191
19192
19193
19194
.....
20210
20211
20212
20213
20214
20215
20216
20217
20218
20219
20220
20221
20222
20223
20224
20225
20226
20227
20228
20229
20230
20231
20232
.....
20253
20254
20255
20256
20257
20258
20259
20260
20261
20262
20263
20264
20265
20266
20267
.....
20286
20287
20288
20289
20290
20291
20292
20293
20294
20295
20296
20297
20298
20299
20300
.....
20900
20901
20902
20903
20904
20905
20906
20907
20908
20909
20910
20911
20912
20913
20914
20915
20916
20917
20918
20919
20920
.....
21394
21395
21396
21397
21398
21399
21400
21401
21402
21403
21404
21405
21406
21407
21408
.....
21564
21565
21566
21567
21568
21569
21570
21571
21572
21573
21574
21575
21576
21577
21578
.....
21659
21660
21661
21662
21663
21664
21665
21666
21667
21668
21669
21670
21671
21672
21673
21674
21675
21676
21677
21678
21679
21680
21681
21682
21683
21684
21685
21686
21687
21688
21689
21690
21691
21692
21693
21694
21695
21696
21697
21698
21699
21700
21701
21702
21703
21704
21705
.....
21710
21711
21712
21713
21714
21715
21716
21717
21718
21719
21720
21721
21722
21723
21724
21725
21726
21727
21728
21729
21730
21731
21732
21733
21734
21735
21736
21737
21738
21739
21740
21741
21742
21743
21744
21745
21746
21747
21748
.....
21776
21777
21778
21779
21780
21781
21782




























21783
21784
21785
21786
21787
21788
21789
21790
21791
21792
21793
21794
21795
21796
21797
21798
21799
21800
21801
21802
21803
21804
21805
21806
21807
21808
21809
21810
21811
21812
21813
21814
21815
21816
21817
21818

21819








21820
21821
21822
21823
21824
21825
21826
.....
21829
21830
21831
21832
21833
21834
21835
21836
21837
21838
21839
21840
21841
21842
21843
21844
21845
21846
21847
21848
21849
21850
21851
21852
21853
21854
.....
21860
21861
21862
21863
21864
21865
21866
21867
21868
21869
21870
21871
21872
21873
21874
21875
21876
21877
21878
21879
21880
21881
21882
21883
21884
21885
21886
21887
21888
.....
21895
21896
21897
21898
21899
21900
21901
21902
21903
21904
21905
21906
21907
21908
21909
21910
21911
21912
21913
21914
21915
21916
21917
21918
21919
21920
21921
21922
21923
21924
21925
21926
21927
21928
21929
21930
21931
21932
21933
21934
21935
21936
21937
21938
21939
21940
21941
21942
21943
21944
21945
21946
21947
21948
21949
21950
21951
21952
21953
21954
21955
21956
21957
21958
21959
21960
21961
21962
21963
21964
21965
21966
21967
21968
21969
21970
21971
21972
21973
21974
21975
21976
21977
21978
21979
21980
21981
21982
21983
21984
21985
21986
21987
21988
21989
21990
21991
21992
21993
21994
21995
21996
21997
21998
21999
22000
22001
22002
22003
22004
22005
22006
22007
22008
22009
22010
22011
22012
22013
22014
22015
22016
22017
22018
22019
22020
22021
22022
22023
22024
22025
22026
22027
22028
22029
22030
22031
22032
22033
22034
22035
22036
22037
22038
22039
22040
22041
22042
22043
22044
22045
22046
22047
22048
22049
22050
22051
22052
22053
22054
22055
22056
22057
22058
22059

22060

22061
22062


22063
22064
22065
22066
22067
22068
22069
22070
22071
22072
22073
22074
22075
22076
22077
22078
22079
22080



































22081











22082
22083
22084
22085
22086
22087
22088
22089
22090
22091
22092
22093
22094


22095
22096
22097
22098
22099
22100
22101

22102
22103
22104
22105
22106
22107
22108
22109
22110
22111
22112
22113
22114
22115
22116
22117
22118
22119
22120
22121
22122
22123
22124
22125
22126
22127
22128
22129
22130
22131
22132
22133
22134
22135
22136
22137
22138
22139
22140
22141
22142
22143
22144
22145
22146

22147


22148
22149
22150
22151
22152
22153
22154
22155
22156
22157
22158
22159
22160
22161
22162
22163
22164
22165
22166
22167
22168

22169

22170
22171
22172

22173
22174
22175
22176
22177
22178
22179
.....
22237
22238
22239
22240
22241
22242
22243
22244
22245
22246
22247
22248
22249
22250
22251
.....
22270
22271
22272
22273
22274
22275
22276
22277
22278
22279
22280
22281
22282
22283
22284
.....
22291
22292
22293
22294
22295
22296
22297
22298
22299
22300
22301
22302
22303
22304
22305
22306
22307
22308
22309
22310
22311
22312
22313
22314
22315
22316
22317
22318
22319
22320
22321
22322
22323
22324
22325
22326
22327
22328
22329
22330
22331
22332
22333
22334
22335
22336
22337
22338
22339
22340
22341
22342
22343
22344
22345
22346
22347
22348
22349
22350
22351
22352
22353
22354
22355
22356
22357
22358
22359
22360
22361
22362
22363
22364
22365
22366
22367
22368
22369
22370
22371
22372
22373
22374
22375
22376
22377
22378
22379
22380
22381
22382
22383
22384
22385
22386
22387
22388
22389
22390
22391
22392
22393
22394
22395
22396
22397
22398
22399
22400
22401
22402
22403
22404
22405
22406
22407
22408
22409
22410
22411
22412
22413
22414
22415
22416
22417
22418
22419
22420
22421
22422
22423
22424
22425
22426
22427
22428
22429
22430
22431
22432
22433
22434
22435
22436
22437
22438
22439
22440
22441
22442
22443
22444
22445
22446
22447
22448
22449
22450
22451
22452
22453
22454
22455
22456
22457
22458
22459
22460
22461
22462
22463
22464
22465
22466
22467
22468
22469
22470
22471
22472
22473
22474
22475
22476
22477
22478
22479
22480
22481
22482
22483
22484
22485
22486
22487
22488
22489
22490
22491
22492
22493
22494
22495
22496
22497
22498
22499
22500
22501
22502
22503
22504
22505
22506
22507
22508
22509
22510
22511
22512
22513
22514
      by TSQLRestServerStaticInMemory.SaveToBinary)
    - fixed issue with TAuthSession.IDCardinal=0 after 76 connections
    - fixed issue in SetInt64Prop() with a setter method
    - fixed potential issue in TSQLTable.SearchValue in case of invalid Client
      supplied parameter (now checks TSQLRest class type)

  Version 1.16
    - added dedicated Exception classes (EORMException, EParsingException,
      ESecurityException, ECommunicationException, EBusinessLayerException,
      EServiceException) all inheriting from SynCommons.ESynException
    - added a generic JSON error message mechanism within the framework
      (including error code as integer and text, with custom error messages
      in RecordCanBeUpdated method and also in TSQLRestServerCallBackParams)
    - the TSQLRestServerCallBack method prototype has been modified to supply
      "var aParams: TSQLRestServerCallBackParams: cardinal" as unique parameter:
      this is a CODE BREAK change and you shall refresh ALL your server-side
      code to match the new signature (using a record passed by value as
................................................................................

  /// maximum handled dimension for TSQLRecordRTree
  // - this value is the one used by SQLite3 R-Tree virtual table
  RTREE_MAX_DIMENSION = 5;


type
  /// generic parent class of all custom Exception types of this unit
  EORMException = class(ESynException);

  /// exception raised in case of wrong Model definition
  EModelException = class(EORMException);

  /// exception raised in case of unexpected parsing error
  EParsingException = class(EORMException);

  /// exception raised in case of a Client-Server communication error
  ECommunicationException = class(EORMException);

  /// exception raised in case of an error in project implementation logic
  EBusinessLayerException = class(EORMException);

  /// exception raised in case of any authentication error
  ESecurityException = class(EORMException);

  /// exception dedicated to interface based service implementation
  EServiceException = class(EORMException);

  TSQLModel = class;
  TSQLRest = class;
  TSQLRestClient = class;

{$M+} { we need the RTTI information to be compiled for the published
        properties of these classes and their children (like TPersistent),
        to enable ORM - must be defined at the forward definition level }
  TSQLRecord = class;      // published properties = ORM fields/columns
  TSQLAuthUser = class;

  TSQLRestServer = class;  // published events = RESTful callbacks handlers
  TSQLRestClientURI = class;

{$M-}

  /// class-reference type (metaclass) of TSQLRecord
  TSQLRecordClass = class of TSQLRecord;

  PSQLRecordClass = ^TSQLRecordClass;
................................................................................

  /// all our services shall inherit from this interface
  // - in the current implementation, we rely on a single inheritance from
  // IService, in order to simplify the implementation
  IService = interface(IInvokable)
  end;


  /// the possible Server-side instance implementation patterns for Services
  // - each interface-based service will be implemented by a corresponding
  // class instance on the server: this parameter is used to define how
  // class instances are created and managed
  // - on the Client-side, each instance will be handled depending on the
  // server side implementation (i.e. with sicClientDriven behavior if necessary)
  // - sicSingle: one object instance is created per call - this is the
................................................................................
    Args: TServiceMethodArgumentDynArray;
    /// needed CPU stack size (in bytes) for all arguments
    ArgsSize: cardinal;
    /// contains all used kind of arguments
    ArgsUsed: set of TServiceMethodValueType;
    /// contains the count of variables for all used kind of arguments
    ArgsUsedCount: array[TServiceMethodValueVar] of integer;
    /// method index in the original interface
    // - our custom methods start at index 3, since QueryInterface, _AddRef,
    // and _Release methods are always defined by default
    MethodIndex: integer;
    /// execute the corresponding method of a given TInterfacedObject instance
    // - will retrieve a JSON array of parameters from Par
    // - will append a JSON array of results in Res, or set an Error message
    function InternalExecute(Instance: pointer; Entry: PInterfaceEntry;
      Par: PUTF8Char; Res: TTextWriter): boolean;
  end;

  /// describe a service provider methods
  TServiceMethodDynArray = array of TServiceMethod;

  /// an abstract service provider, as registered in TServiceContainer
  // - this will be either implemented by a TInterfacedObjectClass on the server,
................................................................................
  protected
    fInterfaceTypeInfo: PTypeInfo;
    fInterfaceIID: TGUID;
    fInterfaceURI: RawUTF8;
    fInterfaceMangledURI: RawUTF8;
    fInstanceCreation: TServiceInstanceImplementation;
    fRest: TSQLRest;


    fMethodsCount: integer;
    fMethods: TServiceMethodDynArray;

    fSharedInstance: TInterfacedObject;
  public
    /// initialize the service provider parameters
    // - it will check and retrieve all methods of the supplied interface,
    // and prepare all internal structures for its serialized execution
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation);
    /// retrieve an instance of this interface
    // - this virtual method will be overriden to reflect the expected
    // behavior of client or server side
    function Get(out Obj): Boolean; virtual; abstract;


    /// get the index in Methods[] of a given method name
    // - returns -1 if the method name is unknown
    function MethodIndex(const aMethod: RawUTF8): integer;
    /// the associated RESTful instance
    property Rest: TSQLRest read fRest;
    /// the registered Interface low-level Delphi RTTI type
    property InterfaceTypeInfo: PTypeInfo read fInterfaceTypeInfo;
................................................................................
    // - only relevant on the server side; on the client side, this class will
    // be accessed only to retrieve a remote access instance, i.e. sicSingle
    property InstanceCreation: TServiceInstanceImplementation read fInstanceCreation;
    /// the declared methods
    property Methods: TServiceMethodDynArray read fMethods;
  end;

  /// server-side service provider use this to store one internal instance
  // - used by TServiceFactoryServer in sicClientDriven mode
  TServiceFactoryServerInstance = record
    /// the internal Instance ID, as remotely sent in "id":1
    // - is set to 0 when an entry in the array is free
    InstanceID: Cardinal;
    /// last time stamp access of this instance
    LastAccess: Cardinal;
    /// the implementation instance itself
    Instance: TInterfacedObject;
  end;

  /// server-side service provider use this to store its internal instances
  // - used by TServiceFactoryServer in sicClientDriven mode
  TServiceFactoryServerInstanceDynArray = array of TServiceFactoryServerInstance;

  /// a service provider implemented on the server side
  TServiceFactoryServer = class(TServiceFactory)
  protected

    fInstances: TServiceFactoryServerInstanceDynArray;
    fInstance: TDynArray;
    fInstancesCount: integer;
    fInstanceCurrentID: integer;
    fInstanceTimeOut: cardinal;
    fInstanceLock: TRTLCriticalSection;
    fImplementationClass: TInterfacedObjectClass;
    fSharedInterface: IInterface;
    function ClientDrivenRetrieve(var Inst: TServiceFactoryServerInstance;
      aMethodIndex: integer): boolean;
  public
    /// initialize the service provider on the server side
    // - expect an implementation class
    // - for sicClientDriven, a time out (in seconds) can be defined
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation;
      aImplementationClass: TInterfacedObjectClass;
      aTimeOutSec: cardinal=30*60); reintroduce;
    /// release all used memory
    // - e.g. any internal TServiceFactoryServerInstance instances
    destructor Destroy; override;
    /// retrieve an instance of this interface from the server side
    // - sicClientDriven kind of instance creation will behave the same as sicSingle
    function Get(out Obj): Boolean; override;
    /// call a given method of this service provider
    // - aMethodIndex is the index in Methods[]
    // - if aMethodIndex=-1, then it will free/release corresponding aInstanceID
    // (is called  e.g. from {"method":"free", "params":[], "id":1234} )
    // - aParamsJSONArray is e.g. '[1,2]' i.e. a true JSON array, which will
    // contain the incoming parameters in the same exact order than the
    // corresponding implemented interface method
................................................................................
    // instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    function ExecuteMethod(aSession: cardinal; aMethodIndex, aInstanceID: Integer;
      aParamsJSONArray: PUTF8Char; var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
  end;

  /// a service provider implemented on the client side
  TServiceFactoryClient = class(TServiceFactory)
  protected
    fClient: TSQLRestClientURI;
    fRemoteClassName: RawUTF8;
    fFakeVTable: array of pointer;
    fFakeStub: PByteArray;
    function CallClient(const aMethod: RawUTF8; const aParams: RawUTF8='';
      aResult: PRawUTF8=nil; aClientDrivenID: PCardinal=nil): boolean;
  public
    /// initialize the service provider parameters
    // - it will check and retrieve all methods of the supplied interface,
    // and prepare all internal structures for its serialized execution
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation);
    /// finalize the service provider used structures
    // - especially the internal shared VTable and code Stub
    destructor Destroy; override;
    /// retrieve an instance of this interface from the server side
    function Get(out Obj): Boolean; override;
  end;

  /// a global services provider class
  // - used to maintain a list of interfaces implementation
  TServiceContainer = class
  protected
    fRest: TSQLRest;
    fList: TObjectList;
    fExpectMangledURI: boolean;
    procedure CheckInterface(const aInterfaces: array of PTypeInfo);
    /// retrieve a service provider from its URI
    function GetService(const aURI: RawUTF8): TServiceFactory;
  public
    /// initialize the list
    constructor Create(aRest: TSQLRest);
    /// release all registered services
    destructor Destroy; override;






    /// return the number of registered service interfaces
    function Count: integer;
    /// retrieve a service provider from its index in the list
    // - returns nil if out of range index
    function Index(aIndex: integer): TServiceFactory; overload; {$ifdef HASINLINE}inline;{$endif}
    /// retrieve a service provider from its GUID
    // - on match, it  will return the service the corresponding interface factory
    // - returns nil if the GUID does not match any registered interface
    function GUID(const aGUID: TGUID): TServiceFactory; overload;
    /// retrieve a service provider from its type information
    // - on match, it  will return the service the corresponding interface factory
    // - returns nil if the type information does not match any registered interface
    function Info(aTypeInfo: PTypeInfo): TServiceFactory; overload; virtual;
    /// retrieve a service provider from its URI
    // - it expects the supplied URI variable  to be e.g. '00amyWGct0y_ze4lIsj2Mw'
    // or 'Calculator', depending on the ExpectMangledURI property
    // - on match, it  will return the service the corresponding interface factory
    // - returns nil if the URI does not match any registered interface
    property Services[const aURI: RawUTF8]: TServiceFactory read GetService; default;



    /// the associated RESTful instance
    property Rest: TSQLRest read fRest;
    /// set if the URI is expected to be mangled from the GUID
    // - by default (FALSE), the clear service name is expected to be supplied at
    // the URI level (e.g. 'Calculator')
    // - if this property is set to TRUE, the mangled URI value will be expected
    // instead (may enhance security) - e.g. '00amyWGct0y_ze4lIsj2Mw'
    property ExpectMangledURI: boolean read fExpectMangledURI write fExpectMangledURI;
  end;

  /// a services provider class to be used on the server side
  // - this will maintain a list of true implementation classes
  TServiceContainerServer = class(TServiceContainer)
  public
    /// method called on the server side to register a service via its interface(s)
    // and a specified implementation class
    // - will raise an exception on error
    // - will return true if some interfaces have been added
    function AddImplementation(aImplementationClass: TInterfacedObjectClass;
      const aInterfaces: array of PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation): boolean;
  end;

  /// a services provider class to be used on the client side
  // - this will maintain a list of fake implementation classes, which will
  // remotely call the server to make the actual process
  TServiceContainerClient = class(TServiceContainer)
  protected
  public
    /// retrieve a service provider from its type information
    // - this overriden method will register the internface, if was not yet
    // - in this case, the interface will be registered with sicClientDriven
    // implementation method
    function Info(aTypeInfo: PTypeInfo): TServiceFactory; overload; override;
    /// method called on the client side to register a service via its interface(s)
    // - will raise an exception on error
    // - will return true if some interfaces have been added
    function AddInterface(const aInterfaces: array of PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation): boolean;
  end;

  /// for TSQLRestCache, stores a table values
  TSQLRestCacheEntryValue = record
    /// corresponding ID
    ID: integer;
    /// GetTickCount value when this cached value was stored
    // - equals 0 licwhen there is no JSON value cached
    TimeStamp: cardinal; 
    /// JSON encoded UTF-8 serialization of the record
    JSON: RawUTF8;
  end;

  /// for TSQLRestCache, stores all tables values
  TSQLRestCacheEntryValueDynArray = array of TSQLRestCacheEntryValue;
................................................................................
  TURIMapRequest = function(url, method, SendData: PUTF8Char; Resp, Head: PPUTF8Char): Int64Rec; cdecl;

{$ifdef MSWINDOWS}
  {$define WITHSTATPROCESS}
  // if defined, the server statistics will contain precise working time process
{$endif}






  /// used for statistics update in TSQLRestServer.URI()
  TSQLRestServerStats = class(TPersistent)
  private
    /// used to determine if something changed
    fLastIncomingBytes: cardinal;
    /// current count of connected clients
    fClientsCurrent,
................................................................................
    property ServiceCalls: cardinal read fServices;
{$ifdef WITHSTATPROCESS}
    /// the global time spent in the server process
    property ProcessTime: RawUTF8 read GetProcessTimeString;
{$endif}
  end;







  ///  used to define how to trigger Events on record update
  // - see TSQLRestServer.OnUpdateEvent property
  // - returns true on success, false if an error occured (but action must continue)
  // - to be used only server-side, not to synchronize some clients: the framework
  // is designed around a stateless RESTful architecture (like HTTP/1.1), in which
  // clients ask the server for refresh (see TSQLRestClientURI.UpdateFromServer)
  TNotifySQLEvent = function(Sender: TSQLRestServer; Event: TSQLEvent;
................................................................................
    {{ wrapper to the protected URI method to call a method on the server, using
      a ModelRoot/[TableName/[ID/]]MethodName RESTful PUT request
      - returns the HTTP error code (e.g. 200 on success)
      - this version will use a PUT with the supplied raw UTF-8 data }
    function CallBackPut(const aMethodName, aSentData: RawUTF8;
      out aResponse: RawUTF8; aTable: TSQLRecordClass=nil; aID: integer=0;
      aResponseHead: PRawUTF8=nil): integer;
    /// register a Service on the client side via its interface
    // - this methods expects a list of interfaces to be registered to the client
    // (e.g. [TypeInfo(IMyInterface)])
    // - instance implementation pattern will be set by the appropriate parameter
    // - will return true on success, false if registration failed (e.g. if any of
    // the supplied interfaces is not correct or is not available on the server)
    // - that is, server side will be called to check for the availability of
    // this interface(s)
    function ServiceRegister(const aInterfaces: array of PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation=sicSingle): boolean; virtual;

    /// is set to TRUE, all BLOB fields are transferred between the Client and
    // the remote Server
    // - i.e. Add() Update() will use Blob-related RESTful PUT/POST request
    // - i.e. Retrieve() will use Blob-related RESTful GET request
    // - note that the Refresh method won't handle BLOB fields, even if this
    // property setting is set to TRUE
    // - by default, this property is set to FALSE, which setting will spare
................................................................................
    // fastest common call with one TSQLRecordClass
    result := TSQLRecordClass(Tables[0]).RecordProps.
      SQLFromSelectWhere(SQLSelect,SQLWhere);
    exit;
  end;
  // 'SELECT T1.F1,T1.F2,T1.F3,T2.F1,T2.F2 FROM T1,T2 WHERE ..' e.g.
  if PtrUInt(high(Tables))>high(Props) then
    raise EModelException.Create('SQLFromSelectWhere');
  for i := 0 to high(Tables) do begin
    Props[i] := TSQLRecordClass(Tables[i]).RecordProps;
    if Props[i]=nil then
      raise EModelException.CreateFmt('SQLFromSelectWhere: not in Model: %s',
        [Tables[i].ClassName]);
  end;
  if SQLSelect='*' then
     // don't send BLOB values to query: retrieve all other fields
    if high(Tables)=0 then
      result := 'SELECT '+Props[0].SQLTableSimpleFields[true,false] else begin
      result := 'SELECT '+Props[0].SQLTableSimpleFields[true,true];
................................................................................
      FU := GetJSONField(P,P);
      inc(Len,length(FU));
      if P=nil then break;
      Fields2[FieldsCount] := FU;
      Values[FieldsCount] := GetSQLValue; // update EndOfObject
      inc(FieldsCount);
      if FieldsCount=MAX_SQLFIELDS then
        raise EParsingException.Create('Too many inlines in GetJSONObjectAsSQL');
    until EndOfObject in [#0,'}',']'];
    Return(@Fields2,@Values,InlinedParams);
  end else begin
    // get "VAL1","VAL2"...
    if RowID>0 then
      raise EParsingException.Create('GetJSONObjectAsSQL(expanded) won''t handle RowID');
    FieldsCount := length(Fields);
    for F := 0 to FieldsCount-1 do begin
      inc(Len,length(Fields[F]));
      Values[F] := GetSQLValue; // update EndOfObject
    end;
    Return(@Fields[0],@Values,InlinedParams);
  end;
................................................................................
procedure TSQLRecordFill.SetMappedFieldsExpandedJSONWriter(aClass: TSQLRecordClass;
  W: TJSONWriter);
var i,n: integer;
begin // update ID, TModTime and FillPrepare mapped fields
  if (self=nil) or (W=nil) or (aClass=nil) then
    exit;
  if fTableMapRecordManyInstances<>nil then
    raise EBusinessLayerException.Create('SetMappedFieldsExpandedJSONWriter after FillPrepareMany');
  if fTableMapFieldsColNames=nil then
  with aClass.RecordProps do begin // init fTableMapFields[Max,ColNames] from mapping
    n := 1;
    for i := 0 to high(Fields) do
      if FieldType[i]=sftModTime then begin
        Include(fTableMapFields,i);
        inc(n);
................................................................................
procedure TSQLRecord.FillRow(aRow: integer; aDest: TSQLRecord=nil);
begin
  if self<>nil then
    if aDest=nil then
      fFill.Fill(aRow) else
      if fFill.fTableMapRecordManyInstances=nil then
        fFill.Fill(aRow,aDest) else
        raise EBusinessLayerException.Create('FillRow() forbidden after FillPrepareMany');
end;

function TSQLRecord.FillOne: boolean;
begin
  if (self=nil) or (fFill=nil) or (fFill.Table=nil) or
     (fFill.Table.RowCount=0) or // also check if FillTable is emtpy
     (cardinal(fFill.FillCurrentRow)>cardinal(fFill.Table.RowCount)) then
................................................................................
      case Kind of
      rFTS3:  result := result+'fts3(';
      rFTS4:  result := result+'fts4(';
      rRTree: result := result+'rtree(ID,';
      rCustomForcedID, rCustomAutoID: begin
        M := aModel.VirtualTableModule(self);
        if M=nil then
          raise EModelException.CreateFmt('No registered module for %s',[ClassName]);
        result := result+M.ModuleName+'('+GetVirtualTableSQLCreate(RecordProps);
      end;
      end;
      case Kind of
      rFTS3, rFTS4: begin
        for i := 0 to n-1 do
          if FieldType[i]<>sftUTF8Text then
            raise EModelException.CreateFmt('%s.%s: FTS3/FTS4 field must be RawUTF8',
              [SQLTableName,FieldsName[i]]) else
            Fields[i]^.AppendName(result,',');
        if InheritsFrom(TSQLRecordFTS3Porter) or
           InheritsFrom(TSQLRecordFTS4Porter) then
          result := result+' tokenize=porter)' else
          result := result+' tokenize=simple)';
      end;
      rRTree: begin
        if (n<3) or (n>RTREE_MAX_DIMENSION*2+1) or (n and 2<>1) then
          raise EModelException.CreateFmt('%d: %s RTREE expects an odd 3..%d column number',
            [n,SQLTableName,RTREE_MAX_DIMENSION*2+1]);
        for i := 0 to n-1 do
          if FieldType[i]<>sftFloat then
            raise EModelException.CreateFmt('%s.%s: RTREE field must be double',
              [SQLTableName,FieldsName[i]]) else
            Fields[i]^.AppendName(result,',');
        result[length(result)] := ')';
      end;
      end;
    end else begin
      // inherits from TSQLRecord: create a "normal" SQLite3 table
................................................................................
        FieldType := Props.FieldType[f];
        FieldName := @Props.Fields[f]^.ShortName;
        FieldRecordClass := TSQLRecordClass(PT^.ClassType^.ClassType);
      end;
    end;
    sftMany:
      if GetTableIndex(TSQLRecordClass(PT^.ClassType^.ClassType))<0 then
        raise EModelException.CreateFmt('%s must include %s for %s.%s',
          [ClassName,TSQLRecordClass(PT^.ClassType^.ClassType).ClassName,
           Tables[aIndex].ClassName,Props.FieldsName[f]]);
    end;
  end;
  if Last then
    QuickSortRawUTF8(fTablesName,length(fTablesName),@fTablesNameIndex,StrIComp);
end;
................................................................................
    aTableIndexCreated^ := n;
  result := true;
end;

constructor TSQLModel.Create(CloneFrom: TSQLModel);
begin
  if CloneFrom=nil then
    raise EModelException.Create('TSQLModel.Create(CloneFrom=nil)');
  Create(CloneFrom.fTables,CloneFrom.fRoot);
  fVirtualTableModule := CloneFrom.fVirtualTableModule;
end;

constructor TSQLModel.Create(TabParameters: PSQLRibbonTabParameters;
  TabParametersCount, TabParametersSize: integer;
  const NonVisibleTables: array of TSQLRecordClass; const aRoot: RawUTF8);
var i: integer;
    Tables: array of TSQLRecordClass;
begin
  if (TabParameters=nil) or (TabParametersCount<=0) or
     (cardinal(TabParametersSize)<sizeof(TSQLRibbonTabParameters)) then
    raise EModelException.Create('TSQLModel.Create(TabParameters?)');
  SetLength(Tables,TabParametersCount+length(NonVisibleTables));
  for i := 0 to TabParametersCount-1 do begin
    Tables[i] := TabParameters^.Table;
    inc(PtrUInt(TabParameters),TabParametersSize);
  end;
  for i := 0 to high(NonVisibleTables) do
    Tables[i+TabParametersCount] := NonVisibleTables[i];
................................................................................
end;

constructor TSQLModel.Create(const Tables: array of TSQLRecordClass; const aRoot: RawUTF8='root');
var N, i: integer;
begin
  N := length(Tables);
  if N>sizeof(SUPERVISOR_ACCESS_RIGHTS.Get)*8 then // TSQLAccessRights bits size
    raise EModelException.CreateFmt('%s has too many Tables: %d>%d',
      [ClassName,N,sizeof(SUPERVISOR_ACCESS_RIGHTS.Get)*8]); // e.g. N>64
  // set the Tables to be associated with this Model, as TSQLRecord classes
  SetLength(fTables,N);
  move(Tables[0],fTables[0],N*Sizeof(Tables[0]));
  SetLength(fTablesName,N);
  SetLength(fTablesNameIndex,N);
  SetLength(TableProps,N);
................................................................................
    result := SetCache(PSQLRecordClass(aRecord)^,aRecord.fID);
end;

constructor TSQLRestCache.Create(aRest: TSQLRest);
var i: integer;
begin
  if aRest=nil then
    EBusinessLayerException.CreateFmt('%s.Create',[ClassName]);
  fRest := aRest;
  SetLength(fCache,length(fRest.Model.Tables));
  for i := 0 to high(fCache) do
    with fCache[i] do begin
      Value.Init(TypeInfo(TSQLRestCacheEntryValueDynArray),Values,@Count);
      Value.Compare := SortDynArrayInteger; // will search/sort by ID
      InitializeCriticalSection(Mutex);
................................................................................
    result := URI(Model.getURICallBack(aMethodName,aTable,aID),
      'PUT',nil,aResponseHead,@aSentData).Lo;
{$ifdef WITHLOG}
    SQLite3Log.Add.Log(sllServiceReturn,'Result=%',result);
{$endif}
  end;
end;

function TSQLRestClientURI.ServiceRegister(const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation=sicSingle): boolean; 
begin
  result := False;
  if (self=nil) or (high(aInterfaces)<0) then
    exit;
  if fServices=nil then
    fServices := TServiceContainerClient.Create(self);
  result := (fServices as TServiceContainerClient).AddInterface(
    aInterfaces,aInstanceCreation);
end;

procedure TSQLRestClientURI.BatchAbort;
begin
  if self<>nil then begin
    fBatchCount := 0;
    fBatchTable := nil;
    fBatch.CancelAll;
................................................................................
    P := pointer(result);
    repeat
      Read := FileRead(Handle,P^,L);
      if Read=0 then begin
        sleep(100); // nothing available -> wait a little and retry
        Read := FileRead(Handle,P^,L);
        if Read=0 then begin // server may be down -> abort
          raise ECommunicationException.Create('ReadString');
          exit;
        end;
      end;
      inc(P,Read);
      dec(L,Read);
    until L=0; // loop until received all expected data 
  end;
................................................................................
end;

destructor TSQLRestServer.Destroy;
var i: integer;
begin
{$ifdef WITHLOG}
  if not InheritsFrom(TSQLRestServerStatic) then
    SQLite3Log.Add.Log(sllInfo,Stats.DebugMessage,self);
{$endif}
{$ifdef MSWINDOWS}
  if GlobalURIRequestServer=self then begin
    GlobalURIRequestServer := nil;
    sleep(200); // way some time any request is finished in another thread
  end;
  // close any opened server
................................................................................
  if (ppBeg=0) or (PosEx(RawUTF8('):'),SQL,ppBeg+2)=0) then
    // SQL code with no valid :(...): internal parameters -> leave maxParam=0
    exit;
  // compute GenericSQL from SQL, converting :(...): into ?
  Gen := PUTF8Char(pointer(result))+ppBeg-1; // Gen^ just before :(
  P := PUTF8Char(pointer(SQL))+ppBeg+1; // P^ just after :(
  repeat
    Gen^ := '?'; // replace :(...): by ?
    inc(Gen);
    if length(Values)<=maxParam then
      SetLength(Values,maxParam+8);
    P := SQLParamContent(P,Types[maxParam],Values[maxParam]);
    if P=nil then begin
      maxParam := 0;
      result := SQL;
................................................................................
      inc(P);
    end;
    if P^=#0 then
      Break;
    inc(P,2);
    inc(maxParam);
    if maxParam>high(Types) then
      raise EParsingException.Create('Too many :(): params');
  until false;
  // return the correct SQL statement, with params in Values[]
  SetLength(result,Gen-pointer(result));
  inc(maxParam);
end;

procedure TSQLRestServer.SetNoAJAXJSON(const Value: boolean);
var i: integer;
begin
................................................................................
  const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation): boolean;
begin
  result := False;
  if (self=nil) or (aImplementationClass=nil) or (high(aInterfaces)<0) then
    exit;
  if fServices=nil then
    fServices := TServiceContainerServer.Create(self);
  result := (fServices as TServiceContainerServer).
    AddImplementation(aImplementationClass,aInterfaces,aInstanceCreation);
end;

function TSQLRestServer.ServiceCall(aSession: cardinal; const aURI, aSentData: RawUTF8;
  var aResp, aHead, aErrorMsg: RawUTF8; var aResult: cardinal): boolean;
var Service: TServiceFactory;
    method, JSON: RawUTF8;
    Values: TPUtf8CharDynArray;
    m, id: integer;
begin
  result := false;
  if aSentData='' then
    exit;
  Service := Services[aURI];
  if Service=nil then
    exit;
  inc(fStats.fServices);
  SetString(JSON,PAnsiChar(aSentData),length(aSentData)); // in-place modif.
  JSONDecode(JSON,['METHOD','PARAMS','ID'],Values,True);
  if Values[0]=nil then begin
    aErrorMsg := 'Method name required';
................................................................................
  end;
  if Values[1]=nil then begin
    aErrorMsg := 'Parameters required';
    exit;
  end;
  method := Values[0];
  id := GetCardinal(Values[2]);
  if IdemPropNameU(method,'ClassName') then begin
    // "method":"ClassName" to retrieve the implementation class and test existing
    aResult := 200; // OK
    aResp := '{"result":["'+RawUTF8(
      (Service as TServiceFactoryServer).fImplementationClass.ClassName)+'"],"id":0}';
    result := true;
    exit; // "id":0 for this method -> no instance was created
  end;
  if (id<>0) and IdemPropNameU(method,'free') then
    // "method":"free" to release the sicClientDriven server-side instance
    m := -1 else begin
    // regular method
    m := Service.MethodIndex(method);
    if m<0 then begin
      aErrorMsg := 'Unknown method';
................................................................................

constructor TSQLRestClientURIDll.Create(aModel: TSQLModel; const DllName: TFileName);
var aRequest: TURIMapRequest;
    aDLL: cardinal;
begin
  aDLL := LoadLibrary(pointer(DllName));
  if aDLL=0 then
    raise ECommunicationException.Create(DllName);
  aRequest := GetProcAddress(aDLL,'URIRequest');
  if (@aRequest=nil) or (aRequest(nil,nil,nil,nil,nil).Lo<>404) then begin
    FreeLibrary(aDLL);
    raise ECommunicationException.CreateFmt('%s doesn''t export a valid URIRequest function',[DllName]);
  end;
  Create(aModel,aRequest);
  fLibraryHandle := aDLL;
end;

constructor TSQLRestClientURIDll.Create(aModel: TSQLModel; aRequest: TURIMapRequest);
begin
................................................................................
    if WaitNamedPipe(pointer(fPipeName),1000) then
      // 1000 since we have sleep(128) in TSQLRestServerNamedPipe.EngineExecute
      CreatePipe;
  end;
  if Pipe=Invalid_Handle_Value then begin // server must exist
    if (aModel<>nil) and (aModel.Owner=self) then
      aModel.Free; // avoid memory leak
    raise ECommunicationException.CreateFmt(
      '%s can''t connect to server "%s"'#13'via "%s":'#13'%s',
      [ClassName,ApplicationName,fPipeName,SysErrorMessage(GetLastError)]);
  end;
{$ifdef ANONYMOUSNAMEDPIPE}
  RevertToSelf; // we just needed to be anonymous during pipe connection
{$endif}
  inherited Create(aModel);
................................................................................
constructor TSQLRestServerStaticInMemory.Create(aClass: TSQLRecordClass; aServer: TSQLRestServer;
  const aFileName: TFileName = ''; aBinaryFile: boolean=false);
var JSON: RawUTF8;
    Stream: TStream;
begin
  inherited Create(aClass,aServer,aFileName,aBinaryFile);
  if fStoredClassProps.Kind in INSERT_WITH_ID then
    raise EModelException.CreateFmt('%s: %s virtual table can''t be static',
      [fStoredClassProps.SQLTableName,aClass.ClassName]);
  fBinaryFile := aBinaryFile;
  fValue := TObjectList.Create;
  if (fFileName<>'') and FileExists(fFileName) then begin
    if aBinaryFile then begin
      Stream := TSynMemoryStreamMapped.Create(fFileName);
      try
................................................................................
{ TSQLRestServerStatic }

constructor TSQLRestServerStatic.Create(aClass: TSQLRecordClass;
  aServer: TSQLRestServer; const aFileName: TFileName; aBinaryFile: boolean);
begin
  inherited Create(nil,false);
  if aClass=nil then
    raise EBusinessLayerException.CreateFmt('%s.Create expect a class',[ClassName]);
  fStoredClass := aClass;
  fStoredClassProps := aClass.RecordProps;
  fIsUnique := fStoredClassProps.IsUniqueFieldsBits;
  if aServer<>nil then begin
    fOwner := aServer;
    fModel := aServer.Model;
    fNoAJAXJSON := aServer.fNoAJAXJSON; // expanded as main Server
................................................................................

constructor TSQLRestClientURIMessage.Create(aModel: TSQLModel;
  const ServerWindowName: string; ClientWindow: HWND; TimeOutMS: cardinal);
begin
  inherited Create(aModel);
  fServerWindow := FindWindow(pointer(ServerWindowName),nil);
  if fServerWindow=0 then
    raise ECommunicationException.CreateFmt('No "%s" window available - server may be down',
      [ServerWindowName]);
  fClientWindow := ClientWindow;
  fTimeOutMS := TimeOutMS;
end;

constructor TSQLRestClientURIMessage.Create(aModel: TSQLModel;
  const ServerWindowName, ClientWindowName: string; TimeOutMS: cardinal);
var H: HWND;
begin
  H := CreateInternalWindow(ClientWindowName,self);
  if H=0 then
    raise ECommunicationException.CreateFmt('Impossible to create "%s" client window',
      [ClientWindowName]);
  fClientWindowName := ClientWindowName;
  Create(aModel,ServerWindowName,H,TimeOutMS);
end;

destructor TSQLRestClientURIMessage.Destroy;
begin
................................................................................
    CP := InternalClassProp(aClassType);
    if CP=nil then
      exit;
    P := @CP^.PropList;
    for pi := 0 to CP^.PropCount-1 do begin
      // 0. check that this property is not an ID/RowID (handled separately)
      if IsRowID(P^.ShortName) then
        raise EModelException.CreateFmt('%s should not include a %s published property',
          [aTable.ClassName,P^.ShortName]);
      // 1. store RTTI for this property
      FieldType[f] := P^.PropType^^.SQLFieldType;
      Fields[f] := P;
      FieldName := RawUTF8(P^.ShortName);
      for j := 0 to f-1 do
        if IdemPropNameU(FieldsName[j],FieldName) then
          raise EModelException.CreateFmt('dup property name %s in %s',
            [FieldName,aTable.ClassName]);
      FieldsName[f] := FieldName;
      fFieldsNameSorted[f] := FieldName;
      fFieldsNameIndex[f] := f; 
      // 2. handle unique fields, i.e. if marked as "stored false"
      Unique := not P^.IsStored;
      if Unique then begin
................................................................................
          ManyFields[nMany] := P;
          inc(nMany);
        end;
        sftBlobDynArray: begin
          if P^.Index<>0 then
            for j := 0 to nDynArray-1 do
            if DynArrayFields[j]^.Index=P^.Index then
              raise EModelException.CreateFmt('dup index %d for %s.%s and %s properties',
                [P^.Index,aTable.ClassName,P^.ShortName,DynArrayFields[j]^.ShortName]);
          DynArrayFields[nDynArray] := P;
          inc(nDynArray);
          goto Simple;
        end;
        else begin
          // this code follows NOT_SIMPLE_FIELDS const 
................................................................................
  SQLTableName := GetDisplayNameFromClass(aTable);
  ExternalTableName := SQLTableName;
  SQLTableNameUpperWithDot := UpperCase(SQLTableName)+'.';
  ClassProp := InternalClassProp(aTable);
  assert(ClassProp<>nil);
  nProps := PClassProp(aTable)^.FieldCountWithParents;
  if nProps>MAX_SQLFIELDS then
    raise EModelException.CreateFmt('%s has too many fields: %d>%d',
      [SQLTableName,nProps,MAX_SQLFIELDS]);
  SetLength(FieldType,nProps);
  SetLength(Fields,nProps);
  SetLength(FieldsName,nProps);
  SetLength(fFieldsNameSorted,nProps);
  SetLength(fFieldsNameIndex,nProps);
  SetLength(ManyFields,nProps);
................................................................................

constructor TSQLVirtualTable.Create(aModule: TSQLVirtualTableModule;
  const aTableName: RawUTF8; FieldCount: integer; Fields: PPUTF8CharArray);
var aTable: TSQLRecordClass;
    aTableIndex: integer;
begin
  if (aModule=nil) or (aTableName='') then
    raise EModelException.CreateFmt('Invalid parameters to %s.Create',[ClassName]);
  fModule := aModule;
  fTableName := aTableName;
  if fModule.fFeatures.StaticClass<>nil then
    // create no fStatic instance e.g. for TSQLVirtualTableLog
    if fModule.Server=nil then
      raise EModelException.CreateFmt('Missing aModule.Server for %s.Create',[ClassName]) else
    with fModule.Server do begin
      aTableIndex := Model.GetTableIndex(aTableName);
      if aTableIndex>=0 then begin
        aTable := Model.Tables[aTableIndex];
        fStatic := fModule.fFeatures.StaticClass.Create(aTable,fModule.Server,
          fModule.FileName(aTableName),self.InheritsFrom(TSQLVirtualTableBinary));
        if length(fStaticVirtualTable)<>length(Model.Tables) then
................................................................................
      {$endif}
      Access;
      exit; // create successfull
    end;
    User.GroupRights.Free;
    User.GroupRights := GID;
  end;
  raise ESecurityException.Create('TAuthSession.Create');
end;

destructor TAuthSession.Destroy;
begin
  if User<>nil then begin
    User.GroupRights.Free;
    fUser.Free;
................................................................................
{ TServiceFactory }

type
  PInterfaceTypeData = ^TInterfaceTypeData;
  TInterfaceTypeData = packed record
    IntfParent : PPTypeInfo; // ancestor
    IntfFlags : set of (ifHasGuid, ifDispInterface, ifDispatch);
    IntfGuid : TGUID;
    IntfUnit : ShortString;
  end;
  TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor,
    mkClassProcedure, mkClassFunction, { Obsolete } mkSafeProcedure, mkSafeFunction);
  TIntfMethodEntryTail = packed record
    Kind: TMethodKind;
    CC: TCallingConvention;
................................................................................
    PP: ^PPTypeInfo absolute P;
    Ancestor: PTypeInfo;
    Kind: TMethodKind;
    i,j, n, m,a,reg,offs: integer;
begin
  // check supplied interface
  if (aRest=nil) or (aInterface=nil) then
    raise EServiceException.Create('Invalid call');
  fRest := aRest;
  fInstanceCreation := aInstanceCreation;
  fInterfaceTypeInfo := aInterface;
  fInterfaceURI := aInterface^.ShortName;
  if fInterfaceURI[1] in ['I','i'] then
    Delete(fInterfaceURI,1,1);
  P := fInterfaceTypeInfo.ClassType;
  if PI^.IntfParent<>nil then
    Ancestor := PI^.IntfParent^ else
    Ancestor := nil;
  if (Ancestor<>nil) and (Ancestor<>TypeInfo(IService)) then
    raise EServiceException.CreateFmt('%s interface should not have %s as parent but IService',
      [fInterfaceTypeInfo^.ShortName,Ancestor^.ShortName]);
  fInterfaceIID := PI^.IntfGuid;
  P := @PI^.IntfUnit[ord(PI^.IntfUnit[0])+1];
  fInterfaceMangledURI := BinToBase64URI(@fInterfaceIID,sizeof(TGUID));
  // retrieve methods (not from ancestors since we inherit from IService)
  n := PW^; inc(PW);
  if PW^=$ffff then
    raise EServiceException.CreateFmt('%s interface has no RTTI',[fInterfaceTypeInfo^.ShortName]);
  inc(PW);
  fMethodsCount := n;
  SetLength(fMethods,n); // QueryInterface, _AddRef, _Release are hard-coded
  for i := 0 to n-1 do
  with fMethods[i] do begin
    MethodIndex := i+3; // +3 because of QueryInterface+_AddRef+_Release
    SetString(URI,PAnsiChar(@PS^[1]),ord(PS^[0]));
    PS := @PS^[ord(PS^[0])+1];
    Kind := PME^.Kind;
    CallingConvention := PME^.CC;
    if CallingConvention<>ccRegister then
      raise EServiceException.CreateFmt('%s.%s method shall use register calling convention',
        [fInterfaceTypeInfo^.ShortName,URI]);
    n := PME^.ParamCount;
    inc(PME);
    if Kind=mkFunction then
      SetLength(Args,n+1) else
      SetLength(Args,n);
    for j := 0 to n-1 do
................................................................................
        ValueDirection := smdOut;
      inc(PF);
      ParamName := PS;
      PS := @PS^[ord(PS^[0])+1];
      TypeName := PS;
      PS := @PS^[ord(PS^[0])+1];
      if PP^=nil then
        raise EServiceException.CreateFmt('%s.%s method %s parameter has no information',
          [fInterfaceTypeInfo^.ShortName,URI,ParamName^]);
      TypeInfo := PP^^;
      inc(PP);
      {$ifdef ISDELPHIXE2}
      inc(PW); // skip attributes
      {$endif}
      if j=0 then
        ValueType := smvSelf else
        ValueType := TypeInfoToMethodValueType(TypeInfo);
      if ValueType=smvNone then
        raise EServiceException.CreateFmt('%s.%s method %s parameter has unexpected type %s',
          [fInterfaceTypeInfo^.ShortName,URI,ParamName^,TypeName^]);
    end;
    if Kind=mkFunction then
    with Args[n] do begin
      ParamName := @CONST_RESULT_NAME;
      ValueDirection := smdResult;
      TypeName := PS;
      PS := @PS^[ord(PS^[0])+1];
      TypeInfo := PP^^;
      inc(PP);
      ValueType := TypeInfoToMethodValueType(TypeInfo);
      if ValueType=smvNone then
        raise EServiceException.CreateFmt('%s.%s method has unexpected result type %s',
          [fInterfaceTypeInfo^.ShortName,URI,TypeName^]);
    end;
    {$ifdef ISDELPHIXE2}
    inc(PW); // skip attributes
    {$endif}
  end;
  // compute asm low-level layout of the parameters for each method
................................................................................
    with Args[a] do
      if OffsetInStack>=0 then begin
        dec(offs,SizeInStack);
        OffsetInStack := offs;
      end;
    assert(offs=0);
  end;




























end;

function TServiceFactory.MethodIndex(const aMethod: RawUTF8): integer;
begin
  for result := 0 to fMethodsCount-1 do
    if IdemPropNameU(aMethod,fMethods[result].URI) then
      exit;
  result := -1;
end;


{ TServiceContainerServer }

function IsEqualGUID(const guid1, guid2: TGUID): Boolean; {$ifdef HASINLINE}inline;{$endif}
var a: array[0..3] of integer absolute guid1;
    b: array[0..3] of integer absolute guid2;
begin // faster implementation than in SysUtils.pas
  Result := (a[0]=b[0]) and (a[1]=b[1]) and (a[2]=b[2]) and (a[3]=b[3]);
end;

function TServiceContainerServer.AddImplementation(
  aImplementationClass: TInterfacedObjectClass;
  const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation): boolean;
var C: TClass;
    T: PInterfaceTable;
    i, j: integer;
    UID: array of ^TGUID;
begin
  result := false;
  // check input parameters
  if (self=nil) or (aImplementationClass=nil) or (high(aInterfaces)<0) then
    exit;
  CheckInterface(aInterfaces);
  SetLength(UID,length(aInterfaces));
  for j := 0 to high(aInterfaces) do

    UID[j] := @PInterfaceTypeData(aInterfaces[j]^.ClassType)^.IntfGuid;








  // check that all interfaces are implemented by this class
  C := aImplementationClass;
  repeat
    T := C.GetInterfaceTable;
    if T<>nil then
      for i := 0 to T^.EntryCount-1 do
        with T^.Entries[i] do
................................................................................
            UID[j] := nil;
            break;
          end;
    C := C.ClassParent;
  until C=nil;
  for j := 0 to high(aInterfaces) do
    if UID[j]<>nil then
      raise EServiceException.CreateFmt('Interface %s not found in %s implementation',
        [aInterfaces[j]^.ShortName,aImplementationClass.ClassName]);
  // register this implementation class
  for j := 0 to high(aInterfaces) do
    fList.Add(TServiceFactoryServer.Create(Rest,aInterfaces[j],aInstanceCreation,
      aImplementationClass));
  result := true;
end;


{ TServiceContainer }

function TServiceContainer.Count: integer;
begin
  if self=nil then
    result := 0 else
    result := fList.Count;
end;

................................................................................

destructor TServiceContainer.Destroy;
begin
  fList.Free;
  inherited;
end;

procedure TServiceContainer.CheckInterface(const aInterfaces: array of PTypeInfo);
var i: integer;
begin
  for i := 0 to high(aInterfaces) do
    with aInterfaces[i]^, PInterfaceTypeData(ClassType)^ do
    if Kind<>tkInterface then
      raise EServiceException.CreateFmt('%s is not an interface',[ShortName]) else
    if not (ifHasGuid in IntfFlags) then
      raise EServiceException.CreateFmt('%s interface has no GUID',[ShortName]) else begin
      if Guid(IntfGuid)<>nil  then
        raise EServiceException.CreateFmt('%s GUID already registered',[ShortName]);
    end;
end;

function TServiceContainer.GetService(const aURI: RawUTF8): TServiceFactory;
var i: Integer;
begin
  if (self<>nil) and (aURI<>'') then
  with fList do begin
    if ExpectMangledURI then begin
      for i := 0 to Count-1 do begin
        result := List[i];
................................................................................
        if IdemPropNameU(aURI,result.fInterfaceURI) then
          exit;
      end;
  end;
  result := nil;
end;

function TServiceContainer.Info(aTypeInfo: PTypeInfo): TServiceFactory;
var i: Integer;
begin
  if self<>nil then
    with fList do
    for i := 0 to Count-1 do begin
      result := List[i];
      if result.fInterfaceTypeInfo=aTypeInfo then
        exit;
    end;
  result := nil;
end;

function TServiceContainer.Index(aIndex: integer): TServiceFactory;
begin
  if (Self=nil) or (Cardinal(aIndex)>=Cardinal(fList.Count)) then
    result := nil else
    result := fList.List[aIndex];
end;

function TServiceContainer.GUID(const aGUID: TGUID): TServiceFactory;
var i: Integer;
begin
  if self<>nil then
    for i := 0 to fList.Count-1 do begin
      result := fList.List[i];
      if IsEqualGUID(result.InterfaceIID,aGUID) then
        exit;
    end;
  result := nil;
end;

{ TServiceFactoryServer }

constructor TServiceFactoryServer.Create(aRest: TSQLRest; aInterface: PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  aImplementationClass: TInterfacedObjectClass; aTimeOutSec: cardinal);
begin
  // extract RTTI from the interface
  inherited Create(aRest,aInterface,aInstanceCreation);
  if fRest.MethodAddress(ShortString(InterfaceURI))<>nil then
    raise EServiceException.CreateFmt('%s is already exposed as %s published method',
      [InterfaceURI,fRest.ClassName]) else
  fImplementationClass := aImplementationClass;
  // initialize the shared instance or client driven parameters
  case InstanceCreation of
  sicShared: begin
    fSharedInstance := fImplementationClass.Create;
    if (fSharedInstance=nil) or
       not fSharedInstance.GetInterface(fInterfaceIID,fSharedInterface) then
      raise EServiceException.CreateFmt('No implementation available for "%s" interface',
        [fInterfaceURI]);
  end;
  sicClientDriven:
    if aTimeOutSec=0 then
      fInstanceCreation := sicSingle else begin
      InitializeCriticalSection(fInstanceLock);
      fInstanceTimeOut := aTimeOutSec*1000;
      fInstance.Init(TypeInfo(TServiceFactoryServerInstanceDynArray),
        fInstances,@fInstancesCount);
      fInstance.Compare := SortDynArrayInteger;
    end;
  end;
end;

destructor TServiceFactoryServer.Destroy;
var i: integer;
begin
  try
    for i := 0 to fInstancesCount-1 do
      fInstances[i].Instance.Free;
  except
    ; // better ignore any error in business logic code
  end;
  DeleteCriticalSection(fInstanceLock);
  inherited;
end;

function TServiceFactoryServer.Get(out Obj): Boolean;
var O: TInterfacedObject;
begin
  result := false;
  if Self<>nil then
    case fInstanceCreation of
    sicShared:
    if fSharedInterface<>nil then begin
      IInterface(Obj) := fSharedInterface; // copy implementation interface
      result := true;
    end;
    sicSingle, sicClientDriven: begin
      O := fImplementationClass.Create;
      if O<>nil then
        result := O.GetInterface(fInterfaceIID,Obj);
    end;
    end;
end;

function TServiceFactoryServer.ClientDrivenRetrieve(var Inst: TServiceFactoryServerInstance;
  aMethodIndex: integer): boolean;
var i: integer;
    TimeOutTimeStamp: cardinal;
begin
  result := false;
  Inst.LastAccess := GetTickCount;
  TimeOutTimeStamp := Inst.LastAccess+fInstanceTimeOut;
  EnterCriticalSection(fInstanceLock);
  try
    // first release any deprecated instances
    for i := 0 to fInstancesCount-1 do
      with fInstances[i] do
      if (LastAccess<Inst.LastAccess) or
         (LastAccess>TimeOutTimeStamp) then begin
        InstanceID := 0; // mark this entry is empty
        FreeAndNil(Instance);
      end;
    // retrieve or initialize the instance
    if Inst.InstanceID=0 then begin
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit;
      // initialize the new instance
      inc(fInstanceCurrentID);
      Inst.InstanceID := fInstanceCurrentID;
      for i := 0 to fInstancesCount-1 do
        if fInstances[i].InstanceID=0 then begin
          Inst.Instance := fImplementationClass.Create; // found an empty entry
          if Inst.Instance<>nil then
            fInstances[i] := Inst;
          break;
        end;
      if Inst.Instance=nil then begin
        Inst.Instance := fImplementationClass.Create; // append a new entry
        if Inst.Instance<>nil then
          fInstance.Add(Inst);
      end;
    end else
      // search the given instance
      for i := 0 to fInstancesCount-1 do
        with fInstances[i] do
        if InstanceID=Inst.InstanceID then begin
          if aMethodIndex<0 then begin
            // aMethodIndex=-1 for {"method":"free", "params":[], "id":1234}
            InstanceID := 0;
            FreeAndNil(Instance);
            result := true; // successfully released instance
            exit;
          end;
          LastAccess := Inst.LastAccess;
          Inst.Instance := Instance;
          break;
        end;
  finally
    LeaveCriticalSection(fInstanceLock);
  end;
end;

function TServiceFactoryServer.ExecuteMethod(aSession: cardinal;
  aMethodIndex, aInstanceID: Integer; aParamsJSONArray: PUTF8Char;
  var aResp, aHead, aErrorMsg: RawUTF8): cardinal;

var Inst: TServiceFactoryServerInstance;

    WR: TTextWriter;
    entry: PInterfaceEntry;


begin
  result := 400;
  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit else
        Inst.Instance := fImplementationClass.Create;
    sicShared:
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven: begin
      Inst.InstanceID := aInstanceID;
      if ClientDrivenRetrieve(Inst,aMethodIndex) then begin
        result := 200;



































        exit; // {"method":"free", "params":[], "id":1234}











      end;
    end;
  end;
  if Inst.Instance=nil then begin
    aErrorMsg := FormatUTF8('Implementation instance %d not found or deprecated',
      [Inst.InstanceID]);
    exit;
  end;
  // 2. call method implementation
  try
    entry := Inst.Instance.GetInterfaceEntry(fInterfaceIID);
    if entry=nil then
      exit;


    WR := TTextWriter.CreateOwnedStream;
    try
      // root/calculator {"method":"add","params":[1,2]} -> {"result":[3],"id":0}
      try
        WR.AddShort('{"result":[');
        if not fMethods[aMethodIndex].InternalExecute(
           Inst.Instance,entry,aParamsJSONArray,WR) then

          exit; // wrong request
        WR.AddShort('],"id":');
        WR.Add(Inst.InstanceID); // only set in sicClientDriven mode
        WR.AddShort('}');
        aResp := WR.Text;
        result := 200; // success
      except
        on E: Exception do begin
          result := 500; // Internal Server Error
          aErrorMsg := FormatUTF8('%s: %s',[E.ClassName,E.Message]);
          exit;
        end;
      end;
    finally
      WR.Free;
    end;
  finally
    if InstanceCreation=sicSingle then
      Inst.Instance.Free; // always release single shot instance
  end;
end;


{ TServiceMethod }

type
  TDynArrayFake = record
    Wrapper: TDynArray;
    Value: Pointer;
  end;

// we already have the value on the FPU ST(0) register -> do nothing functions
function LoadDouble: double; asm end;
function LoadCurrency: currency; asm end;

function TServiceMethod.InternalExecute(Instance: pointer; Entry: PInterfaceEntry;
  Par: PUTF8Char; Res: TTextWriter): boolean;
var Stack: TByteDynArray;
    StackSize: integer;
    Int64s: TInt64DynArray;
    RawUTF8s: TRawUTF8DynArray;
    Strings: TStringDynArray;
    WideStrings: TWideStringDynArray;
    Objects: array of TObject;
    DynArrays: array of TDynArrayFake;

    Value, method: pointer;


    i,a: integer;
    wasString, valid: boolean;
    EndOfObject: AnsiChar;
    Val: PUTF8Char;
    cla: TClass;
    obj: TJSONObject;
    r: packed record EAX, EDX, ECX, EAX2, EDX2: integer; end;
begin
  result := false;
  StackSize := ArgsSize;
  SetLength(Stack,StackSize);
  SetLength(Int64s,ArgsUsedCount[smvv64]);
  SetLength(RawUTF8s,ArgsUsedCount[smvvRawUTF8]);
  SetLength(Strings,ArgsUsedCount[smvvString]);
  SetLength(WideStrings,ArgsUsedCount[smvvWideString]);
  SetLength(Objects,ArgsUsedCount[smvvObject]);
  SetLength(DynArrays,ArgsUsedCount[smvvDynArray]);
  try
    // 1. read the parameters
    while (Par^<>#0) and (Par^<=' ') do inc(Par);
    if Par^<>'[' then

      Exit;

    inc(Par);
    for a:= 0 to high(Args) do
    with Args[a] do begin

      case ValueType of
      smvSelf:
        continue; // self parameter is never transmitted
      smvObject: begin
        if TypeInfo^.Kind<>tkClass then
          Exit;
        cla := TypeInfo^.ClassType^.ClassType;
................................................................................
        REGEAX: r.EAX := PInteger(Value)^;
        REGEDX: r.EDX := PInteger(Value)^;
        REGECX: r.ECX := PInteger(Value)^;
        else    move(Value^,Stack[OffsetInStack],SizeInStack);
        end;
    end;
    // 3. execute the method
    method := PPointerArray(PPointer(PtrInt(Instance)+entry^.IOffset)^)^[MethodIndex];
    asm
      mov eax,StackSize
      mov edx,dword ptr Stack
      add edx,eax // pascal/register convention = left-to-right
      shr eax,2
      jz @z
  @n: sub edx,4
................................................................................
      if (ValueVar=smvv64) and (ValueDirection=smdResult) then begin
        case ValueType of // ordinal/real result values from CPU/FPU registers
        smvDouble, smvDateTime: Res.Add(LoadDouble);
        smvCurrency:            Res.Add(LoadCurrency);
        smvBoolean:             Res.Add(PByte(@r.EAX2)^);
        smvInteger:             Res.Add(r.EAX2);
        smvInt64:               Res.Add(PInt64(@r.EAX2)^);
        else raise EServiceException.CreateFmt('Invalid result type %d',[ord(ValueType)]);
        end;
      end else
      case ValueType of
      smvObject:     Res.WriteObject(Objects[IndexVar],False,False,true);
      smvDynArray:   Res.AddDynArrayJSON(DynArrays[IndexVar].Wrapper);
      smvBoolean:    Res.Add(PByte(@Int64s[IndexVar])^);
      smvInteger:    Res.Add(PInteger(@Int64s[IndexVar])^);
................................................................................
      smvWideString: Res.AddJSONEscapeW(pointer(WideStrings[IndexVar]));
      end;
      if ValueIsString then
        Res.Add('"',',') else
        Res.Add(',');
    end;
    Res.CancelLastComma;
    Result := true;
  finally // manual release memory for Objects[] and DynArrays[]
    for i := 0 to high(Objects) do
      Objects[i].Free;
    for i := 0 to high(DynArrays) do
      DynArrays[i].Wrapper.Clear;
  end;
end;


{ TServiceContainerClient }

function TServiceContainerClient.Info(aTypeInfo: PTypeInfo): TServiceFactory;
begin
  result := inherited Info(aTypeInfo);
  if (result=nil) and AddInterface(aTypeInfo,sicClientDriven) then
    result := inherited Info(aTypeInfo);
end;

function TServiceContainerClient.AddInterface(
  const aInterfaces: array of PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation): boolean;
var i: integer;
begin
  result := false;
  if (self=nil) or (high(aInterfaces)<0) then
    exit;
  CheckInterface(aInterfaces);
  for i := 0 to high(aInterfaces) do
    fList.Add(TServiceFactoryClient.Create(Rest,aInterfaces[i],aInstanceCreation));
  result := true;
end;


{ TServiceFactoryClient }

type
  /// map the stack memory layout at TInterfacedObjectFake.FakeCall()
  TFakeCall = packed record
    EDX, ECX, MethodIndex, EBP, Ret2: integer;
    Args: array[word] of byte;
  end;

  /// instances of this class will emulate a given interface
  TInterfacedObjectFake = class(TInterfacedObject)
  protected
    fFactory: TServiceFactoryClient;
    fVTable: PPointerArray;
    function FakeCall(const aCall: TFakeCall): Int64;
    function SelfFromFake: TInterfacedObjectFake;
    function FakeQueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function Fake_AddRef: Integer; stdcall;
    function Fake_Release: Integer; stdcall;
  public
    /// create an instance, using the specified interface
    constructor Create(aFactory: TServiceFactoryClient);
    /// release the remote server instance (in sicClientDriven mode);
    destructor Destroy; override;
    /// the associated interface factory
    property Factory: TServiceFactoryClient read fFactory;
  end;

constructor TInterfacedObjectFake.Create(aFactory: TServiceFactoryClient);
begin
  inherited Create;
  fFactory := aFactory;
  fVTable := Pointer(aFactory.fFakeVTable);
end;

destructor TInterfacedObjectFake.Destroy;
begin
  if (fFactory<>nil) and (fFactory.InstanceCreation=sicClientDriven) then
  try
    fFactory.CallClient('free'); // release server instance
  except
    ; // ignore any exception here
  end;
  inherited;
end;

function TInterfacedObjectFake.Fake_AddRef: Integer;
begin
  result := SelfFromFake._AddRef;
end;

function TInterfacedObjectFake.Fake_Release: Integer;
begin
  result := SelfFromFake._Release;
end;

function TInterfacedObjectFake.FakeQueryInterface(const IID: TGUID; out Obj): HResult;
begin
  self := SelfFromFake;
  if (fFactory<>nil) and IsEqualGUID(IID,fFactory.fInterfaceIID) then begin
    pointer(Obj) := @fVTable;
    _AddRef;
    result := NOERROR;
  end else
    result := SelfFromFake.QueryInterface(IID,Obj);
end;

function TInterfacedObjectFake.SelfFromFake: TInterfacedObjectFake;
asm
  sub eax,TInterfacedObjectFake.fVTable
end;

function TInterfacedObjectFake.FakeCall(const aCall: TFakeCall): Int64;
begin
  self := SelfFromFake;
  assert(fFactory.ClassNameIs('TServiceFactoryClient'));
  with aCall do  { TODO: remote RESTful server call using JSON }
  case MethodIndex of
  0: result := EDX+ECX;
  1: result := PInt64(@Args[0])^*PInt64(@Args[8])^;
  else result := 0;
  end;
end;

function TServiceFactoryClient.CallClient(const aMethod, aParams: RawUTF8;
  aResult: PRawUTF8; aClientDrivenID: Pcardinal): boolean;
var sent,resp,head: RawUTF8;
    Values: TPUtf8CharDynArray;
begin
  Result := false;
  if Self=nil then
    exit;
  if fClient=nil then
    fClient := fRest as TSQLRestClientURI;
  sent := '{"method":"'+aMethod+'","params":['+aParams+']}';
  if fClient.URI(fClient.Model.Root+'/'+fInterfaceURI,'POST',@resp,@head,@sent).Lo<>200 then
    exit;
  JSONDecode(resp,['RESULT','ID'],Values,True);
  if (Values[0]=nil) or (Values[1]=nil) then
    Exit;
  if aResult<>nil then
    aResult^ := Values[0];
  if aClientDrivenID<>nil then
    aClientDrivenID^ := GetCardinal(Values[1]);
  result := true;
end;

constructor TServiceFactoryClient.Create(aRest: TSQLRest;
  aInterface: PTypeInfo; aInstanceCreation: TServiceInstanceImplementation);
var i, siz: integer;
    P: PCardinal;
begin
  // extract RTTI from the interface
  if not aRest.InheritsFrom(TSQLRestClientURI) then
    EServiceException.CreateFmt('%s interface needs a Client connection',
      [aInterface^.ShortName]);
  inherited Create(aRest,aInterface,aInstanceCreation);
  // check if this interface is supported on the server
  if not CallClient('ClassName','',@fRemoteClassName) then
    raise EServiceException.CreateFmt('%s interface not supported by server',
      [fInterfaceURI]);
  // create the fake interface
  SetLength(fFakeVTable,fMethodsCount+3);
  fFakeVTable[0] := @TInterfacedObjectFake.FakeQueryInterface;
  fFakeVTable[1] := @TInterfacedObjectFake.Fake_AddRef;
  fFakeVTable[2] := @TInterfacedObjectFake.Fake_Release;
  siz := (((fMethodsCount*24) shr 12)+1) shl 12; // 4 KB granularity
  fFakeStub := VirtualAlloc(nil,siz,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
  P := pointer(fFakeStub);
  for i := 0 to fMethodsCount-1 do begin
    fFakeVTable[i+3] := P;
    P^ := $68ec8b55; inc(P);                 // push ebp; mov ebp,esp
    P^ := i; inc(P);                         // push {MethodIndex}
    P^ := $e2895251; inc(P);                 // push ecx; push edx; mov edx,esp
    PByte(P)^ := $e8; inc(PByte(P));         // call FakeCall
    P^ := PtrUInt(@TInterfacedObjectFake.FakeCall)-PtrUInt(P)-4; inc(P);
    P^ := $c25dec89; inc(P);                 // mov esp,ebp; pop ebp
    P^ := fMethods[i].ArgsSize or $900000;   // ret {StackSize}; nop
    inc(PByte(P),3); 
  end;
  // initialize a shared instance (if needed)
  if fInstanceCreation=sicShared then begin
    fSharedInstance := TInterfacedObjectFake.Create(self);
    TInterfacedObjectFake(fSharedInstance)._AddRef; // force stay alive
  end;
end;

function TServiceFactoryClient.Get(out Obj): Boolean;
var O: TInterfacedObjectFake;
begin
  result := false;
  if Self=nil then
    exit;
  case fInstanceCreation of
  sicShared:
    O := TInterfacedObjectFake(fSharedInstance);
  sicSingle, sicClientDriven:
    O := TInterfacedObjectFake.Create(self);
  else exit;
  end;
  pointer(Obj) := @O.fVTable;
  O._AddRef;
  result := true;
end;

destructor TServiceFactoryClient.Destroy;
begin
  Assert(TInterfacedObjectFake(fSharedInstance).fRefCount=1);
  TInterfacedObjectFake(fSharedInstance)._Release; // bonne nuit les petits
  if fFakeStub<>nil then
    VirtualFree(fFakeStub,0,MEM_RELEASE);
  inherited;
end;


initialization
  pointer(@SQLFieldTypeComp[sftAnsiText]) := @AnsiIComp;
{$ifndef USENORMTOUPPER}
  pointer(@SQLFieldTypeComp[sftUTF8Text]) := @AnsiIComp;
{$endif}
  DefaultHasher := @crc32; // faster and more accurate than kr32()

end.

Changes to SQLite3/SQLite3HttpServer.pas.

607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
...
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
  if ErrMsg='' then
    for i := 0 to high(aServers) do
    with aServers[i].Model do
    for j := i+1 to high(aServers) do
      if aServers[j].Model.Root=Root then
        ErrMsg:= 'Duplicated Root URI';
  if ErrMsg<>'' then
     raise Exception.Create('TSQLite3HttpServer.Create: '+ErrMsg);
  SetLength(fDBServers,length(aServers));
  for i := 0 to high(aServers) do
  with fDBServers[i] do begin
    Server := aServers[i];
    RestAccessRights := HTTP_DEFAULT_ACCESS_RIGHTS;
  end;
  {$ifndef USETCPPREFIX}
................................................................................
    fHttpServer := THttpApiServer.Create(false);
    for i := 0 to high(aServers) do begin
      j := THttpApiServer(fHttpServer).AddUrl(
        aServers[i].Model.Root,aPort,false,aDomainName);
      if j<>NO_ERROR then begin
        ErrMsg := 'Impossible to register URL';
        if j=ERROR_ACCESS_DENIED then
          ErrMsg := ': administrator rights needed';
        raise Exception.Create('TSQLite3HttpServer.Create: '+ErrMsg);
        break;
      end;
    end;
  except
    on E: Exception do begin
      {$ifdef WITHLOG}
      Log.Log(sllError,'% for %',[E,fHttpServer],self);






|







 







|
|







607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
...
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
  if ErrMsg='' then
    for i := 0 to high(aServers) do
    with aServers[i].Model do
    for j := i+1 to high(aServers) do
      if aServers[j].Model.Root=Root then
        ErrMsg:= 'Duplicated Root URI';
  if ErrMsg<>'' then
     raise EModelException.CreateFmt('%s.Create: %s',[ClassName,ErrMsg]);
  SetLength(fDBServers,length(aServers));
  for i := 0 to high(aServers) do
  with fDBServers[i] do begin
    Server := aServers[i];
    RestAccessRights := HTTP_DEFAULT_ACCESS_RIGHTS;
  end;
  {$ifndef USETCPPREFIX}
................................................................................
    fHttpServer := THttpApiServer.Create(false);
    for i := 0 to high(aServers) do begin
      j := THttpApiServer(fHttpServer).AddUrl(
        aServers[i].Model.Root,aPort,false,aDomainName);
      if j<>NO_ERROR then begin
        ErrMsg := 'Impossible to register URL';
        if j=ERROR_ACCESS_DENIED then
          ErrMsg := ErrMsg+' (administrator rights needed)';
        raise ECommunicationException.CreateFmt('%s.Create: %s',[ClassName,ErrMsg]);
        break;
      end;
    end;
  except
    on E: Exception do begin
      {$ifdef WITHLOG}
      Log.Log(sllError,'% for %',[E,fHttpServer],self);

Changes to SQLite3/SQlite3BigTable.pas.

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
constructor TSQLRestServerStaticBigTable.Create(aClass: TSQLRecordClass;
  aServer: TSQLRestServer; const aFileName: TFileName;
  aBinaryFile: boolean);
begin
  inherited Create(aClass,aServer,aFileName,aBinaryFile);
  if fStoredClassProps.Kind in INSERT_WITH_ID then
    raise Exception.CreateFmt('%s: %s virtual table can''t be static',
      [fStoredClassProps.SQLTableName,aClass.ClassName]);
  if aBinaryFile then
    fBig := TSynBigTableMetaData.Create(aFileName,fStoredClassProps.SQLTableName) else
    fBig := TSynBigTableRecord.Create(aFileName,fStoredClassProps.SQLTableName);
end;

function TSQLRestServerStaticBigTable.CreateSQLMultiIndex(






|







131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
constructor TSQLRestServerStaticBigTable.Create(aClass: TSQLRecordClass;
  aServer: TSQLRestServer; const aFileName: TFileName;
  aBinaryFile: boolean);
begin
  inherited Create(aClass,aServer,aFileName,aBinaryFile);
  if fStoredClassProps.Kind in INSERT_WITH_ID then
    raise EModelException.CreateFmt('%s: %s virtual table can''t be static',
      [fStoredClassProps.SQLTableName,aClass.ClassName]);
  if aBinaryFile then
    fBig := TSynBigTableMetaData.Create(aFileName,fStoredClassProps.SQLTableName) else
    fBig := TSynBigTableRecord.Create(aFileName,fStoredClassProps.SQLTableName);
end;

function TSQLRestServerStaticBigTable.CreateSQLMultiIndex(

Changes to SQLite3/Samples/12 - SynDB Explorer/SynDBExplorer.dpr.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  Version 1.15 - July 12, 2011
  - Initial Release, handling OleDB, Oracle/OCI, and SQLite3 databases

  Version 1.16
  - SynDbExplorer now executes selected text statement (very convenient)
  - will try to reconnect to the server in case of error triggerred
  - added advanced Query Builder (right click on selected tables in left list)
  - now accepts a SQLite3 database file as command line parameter
  - fix error ORA-00932 at OCI client level
  - added UTF-8 BOM to CSV or TXT exports
  - now direct-to-file fast export feature (into CSV, TXT, SQLite3,
    Synopse BigTable records or two JSON flavors)
  - SQLite3 3.7.10 including (beta) private encryption methods







|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  Version 1.15 - July 12, 2011
  - Initial Release, handling OleDB, Oracle/OCI, and SQLite3 databases

  Version 1.16
  - SynDbExplorer now executes selected text statement (very convenient)
  - will try to reconnect to the server in case of error triggerred
  - added advanced Query Builder
  - now accepts a SQLite3 database file as command line parameter
  - fix error ORA-00932 at OCI client level
  - added UTF-8 BOM to CSV or TXT exports
  - now direct-to-file fast export feature (into CSV, TXT, SQLite3,
    Synopse BigTable records or two JSON flavors)
  - SQLite3 3.7.10 including (beta) private encryption methods

Changes to SynCommons.pas.

224
225
226
227
228
229
230

231
232
233
234
235
236
237
...
302
303
304
305
306
307
308









309
310
311
312
313
314
315
....
6942
6943
6944
6945
6946
6947
6948
6949

6950
6951
6952
6953
6954
6955
6956
.....
16583
16584
16585
16586
16587
16588
16589
16590
16591
16592
16593
16594
16595
16596
16597
.....
16723
16724
16725
16726
16727
16728
16729
16730
16731
16732
16733
16734
16735
16736
16737
.....
17318
17319
17320
17321
17322
17323
17324
17325
17326
17327
17328
17329
17330
17331
17332
.....
17912
17913
17914
17915
17916
17917
17918
17919
17920
17921
17922
17923
17924
17925
17926
.....
18968
18969
18970
18971
18972
18973
18974
18975
18976
18977
18978
18979
18980
18981
18982
.....
22749
22750
22751
22752
22753
22754
22755
22756
22757
22758
22759
22760
22761
22762
22763
.....
22811
22812
22813
22814
22815
22816
22817
22818
22819
22820
22821
22822
22823
22824
22825
.....
23196
23197
23198
23199
23200
23201
23202
23203
23204
23205
23206
23207
23208
23209
23210
.....
23299
23300
23301
23302
23303
23304
23305
23306
23307
23308
23309
23310
23311
23312
23313
.....
24092
24093
24094
24095
24096
24097
24098
24099
24100
24101
24102
24103
24104
24105
24106
.....
24119
24120
24121
24122
24123
24124
24125
24126
24127
24128
24129
24130
24131
24132
24133
.....
24600
24601
24602
24603
24604
24605
24606
24607
24608
24609
24610
24611
24612
24613
24614
.....
25095
25096
25097
25098
25099
25100
25101
25102
25103
25104
25105
25106
25107
25108
25109
.....
26065
26066
26067
26068
26069
26070
26071
26072
26073
26074
26075
26076
26077
26078
26079
26080
26081
26082
26083
26084
26085
26086
26087
26088
26089
26090
26091
26092
.....
26136
26137
26138
26139
26140
26141
26142
26143
26144
26145
26146
26147
26148
26149
26150
.....
26174
26175
26176
26177
26178
26179
26180
26181
26182
26183
26184
26185
26186
26187
26188
26189
26190
.....
26470
26471
26472
26473
26474
26475
26476





26477
26478
26479
26480
26481
26482
26483
.....
26668
26669
26670
26671
26672
26673
26674
26675
26676


26677
26678
26679
26680
26681
26682
26683
.....
26720
26721
26722
26723
26724
26725
26726
26727
26728
26729
26730
26731
26732
26733
26734
.....
26867
26868
26869
26870
26871
26872
26873
26874

26875
26876
26877
26878
26879
26880
26881
26882
26883
26884
26885
26886
26887
26888
26889
26890
.....
27585
27586
27587
27588
27589
27590
27591
27592
27593
27594
27595
27596
27597
27598
27599
27600
27601
27602
.....
27604
27605
27606
27607
27608
27609
27610
27611
27612
27613
27614
27615
27616
27617
27618
    (inserting '?' as inlined :(...): parameters, with proper string quote) -
    with associated regression tests

  Version 1.16
  - introducing new TSynAnsiConvert and TSynAnsiFixedWidth classes, able to
    process Unicode to/from Ansi conversion in all possible code pages, with
    generic access methods and optimized handling of fixed width encodings

  - TSynLog allows read sharing of the .log created file
  - TSynLog now stores the executable build time, and library name (if any)
  - TSynLog and TSynMapFile now handle libraries (.dll/.ocx/.bpl) .map/.mab
    debugging information (only .exe was previously handled) 
  - TSynCache now handle an integer ResultTag: PtrInt value parameter (used e.g.
    to store the row counts of a SQL result cache)
  - TMemoryMapText class (and therefore TSynLogFile) is now able to map/open
................................................................................
  SysUtils;


const
  {{ the corresponding version of the freeware Synopse framework }
  SYNOPSE_FRAMEWORK_VERSION = '1.16'{$ifdef LVCL}+' LVCL'{$endif};











{ ************ common types used for compatibility between compilers and CPU }

{$ifndef FPC} { make cross-compiler and cross-CPU types available to Delphi }
type

  /// a CPU-dependent unsigned integer type cast of a pointer / register
................................................................................
    move(U256,fAnsiToWide[0],256*2);
    SetLength(fWideToAnsi,65536);
    fillchar(fWideToAnsi[1],65535,ord('?')); // '?' for unknown char
    for i := 1 to 255 do
      if fAnsiToWide[i]<>0 then
        fWideToAnsi[fAnsiToWide[i]] := i;
  end else
    raise Exception.CreateFmt('%s.Create - Invalid code page %d',[ClassName,fCodePage]);

end;

function TSynAnsiFixedWidth.IsValidAnsi(WideText: PWideChar; Length: integer): boolean;
var i: integer;
    wc: cardinal;
begin
  result := false;
................................................................................
    exit; // avoid GPF if void
  if Stream.InheritsFrom(TCustomMemoryStream) then begin
    Posi := MemStream.Seek(0,soFromCurrent);
    PosiEnd := Posi+SaveToLength;
    if PosiEnd>MemStream.Size then
      MemStream.Size := PosiEnd;
    if SaveTo(PAnsiChar(MemStream.Memory)+Posi)-MemStream.Memory<>PosiEnd then
      Exception.Create('TDynArray.SaveToStream');
    MemStream.Seek(PosiEnd,soFromBeginning);
  end else begin
    tmp := SaveTo;
    Stream.Write(pointer(tmp)^,length(tmp));
  end;
end;

................................................................................
function TDynArray.SaveTo: RawByteString;
var Len: integer;
begin
  Len := SaveToLength;
  SetString(result,nil,Len);
  if Len<>0 then
    if SaveTo(pointer(result))-pointer(result)<>Len then
      raise Exception.Create('TDynArray.SaveTo');
end;

function JSONArrayCount(P: PUTF8Char): integer;
var n: integer;
begin
  result := -1;
  n := 0;
................................................................................

procedure TDynArray.Init(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil);
var Typ: PDynArrayTypeInfo absolute aTypeInfo;
begin
  TypeInfo := aTypeInfo;
  Value := @aValue;
  if Typ^.Kind<>tkDynArray then
    raise Exception.CreateFmt('%s is not a dynamic array',[Typ^.Name]);
  inc(PtrUInt(Typ),ord(Typ^.Name[0]));
  with Typ^ do begin
    ElemSize := elSize;
    if elType=nil then
      ElemType := nil else
      ElemType := elType^;
  end;
................................................................................
      if looped then
        Break else begin
        result := 0;
        n := first;
        looped := true;
      end;
  until false;
  raise Exception.Create('HashFind'); // we should never reach here
end;

function TDynArrayHashed.GetHashFromIndex(aIndex: Integer): Cardinal;
begin
  if cardinal(aIndex)>=cardinal(Count) then
    result := 0 else
    // it's faster to rehash than to loop in fHashs[].Index values
................................................................................

function TTextWriter.Text: RawUTF8;
begin
  Flush;
  if fStream.InheritsFrom(TRawByteStringStream) then
    if fInitialStreamPosition=0 then
      result := TRawByteStringStream(fStream).DataString else
      raise Exception.CreateFmt(
        'TTextWriter.Text with fInitialStreamPosition=%d',[fInitialStreamPosition]) else
  if fStream.InheritsFrom(TMemoryStream) then
    SetString(result,PAnsiChar(TMemoryStream(fStream).Memory)+fInitialStreamPosition,
      TMemoryStream(fStream).Seek(0,soFromCurrent)-fInitialStreamPosition) else
    result := '';
end;

................................................................................
    if aCustomSize>Available then
      fBufSize := Int64Rec(Available).Lo;
      fBufSize := aCustomSize;
  end;
  with Int64Rec(fFileSize) do
    fMap := CreateFileMapping(fFile,nil,PAGE_READONLY,Hi,Lo,nil);
  if fMap=0 then
    raise Exception.Create('MemoryMap.Map');
  with Int64Rec(aCustomOffset) do
    fBuf := MapViewOfFile(fMap,FILE_MAP_READ,Hi,Lo,fBufSize);
  if fBuf=nil then begin
    // Windows failed to find a contiguous VA space -> fall back on direct read
    CloseHandle(fMap);
    fMap := 0;
  end else
................................................................................
  fFileStream := TFileStream.Create(aFileName,fmOpenRead or fmShareDenyNone);
  Create(fFileStream.Handle);
end;

constructor TSynMemoryStreamMapped.Create(aFile: THandle; aCustomSize: cardinal; aCustomOffset: Int64);
begin
  if not fMap.Map(aFile) then
    raise Exception.Create('TSynMemoryStreamMapped mapping error');
  inherited Create(fMap.fBuf,fMap.fBufSize);
end;

destructor TSynMemoryStreamMapped.Destroy;
begin
  fMap.UnMap;
  fFileStream.Free;
................................................................................
        end;
        PInteger(PBeg)^ := PAnsiChar(P)-PBeg-4; // format: Isize+varUInt32s
      end;
      wkSorted: begin
        PBeg := PAnsiChar(P)+4; // leave space for chunk size
        P := PByte(CleverStoreInteger(pointer(PI),PBeg,PEnd,ValuesCount,n));
        if P=nil then
          raise Exception.Create('TFileBufferWriter.WriteVarUInt32Array: data not sorted');
        PInteger(PBeg-4)^ := PAnsiChar(P)-PBeg; // format: Isize+cleverStorage
      end;
      end;
      inc(PtrUInt(PI),n*4);
      fPos := PtrUInt(P)-PtrUInt(fBuf);
      inc(fTotalWritten,PtrUInt(fPos-pos));
      dec(ValuesCount,n);
................................................................................
procedure TFileBufferReader.Close;
begin
  fMap.UnMap;
end;

procedure TFileBufferReader.ErrorInvalidContent;
begin
  raise Exception.Create('TFileBufferReader: invalid content');
end;

procedure TFileBufferReader.OpenFrom(aBuffer: pointer; aBufferSize: cardinal);
begin
  fCurrentPos := 0;
  fMap.Map(aBuffer,aBufferSize);
end;
................................................................................
  if (self<>nil) and ((RecordBuffer=nil) or (RecordBufferLen=0)) then begin
    // no data yet -> use default
    RecordBuffer := pointer(fDefaultRecordData);
    RecordBufferLen := fDefaultRecordLength;
  end;
  if RecordBuffer=pointer(result) then
    // update content code below will fail -> please correct calling code
    raise Exception.Create('In-place call of TSynTable.UpdateFieldData');
  if (self=nil) or (cardinal(FieldIndex)>=cardinal(fField.Count)) then begin
    SetString(result,PAnsiChar(RecordBuffer),RecordBufferLen);
    exit;
  end;
  F := TSynTableFieldProperties(fField.List[FieldIndex]);
  NewSize := length(NewFieldData);
  if NewSize=0 then begin
................................................................................
  move(NewData^,PByteArray(result)[DestOffset],NewSize);
  move(Dest[OldSize],PByteArray(result)[DestOffset+NewSize],RecordBufferLen-DestOffset);
end;

constructor TSynTable.Create(const aTableName: RawUTF8);
begin
  if not FieldNameValid(pointer(aTableName)) then
    raise Exception.CreateFmt('Invalid TSynTable.Create(%s)',[aTableName]);
  fTableName := aTableName;
  fField := TObjectList.Create;
  fFieldVariableIndex := -1;
end;

procedure TSynTable.LoadFrom(var RD: TFileBufferReader);
var n, i: integer;
................................................................................
      aSize := Getlength(Data);
      WR.Write(Data,aSize);
      Inc(PtrUInt(Data),aSize);
    end else
      // add default field content for a newly added field
      WR.Write(Pointer(fDefaultFieldData),fDefaultFieldLength);
    if WR.fTotalWritten>1 shl 30 then
      raise Exception.Create('File size too big (>1GB)') else
      Offsets64[Count] := WR.fTotalWritten;
    IDs[Count] := ID;
    NewIndexs[Index] := Count;
    inc(Count);
  end;
end;

................................................................................
      // both indexes equal -1 -> force sort
      OrderedIndexSort(0,OrderedIndexCount-1);
      OrderedIndexNotSorted := false;
    end else begin
      // added record
      if tfoUnique in Options then begin
        if fOrderedIndexFindAdd<0 then
          raise Exception.CreateFmt(
            '%s.CheckConstraint call needed before %s.OrderedIndexUpdate',[ClassName,Name]);
        OrderedIndexReverseSet(InsertInteger(OrderedIndex,OrderedIndexCount,
          aNewIndex,fOrderedIndexFindAdd));
      end else begin
        AddInteger(OrderedIndex,OrderedIndexCount,aNewIndex);
        OrderedIndexReverseSet(OrderedIndexCount-1);
        OrderedIndexNotSorted := true; // -> OrderedIndexSort() call on purpose
................................................................................
{$ifndef DELPHI5OROLDER}

{ TSynTableData }

procedure TSynTableData.CheckVTableInitialized;
begin
  if VTable=nil then
    raise Exception.Create('TSynTableData non initialized');
end;

{$ifdef USESYNTABLEVARIANT}

function TSynTableData.GetFieldValue(const FieldName: RawUTF8): Variant;
var aField: TSynTableFieldProperties;
begin
  if IsRowID(Pointer(FieldName)) then
    result := VID else begin
    CheckVTableInitialized;
    aField := VTable.FieldFromName[FieldName];
    if aField=nil then
      raise Exception.CreateFmt('Unknown %s property',[FieldName]) else
    result := GetFieldValue(aField);
  end;
end;

function TSynTableData.GetFieldValue(aField: TSynTableFieldProperties): Variant;
begin
  CheckVTableInitialized;
................................................................................
var F: TSynTableFieldProperties;
begin
  CheckVTableInitialized;
  if IsRowID(Pointer(FieldName)) then
    VID := Value else begin
    F := VTable.FieldFromName[FieldName];
    if F=nil then
      raise Exception.CreateFmt('Unknown %s property',[FieldName]) else
      SetFieldValue(F,Value);
  end;
end;

procedure TSynTableData.SetFieldValue(aField: TSynTableFieldProperties; const Value: Variant);
begin
  SetFieldSBFValue(aField,aField.SBF(Value));
................................................................................
  Result := VTable.Validate(Pointer(VValue),RecordIndex);
end;

{$endif DELPHI5OROLDER}

{ TSynMapFile }

var
  ExeMapFile: TSynMapFile = nil;

const
  MAGIC_MAB = $A5A5A5A5;

type
  TSynLZHead = packed record
    Magic: cardinal;
    CompressedSize: integer;
................................................................................
    S^.Stop := Addr-1;
    inc(PtrUInt(S),A.ElemSize);
    S^.Start := Addr;
  end;
  S^.Stop := Addr+FromVarUInt32(P);
  R.fCurrentPos := PtrUInt(P)-PtrUInt(R.fMap.fBuf);
end;






constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean=true);

  procedure LoadMap;
    var P, PEnd: PUTF8Char;
    procedure NextLine;
    begin
................................................................................
var SymCount, UnitCount, i: integer;
    MabFile: TFileName;
    MapAge, MabAge: TDateTime;
begin
  fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol,@SymCount);
  fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit,nil,nil,nil,@UnitCount);
  // 1. search for an external .map file matching the running .exe/.dll name
  if aExeName='' then
    fMapFile := GetModuleName(hInstance) else


    fMapFile := aExeName;
  fMapFile := ChangeFileExt(fMapFile,'.map');
  MabFile := ChangeFileExt(fMapFile,'.mab');
  MapAge := FileAgeToDateTime(fMapFile);
  MabAge := FileAgeToDateTime(MabFile);
  if (MabAge<=MapAge) and (MapAge>0) then
    LoadMap; // if no faster-to-load .mab available and accurate
................................................................................
  S := A.Value^;
  Diff := S^.Start;
  W.WriteVarUInt32(Diff);
  if W.fPos+n*5>W.fBufLen then
    W.fTotalWritten := W.Flush;
  with W do
    if fPos+n*5>fBufLen then // BufLen=1 shl 19=512 KB should be enough
      raise Exception.CreateFmt('too big %s',[PDynArrayTypeInfo(A.TypeInfo).Name]) else
      P := @PByteArray(fBuf)^[fPos];
  Beg := PtrUInt(P);
  for i := 1 to n-1 do begin
    inc(PtrUInt(S),A.ElemSize);
    P := ToVarUInt32(S^.Start-Diff,P);
    Diff := S^.Start;
  end;
................................................................................
        until L>R;
        exit;
      end;
  until L>R;
  result := -1;
end;

const

  /// Delphi linker starts the code section at this fixed offset
  CodeSection = $1000;

class procedure TSynMapFile.Log(W: TTextWriter; Addr: PtrUInt);
var u, s, Line: integer;
begin
  if (W=nil) or (Addr=0) or (ExeMapFile=nil) then
    exit;
  with ExeMapFile do
  if HasDebugInfo then begin
    dec(Addr,fGetModuleHandle);
    s := FindSymbol(Addr);
    u := FindUnit(Addr,Line);
    if s<0 then begin
      if u<0 then
        exit;
................................................................................
  end;
end;

procedure TSynLog.LogFileHeader;
begin
  QueryPerformanceFrequency(fFrequencyTimeStamp);
  ExeVersionRetrieve;
  if ExeMapFile=nil then begin
    ExeMapFile := TSynMapFile.Create;
    GarbageCollector.Add(ExeMapFile);
    ExeMapFile.fGetModuleHandle := GetModuleHandle(nil)+CodeSection;
  end;
  // array of const is buggy under Delphi 5 :( -> use fWriter.Add*()
  with ExeVersion, SystemInfo, OSVersionInfo, fWriter do begin
    AddString(ProgramFullSpec);
    AddShort(#13'Host='); AddString(Host);
    AddShort(' User=');   AddString(User);
    AddShort(' CPU='); Add(dwNumberOfProcessors); Add('*');
................................................................................
    Add(wProcessorRevision);
    AddShort(' OS='); Add(ord(OSVersion)); Add('.'); Add(wServicePackMajor);
    Add('='); Add(dwMajorVersion); Add('.'); Add(dwMinorVersion); Add('.');
    Add(dwBuildNumber);
    AddShort(' Wow64='); Add(integer(IsWow64));
    AddShort(' Freq='); Add(fFrequencyTimeStamp);
    if IsLibrary then begin
      AddShort(' Instance='); AddString(InstanceFileName);
    end;
    Add(#13);
    AddClassName(self.ClassType); AddShort(' '+SYNOPSE_FRAMEWORK_VERSION+' ');
    AddDateTime(Now); Add(#13,#13);
  end;
  QueryPerformanceCounter(fStartTimeStamp);
  fHeaderWritten := true;






>







 







>
>
>
>
>
>
>
>
>







 







|
>







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|







 







|












|







 







|







 







<
<
<







 







>
>
>
>
>







 







|
|
>
>







 







|







 







<
>
|
<
|



|

|







 







|
|
|
<







 







|







224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
...
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
....
6952
6953
6954
6955
6956
6957
6958
6959
6960
6961
6962
6963
6964
6965
6966
6967
.....
16594
16595
16596
16597
16598
16599
16600
16601
16602
16603
16604
16605
16606
16607
16608
.....
16734
16735
16736
16737
16738
16739
16740
16741
16742
16743
16744
16745
16746
16747
16748
.....
17329
17330
17331
17332
17333
17334
17335
17336
17337
17338
17339
17340
17341
17342
17343
.....
17923
17924
17925
17926
17927
17928
17929
17930
17931
17932
17933
17934
17935
17936
17937
.....
18979
18980
18981
18982
18983
18984
18985
18986
18987
18988
18989
18990
18991
18992
18993
.....
22760
22761
22762
22763
22764
22765
22766
22767
22768
22769
22770
22771
22772
22773
22774
.....
22822
22823
22824
22825
22826
22827
22828
22829
22830
22831
22832
22833
22834
22835
22836
.....
23207
23208
23209
23210
23211
23212
23213
23214
23215
23216
23217
23218
23219
23220
23221
.....
23310
23311
23312
23313
23314
23315
23316
23317
23318
23319
23320
23321
23322
23323
23324
.....
24103
24104
24105
24106
24107
24108
24109
24110
24111
24112
24113
24114
24115
24116
24117
.....
24130
24131
24132
24133
24134
24135
24136
24137
24138
24139
24140
24141
24142
24143
24144
.....
24611
24612
24613
24614
24615
24616
24617
24618
24619
24620
24621
24622
24623
24624
24625
.....
25106
25107
25108
25109
25110
25111
25112
25113
25114
25115
25116
25117
25118
25119
25120
.....
26076
26077
26078
26079
26080
26081
26082
26083
26084
26085
26086
26087
26088
26089
26090
26091
26092
26093
26094
26095
26096
26097
26098
26099
26100
26101
26102
26103
.....
26147
26148
26149
26150
26151
26152
26153
26154
26155
26156
26157
26158
26159
26160
26161
.....
26185
26186
26187
26188
26189
26190
26191



26192
26193
26194
26195
26196
26197
26198
.....
26478
26479
26480
26481
26482
26483
26484
26485
26486
26487
26488
26489
26490
26491
26492
26493
26494
26495
26496
.....
26681
26682
26683
26684
26685
26686
26687
26688
26689
26690
26691
26692
26693
26694
26695
26696
26697
26698
.....
26735
26736
26737
26738
26739
26740
26741
26742
26743
26744
26745
26746
26747
26748
26749
.....
26882
26883
26884
26885
26886
26887
26888

26889
26890

26891
26892
26893
26894
26895
26896
26897
26898
26899
26900
26901
26902
26903
26904
.....
27599
27600
27601
27602
27603
27604
27605
27606
27607
27608

27609
27610
27611
27612
27613
27614
27615
.....
27617
27618
27619
27620
27621
27622
27623
27624
27625
27626
27627
27628
27629
27630
27631
    (inserting '?' as inlined :(...): parameters, with proper string quote) -
    with associated regression tests

  Version 1.16
  - introducing new TSynAnsiConvert and TSynAnsiFixedWidth classes, able to
    process Unicode to/from Ansi conversion in all possible code pages, with
    generic access methods and optimized handling of fixed width encodings
  - added dedicated Exception classes (ESynException, ETableDataException)
  - TSynLog allows read sharing of the .log created file
  - TSynLog now stores the executable build time, and library name (if any)
  - TSynLog and TSynMapFile now handle libraries (.dll/.ocx/.bpl) .map/.mab
    debugging information (only .exe was previously handled) 
  - TSynCache now handle an integer ResultTag: PtrInt value parameter (used e.g.
    to store the row counts of a SQL result cache)
  - TMemoryMapText class (and therefore TSynLogFile) is now able to map/open
................................................................................
  SysUtils;


const
  {{ the corresponding version of the freeware Synopse framework }
  SYNOPSE_FRAMEWORK_VERSION = '1.16'{$ifdef LVCL}+' LVCL'{$endif};

{ ************ some custom Exception classes }

type
  /// generic parent class of all custom Exception types of this unit
  ESynException = class(Exception);

  /// exception raised by all TSynTable related code
  ETableDataException = class(Exception);
  

{ ************ common types used for compatibility between compilers and CPU }

{$ifndef FPC} { make cross-compiler and cross-CPU types available to Delphi }
type

  /// a CPU-dependent unsigned integer type cast of a pointer / register
................................................................................
    move(U256,fAnsiToWide[0],256*2);
    SetLength(fWideToAnsi,65536);
    fillchar(fWideToAnsi[1],65535,ord('?')); // '?' for unknown char
    for i := 1 to 255 do
      if fAnsiToWide[i]<>0 then
        fWideToAnsi[fAnsiToWide[i]] := i;
  end else
    raise ESynException.CreateFmt('%s.Create - Invalid code page %d',
      [ClassName,fCodePage]);
end;

function TSynAnsiFixedWidth.IsValidAnsi(WideText: PWideChar; Length: integer): boolean;
var i: integer;
    wc: cardinal;
begin
  result := false;
................................................................................
    exit; // avoid GPF if void
  if Stream.InheritsFrom(TCustomMemoryStream) then begin
    Posi := MemStream.Seek(0,soFromCurrent);
    PosiEnd := Posi+SaveToLength;
    if PosiEnd>MemStream.Size then
      MemStream.Size := PosiEnd;
    if SaveTo(PAnsiChar(MemStream.Memory)+Posi)-MemStream.Memory<>PosiEnd then
      EStreamError.Create('TDynArray.SaveToStream');
    MemStream.Seek(PosiEnd,soFromBeginning);
  end else begin
    tmp := SaveTo;
    Stream.Write(pointer(tmp)^,length(tmp));
  end;
end;

................................................................................
function TDynArray.SaveTo: RawByteString;
var Len: integer;
begin
  Len := SaveToLength;
  SetString(result,nil,Len);
  if Len<>0 then
    if SaveTo(pointer(result))-pointer(result)<>Len then
      raise ESynException.Create('TDynArray.SaveTo');
end;

function JSONArrayCount(P: PUTF8Char): integer;
var n: integer;
begin
  result := -1;
  n := 0;
................................................................................

procedure TDynArray.Init(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil);
var Typ: PDynArrayTypeInfo absolute aTypeInfo;
begin
  TypeInfo := aTypeInfo;
  Value := @aValue;
  if Typ^.Kind<>tkDynArray then
    raise ESynException.CreateFmt('%s is not a dynamic array',[Typ^.Name]);
  inc(PtrUInt(Typ),ord(Typ^.Name[0]));
  with Typ^ do begin
    ElemSize := elSize;
    if elType=nil then
      ElemType := nil else
      ElemType := elType^;
  end;
................................................................................
      if looped then
        Break else begin
        result := 0;
        n := first;
        looped := true;
      end;
  until false;
  raise ESynException.Create('HashFind'); // we should never reach here
end;

function TDynArrayHashed.GetHashFromIndex(aIndex: Integer): Cardinal;
begin
  if cardinal(aIndex)>=cardinal(Count) then
    result := 0 else
    // it's faster to rehash than to loop in fHashs[].Index values
................................................................................

function TTextWriter.Text: RawUTF8;
begin
  Flush;
  if fStream.InheritsFrom(TRawByteStringStream) then
    if fInitialStreamPosition=0 then
      result := TRawByteStringStream(fStream).DataString else
      raise ESynException.CreateFmt(
        'TTextWriter.Text with fInitialStreamPosition=%d',[fInitialStreamPosition]) else
  if fStream.InheritsFrom(TMemoryStream) then
    SetString(result,PAnsiChar(TMemoryStream(fStream).Memory)+fInitialStreamPosition,
      TMemoryStream(fStream).Seek(0,soFromCurrent)-fInitialStreamPosition) else
    result := '';
end;

................................................................................
    if aCustomSize>Available then
      fBufSize := Int64Rec(Available).Lo;
      fBufSize := aCustomSize;
  end;
  with Int64Rec(fFileSize) do
    fMap := CreateFileMapping(fFile,nil,PAGE_READONLY,Hi,Lo,nil);
  if fMap=0 then
    raise ESynException.Create('MemoryMap.Map');
  with Int64Rec(aCustomOffset) do
    fBuf := MapViewOfFile(fMap,FILE_MAP_READ,Hi,Lo,fBufSize);
  if fBuf=nil then begin
    // Windows failed to find a contiguous VA space -> fall back on direct read
    CloseHandle(fMap);
    fMap := 0;
  end else
................................................................................
  fFileStream := TFileStream.Create(aFileName,fmOpenRead or fmShareDenyNone);
  Create(fFileStream.Handle);
end;

constructor TSynMemoryStreamMapped.Create(aFile: THandle; aCustomSize: cardinal; aCustomOffset: Int64);
begin
  if not fMap.Map(aFile) then
    raise ESynException.CreateFmt('%s mapping error',[ClassName]);
  inherited Create(fMap.fBuf,fMap.fBufSize);
end;

destructor TSynMemoryStreamMapped.Destroy;
begin
  fMap.UnMap;
  fFileStream.Free;
................................................................................
        end;
        PInteger(PBeg)^ := PAnsiChar(P)-PBeg-4; // format: Isize+varUInt32s
      end;
      wkSorted: begin
        PBeg := PAnsiChar(P)+4; // leave space for chunk size
        P := PByte(CleverStoreInteger(pointer(PI),PBeg,PEnd,ValuesCount,n));
        if P=nil then
          raise ESynException.Create('WriteVarUInt32Array: data not sorted');
        PInteger(PBeg-4)^ := PAnsiChar(P)-PBeg; // format: Isize+cleverStorage
      end;
      end;
      inc(PtrUInt(PI),n*4);
      fPos := PtrUInt(P)-PtrUInt(fBuf);
      inc(fTotalWritten,PtrUInt(fPos-pos));
      dec(ValuesCount,n);
................................................................................
procedure TFileBufferReader.Close;
begin
  fMap.UnMap;
end;

procedure TFileBufferReader.ErrorInvalidContent;
begin
  raise ESynException.Create('TFileBufferReader: invalid content');
end;

procedure TFileBufferReader.OpenFrom(aBuffer: pointer; aBufferSize: cardinal);
begin
  fCurrentPos := 0;
  fMap.Map(aBuffer,aBufferSize);
end;
................................................................................
  if (self<>nil) and ((RecordBuffer=nil) or (RecordBufferLen=0)) then begin
    // no data yet -> use default
    RecordBuffer := pointer(fDefaultRecordData);
    RecordBufferLen := fDefaultRecordLength;
  end;
  if RecordBuffer=pointer(result) then
    // update content code below will fail -> please correct calling code
    raise ETableDataException.Create('In-place call of TSynTable.UpdateFieldData');
  if (self=nil) or (cardinal(FieldIndex)>=cardinal(fField.Count)) then begin
    SetString(result,PAnsiChar(RecordBuffer),RecordBufferLen);
    exit;
  end;
  F := TSynTableFieldProperties(fField.List[FieldIndex]);
  NewSize := length(NewFieldData);
  if NewSize=0 then begin
................................................................................
  move(NewData^,PByteArray(result)[DestOffset],NewSize);
  move(Dest[OldSize],PByteArray(result)[DestOffset+NewSize],RecordBufferLen-DestOffset);
end;

constructor TSynTable.Create(const aTableName: RawUTF8);
begin
  if not FieldNameValid(pointer(aTableName)) then
    raise ETableDataException.CreateFmt('Invalid TSynTable.Create(%s)',[aTableName]);
  fTableName := aTableName;
  fField := TObjectList.Create;
  fFieldVariableIndex := -1;
end;

procedure TSynTable.LoadFrom(var RD: TFileBufferReader);
var n, i: integer;
................................................................................
      aSize := Getlength(Data);
      WR.Write(Data,aSize);
      Inc(PtrUInt(Data),aSize);
    end else
      // add default field content for a newly added field
      WR.Write(Pointer(fDefaultFieldData),fDefaultFieldLength);
    if WR.fTotalWritten>1 shl 30 then
      raise ETableDataException.Create('File size too big (>1GB)') else
      Offsets64[Count] := WR.fTotalWritten;
    IDs[Count] := ID;
    NewIndexs[Index] := Count;
    inc(Count);
  end;
end;

................................................................................
      // both indexes equal -1 -> force sort
      OrderedIndexSort(0,OrderedIndexCount-1);
      OrderedIndexNotSorted := false;
    end else begin
      // added record
      if tfoUnique in Options then begin
        if fOrderedIndexFindAdd<0 then
          raise ETableDataException.CreateFmt(
            '%s.CheckConstraint call needed before %s.OrderedIndexUpdate',[ClassName,Name]);
        OrderedIndexReverseSet(InsertInteger(OrderedIndex,OrderedIndexCount,
          aNewIndex,fOrderedIndexFindAdd));
      end else begin
        AddInteger(OrderedIndex,OrderedIndexCount,aNewIndex);
        OrderedIndexReverseSet(OrderedIndexCount-1);
        OrderedIndexNotSorted := true; // -> OrderedIndexSort() call on purpose
................................................................................
{$ifndef DELPHI5OROLDER}

{ TSynTableData }

procedure TSynTableData.CheckVTableInitialized;
begin
  if VTable=nil then
    raise ETableDataException.Create('TSynTableData non initialized');
end;

{$ifdef USESYNTABLEVARIANT}

function TSynTableData.GetFieldValue(const FieldName: RawUTF8): Variant;
var aField: TSynTableFieldProperties;
begin
  if IsRowID(Pointer(FieldName)) then
    result := VID else begin
    CheckVTableInitialized;
    aField := VTable.FieldFromName[FieldName];
    if aField=nil then
      raise ETableDataException.CreateFmt('Unknown %s property',[FieldName]) else
    result := GetFieldValue(aField);
  end;
end;

function TSynTableData.GetFieldValue(aField: TSynTableFieldProperties): Variant;
begin
  CheckVTableInitialized;
................................................................................
var F: TSynTableFieldProperties;
begin
  CheckVTableInitialized;
  if IsRowID(Pointer(FieldName)) then
    VID := Value else begin
    F := VTable.FieldFromName[FieldName];
    if F=nil then
      raise ETableDataException.CreateFmt('Unknown %s property',[FieldName]) else
      SetFieldValue(F,Value);
  end;
end;

procedure TSynTableData.SetFieldValue(aField: TSynTableFieldProperties; const Value: Variant);
begin
  SetFieldSBFValue(aField,aField.SBF(Value));
................................................................................
  Result := VTable.Validate(Pointer(VValue),RecordIndex);
end;

{$endif DELPHI5OROLDER}

{ TSynMapFile }




const
  MAGIC_MAB = $A5A5A5A5;

type
  TSynLZHead = packed record
    Magic: cardinal;
    CompressedSize: integer;
................................................................................
    S^.Stop := Addr-1;
    inc(PtrUInt(S),A.ElemSize);
    S^.Start := Addr;
  end;
  S^.Stop := Addr+FromVarUInt32(P);
  R.fCurrentPos := PtrUInt(P)-PtrUInt(R.fMap.fBuf);
end;

const
  /// Delphi linker starts the code section at this fixed offset
  CODE_SECTION = $1000;


constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean=true);

  procedure LoadMap;
    var P, PEnd: PUTF8Char;
    procedure NextLine;
    begin
................................................................................
var SymCount, UnitCount, i: integer;
    MabFile: TFileName;
    MapAge, MabAge: TDateTime;
begin
  fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol,@SymCount);
  fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit,nil,nil,nil,@UnitCount);
  // 1. search for an external .map file matching the running .exe/.dll name
  if aExeName='' then begin
    fMapFile := GetModuleName(hInstance);
    fGetModuleHandle := GetModuleHandle(pointer(ExtractFileName(fMapFile)))+CODE_SECTION;
  end else
    fMapFile := aExeName;
  fMapFile := ChangeFileExt(fMapFile,'.map');
  MabFile := ChangeFileExt(fMapFile,'.mab');
  MapAge := FileAgeToDateTime(fMapFile);
  MabAge := FileAgeToDateTime(MabFile);
  if (MabAge<=MapAge) and (MapAge>0) then
    LoadMap; // if no faster-to-load .mab available and accurate
................................................................................
  S := A.Value^;
  Diff := S^.Start;
  W.WriteVarUInt32(Diff);
  if W.fPos+n*5>W.fBufLen then
    W.fTotalWritten := W.Flush;
  with W do
    if fPos+n*5>fBufLen then // BufLen=1 shl 19=512 KB should be enough
      raise ESynException.CreateFmt('too big %s',[PDynArrayTypeInfo(A.TypeInfo).Name]) else
      P := @PByteArray(fBuf)^[fPos];
  Beg := PtrUInt(P);
  for i := 1 to n-1 do begin
    inc(PtrUInt(S),A.ElemSize);
    P := ToVarUInt32(S^.Start-Diff,P);
    Diff := S^.Start;
  end;
................................................................................
        until L>R;
        exit;
      end;
  until L>R;
  result := -1;
end;


var
  InstanceMapFile: TSynMapFile;

  
class procedure TSynMapFile.Log(W: TTextWriter; Addr: PtrUInt);
var u, s, Line: integer;
begin
  if (W=nil) or (Addr=0) or (InstanceMapFile=nil) then
    exit;
  with InstanceMapFile do
  if HasDebugInfo then begin
    dec(Addr,fGetModuleHandle);
    s := FindSymbol(Addr);
    u := FindUnit(Addr,Line);
    if s<0 then begin
      if u<0 then
        exit;
................................................................................
  end;
end;

procedure TSynLog.LogFileHeader;
begin
  QueryPerformanceFrequency(fFrequencyTimeStamp);
  ExeVersionRetrieve;
  if InstanceMapFile=nil then begin
    InstanceMapFile := TSynMapFile.Create;
    GarbageCollector.Add(InstanceMapFile);

  end;
  // array of const is buggy under Delphi 5 :( -> use fWriter.Add*()
  with ExeVersion, SystemInfo, OSVersionInfo, fWriter do begin
    AddString(ProgramFullSpec);
    AddShort(#13'Host='); AddString(Host);
    AddShort(' User=');   AddString(User);
    AddShort(' CPU='); Add(dwNumberOfProcessors); Add('*');
................................................................................
    Add(wProcessorRevision);
    AddShort(' OS='); Add(ord(OSVersion)); Add('.'); Add(wServicePackMajor);
    Add('='); Add(dwMajorVersion); Add('.'); Add(dwMinorVersion); Add('.');
    Add(dwBuildNumber);
    AddShort(' Wow64='); Add(integer(IsWow64));
    AddShort(' Freq='); Add(fFrequencyTimeStamp);
    if IsLibrary then begin
      AddShort(' Instance='); AddJSONEscapeString(InstanceFileName);
    end;
    Add(#13);
    AddClassName(self.ClassType); AddShort(' '+SYNOPSE_FRAMEWORK_VERSION+' ');
    AddDateTime(Now); Add(#13,#13);
  end;
  QueryPerformanceCounter(fStartTimeStamp);
  fHeaderWritten := true;

Changes to SynLZ.pas.

1
2
3
4
5
6
7
8
9
...
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
...
180
181
182
183
184
185
186



187
188
189
190
191
192
193
....
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
....
1091
1092
1093
1094
1095
1096
1097
1098




1099
1100
1101
1102
1103
1104
1105
/// SynLZ Compression routines
// - licensed under a MPL/GPL/LGPL tri-license; version 1.15
unit SynLZ;

{
    This file is part of Synopse SynLZ Compression.

    Synopse SynLZ Compression. Copyright (C) 2012 Arnaud Bouchez
      Synopse Informatique - http://synopse.info
................................................................................

  Conclusion:
   SynLZ compresses better than LZ4,
   SynLZ is faster to compress than LZ4,
   but SynLZ is slower to decompress than LZ4,
   and SynLZ is still very competitive for our Client-Server mORMot purpose ;)

   
  Revision history

  Version 1.6
  - first release, associated with the main Synopse SQLite3 framework

  Version 1.13
  - code modifications to compile with Delphi 5 compiler
................................................................................
  - comment refactoring (mostly for inclusion in SynProject documentation)
  - new CompressSynLZ function, for THttpSocket.RegisterCompress - this
    function will return 'synlzo' as "ACCEPT-ENCODING:" HTTP header parameter

  Version 1.15
  - force ignore asm version of the code if PUREPASCAL conditional is defined




}

interface

{$I Synopse.inc}

/// get maximum possible (worse) compressed size for out_p
................................................................................
  assert(result=dst-dst_beg);
end;

function Hash32(P: PIntegerArray; L: integer): cardinal;
// faster than Adler32, even asm version, because read DWORD aligned data
var s1,s2: cardinal;
    i: integer;
const Mask: array[0..3] of cardinal = (0,$ff,$ffff,$ffffff);
begin
  if P<>nil then begin
    s1 := 0;
    s2 := 0;
    for i := 1 to L shr 4 do begin // 16 bytes (4 DWORD) by loop - aligned read
      inc(s1,P^[0]);
      inc(s2,s1);
................................................................................
      inc(PtrUInt(P),16);
    end;
    for i := 1 to (L shr 2)and 3 do begin // 4 bytes (DWORD) by loop
      inc(s1,P^[0]);
      inc(s2,s1);
      inc(PtrUInt(P),4);
    end;
    inc(s1,P^[0] and Mask[L and 3]);      // remaining 0..3 bytes




    inc(s2,s1);
    result := s1 xor (s2 shl 16);
  end else
    result := 0;
end;

function CompressSynLZ(var Data: AnsiString; Compress: boolean): AnsiString;
|







 







|







 







>
>
>







 







<







 







|
>
>
>
>







1
2
3
4
5
6
7
8
9
...
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
...
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
....
1073
1074
1075
1076
1077
1078
1079

1080
1081
1082
1083
1084
1085
1086
....
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
/// SynLZ Compression routines
// - licensed under a MPL/GPL/LGPL tri-license; version 1.16
unit SynLZ;

{
    This file is part of Synopse SynLZ Compression.

    Synopse SynLZ Compression. Copyright (C) 2012 Arnaud Bouchez
      Synopse Informatique - http://synopse.info
................................................................................

  Conclusion:
   SynLZ compresses better than LZ4,
   SynLZ is faster to compress than LZ4,
   but SynLZ is slower to decompress than LZ4,
   and SynLZ is still very competitive for our Client-Server mORMot purpose ;)


  Revision history

  Version 1.6
  - first release, associated with the main Synopse SQLite3 framework

  Version 1.13
  - code modifications to compile with Delphi 5 compiler
................................................................................
  - comment refactoring (mostly for inclusion in SynProject documentation)
  - new CompressSynLZ function, for THttpSocket.RegisterCompress - this
    function will return 'synlzo' as "ACCEPT-ENCODING:" HTTP header parameter

  Version 1.15
  - force ignore asm version of the code if PUREPASCAL conditional is defined

  Version 1.16
  - fixed potential GPF issue in Hash32() function

}

interface

{$I Synopse.inc}

/// get maximum possible (worse) compressed size for out_p
................................................................................
  assert(result=dst-dst_beg);
end;

function Hash32(P: PIntegerArray; L: integer): cardinal;
// faster than Adler32, even asm version, because read DWORD aligned data
var s1,s2: cardinal;
    i: integer;

begin
  if P<>nil then begin
    s1 := 0;
    s2 := 0;
    for i := 1 to L shr 4 do begin // 16 bytes (4 DWORD) by loop - aligned read
      inc(s1,P^[0]);
      inc(s2,s1);
................................................................................
      inc(PtrUInt(P),16);
    end;
    for i := 1 to (L shr 2)and 3 do begin // 4 bytes (DWORD) by loop
      inc(s1,P^[0]);
      inc(s2,s1);
      inc(PtrUInt(P),4);
    end;
    case L and 3 of // remaining 0..3 bytes
    1: inc(s1,PByte(P)^);
    2: inc(s1,PWord(P)^);
    3: inc(s1,PWord(P)^ or (PByteArray(P)^[2] shl 16));
    end;
    inc(s2,s1);
    result := s1 xor (s2 shl 16);
  end else
    result := 0;
end;

function CompressSynLZ(var Data: AnsiString; Compress: boolean): AnsiString;

Changes to SynSQLite3.pas.

105
106
107
108
109
110
111
112

113
114
115
116
117
118
119
...
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
....
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
....
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
....
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
....
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
....
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
....
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
....
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
....
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
....
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
....
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
....
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
....
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
....
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
....
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
....
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
....
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
....
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
....
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
....
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
....
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
    for an optional integer pointer, to return the count of row data
  - added an optional behavior parameter to TSQLDataBase.TransactionBegin method
  - reintroduce TSQLDataBaseSQLFunction.Create() constructor, and added some
    TSQLDataBase.RegisterSQLFunction() overloaded methods
  - fixed issue in TSQLRequest.Reset() which was triggered an error about the
    latest statement execution
  - fixed rounding issue when exporting DOUBLE columns into JSON
  - fixed issue of unraised exception in TSQLRequest.PrepareNext 

  - engine is now compiled including tracing within the FTS3 extension - added
    sqlite3_trace() function prototype to register your own tracing callback

    Todo:
    - port to systems other than Delphi+Win32 (use external DLL?)
}

................................................................................
{$endif}

{$define INCLUDE_FTS3}
{ define this if you want to include the FTS3/FTS4 feature into the library
  - FTS3 is an SQLite module implementing full-text search
  - will include also FTS4 extension module since 3.7.4
  - see http://www.sqlite.org/fts3.html for documentation
  - not defined by default, to save about 50 KB of code size
  - should be defined for both SynSQLite3 and SQLite3 units }

{$ifdef INCLUDE_FTS3}
  {$define INCLUDE_TRACE} 
  { define this is you want to include the TRACE feature into the library
   - our C source code custom header will define SQLITE_OMIT_TRACE if FTS3/FST4
   is not defined }
................................................................................
const
  /// SQL statement to get all tables names in the current database file
  // (taken from official SQLite3 documentation)
  SQL_GET_TABLE_NAMES =
    'SELECT name FROM sqlite_master WHERE type=''table'' AND name NOT LIKE ''sqlite_%'';';

type
  /// custom SQLite3 Exception type
  ESQLException = class(Exception)
  public
    /// the DB which raised this exception
    DB: TSQLite3DB;
    /// the corresponding error code
    ErrorCode: integer;
    /// create the exception, getting the message from DB
    constructor Create(aDB: TSQLite3DB; aErrorCode: integer); reintroduce; overload;
    /// create the exception, getting the message from caller
    constructor Create(const aMessage: string; aErrorCode: integer); reintroduce; overload;
  end;

{{ test the result state of a sqlite3_*() function
  - raise a ESQLException if the result state is an error
  - return the result state otherwize (SQLITE_OK,SQLITE_ROW,SQLITE_DONE e.g.) }
function sqlite3_check(DB: TSQLite3DB; aResult: integer): integer;

{{ Returns a pointer to a block of memory at least N bytes in length
 - should call native malloc() function, i.e. GetMem() in this unit }
function sqlite3_malloc(n: Integer): Pointer;
  {$ifndef USEFASTCALL}cdecl;{$endif} external;
................................................................................
    function GetParamCount: integer;

  // 1. general request process
  public
    {{ Prepare a UTF-8 encoded SQL statement
     - compile the SQL into byte-code
     - parameters ? ?NNN :VV @VV $VV can be bound with Bind*() functions below
     - raise an ESQLException on any error }
    function Prepare(DB: TSQLite3DB; const SQL: RawUTF8): integer;
    {{ Prepare a WinAnsi SQL statement
     - behave the same as Prepare() }
    function PrepareAnsi(DB: TSQLite3DB; const SQL: WinAnsiString): integer;
    {{ Prepare the next SQL command initialized in previous Prepare()
     - raise an ESQLException on any error }
    function PrepareNext: integer;
    {{ Evaluate An SQL Statement, returning the sqlite3_step() result status:
     - return SQLITE_ROW on success, with data ready to be retrieved via the
      Field*() methods
     - return SQLITE_DONE if the SQL commands were executed
     - raise an ESQLException on any error }
    function Step: integer;
    {{ Reset A Prepared Statement Object
     - reset a prepared statement object back to its initial state,
      ready to be re-executed.
     - any SQL statement variables that had values bound to them using the Bind*()
      function below retain their values. Use BindReset() to reset the bindings
     - return SQLITE_OK on success, or the previous Step error code }
    function Reset: integer;
    {{ Execute all SQL statements already prepared by a call to Prepare()
     - the statement is closed
     - raise an ESQLException on any error }
    procedure ExecuteAll; overload;
    {{ Execute all SQL statements in the aSQL UTF-8 encoded string
     - internaly call Prepare() then Step then PrepareNext until end of aSQL
     - Close is always called internaly
     - raise an ESQLException on any error }
    procedure ExecuteAll(aDB: TSQLite3DB; const aSQL: RawUTF8); overload;
    {{ Execute one SQL statement already prepared by a call to Prepare()
     - the statement is closed
     - raise an ESQLException on any error }
    procedure Execute; overload;
    {{ Execute one SQL statement in the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL: call Prepare() then Step once
     - Close is always called internaly
     - raise an ESQLException on any error }
    procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8); overload;
    {{ Execute a SQL statement which return integers from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of INTEGER
     - return result as a dynamic array of Int64 in ID
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLException on any error }
    function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var ID: TInt64DynArray): integer; overload;
    {{ Execute a SQL statement which return one integer from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of INTEGER
     - return result as an unique Int64 in ID
     - raise an ESQLException on any error }
    procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out ID: Int64); overload;
    {{ Execute a SQL statement which return one TEXT value from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of TEXT
     - raise an ESQLException on any error }
    procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out Value: RawUTF8); overload;
    {{ Execute a SQL statement which return TEXT from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of TEXT
     - return result as a dynamic array of RawUTF8 in ID
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLException on any error }
    function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer; overload;
    /// Execute one SQL statement which return the results in JSON format
    // - JSON format is more compact than XML and well supported
    // - Execute the first statement in aSQL
    // - if SQL is '', the statement should have been prepared, reset and bound if necessary
    // - raise an ESQLException on any error
    // - JSON data is added to TStream, with UTF-8 encoding
    // - if Expand is true, JSON data is an array of objects, for direct use
    // with any Ajax or .NET client:
    // & [ {"col1":val11,"col2":"val12"},{"col1":val21,... ]
    // - if Expand is false, JSON data is serialized (used in TSQLTableJSON)
    // & { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
    // - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"'
................................................................................
    function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; JSON: TStream;
      Expand: boolean=false): PtrInt; overload;
    /// Execute one SQL statement which return the results in JSON format
    // - use internaly Execute() above with a TRawByteStringStream, and return a string
    // - BLOB field value is saved as Base64, e.g. '"\uFFF0base64encodedbinary"'
    // - returns the number of data rows added to JSON (excluding the headers)
    // in the integer variable mapped by aResultCount (if any)
    // - if any error occurs, the ESQLException is handled and '' is returned
    function ExecuteJSON(aDB: TSQLite3DB; const aSQL: RawUTF8; Expand: boolean=false;
      aResultCount: PPtrInt=nil): RawUTF8;
    {{ Execute all SQL statements in the aSQL UTF-8 encoded string, results will
      be written as ANSI text in OutFile }
    procedure ExecuteDebug(aDB: TSQLite3DB; const aSQL: RawUTF8; const OutFile: Text);
    {{ close the Request handle
     - call it even if an ESQLException has been raised }
    procedure Close;

    {{ read-only access to the Request (SQLite3 statement) handle }
    property Request: TSQLite3Statement read fRequest;
    {{ read-only access to the SQLite3 database handle }
    property RequestDB: TSQLite3DB read fDB;
    {{ returns true if the current prepared statement makes no direct changes
................................................................................
  public
    {{ Reset All Bindings On A Prepared Statement
     - Contrary to the intuition of many, Reset() does not reset the bindings
      on a prepared statement. Use this routine to reset all host parameter }
    procedure BindReset;
    {{ bind a NULL value to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLException on any error }
    procedure BindNull(Param: Integer); 
    {{ bind an integer value to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLException on any error }
    procedure Bind(Param: Integer; Value: Int64); overload;
    {{ bind a double value to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLException on any error }
    procedure Bind(Param: Integer; Value: double); overload;
    {{ bind a UTF-8 encoded string to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLException on any error }
    procedure Bind(Param: Integer; const Value: RawUTF8); overload;
    {{ bind a Blob buffer to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLException on any error }
    procedure Bind(Param: Integer; Data: pointer; Size: integer); overload;
    {{ bind a Blob TCustomMemoryStream buffer to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLException on any error }
    procedure Bind(Param: Integer; Data: TCustomMemoryStream); overload;
    {{ bind a ZeroBlob buffer to a parameter
     - uses a fixed amount of memory (just an integer to hold its size) while
      it is being processed. Zeroblobs are intended to serve as placeholders
      for BLOBs whose content is later written using incremental BLOB I/O routines
      (as with TSQLBlobStream created from TSQLDataBase.Blob() e.g.).
     - a negative value for the Size parameter results in a zero-length BLOB
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLException on any error }
    procedure BindZero(Param: Integer; Size: integer);

  // 3. Field attributes after a sucessfull Step() (returned SQLITE_ROW)
  public
    {{ the field name of the current ROW  }
    function FieldName(Col: integer): RawUTF8;
    {{ the field index matching this name
................................................................................
  // - called for every row of a Statement
  // - the implementation may update the database directly by using a
  // local or shared TSQLRequest
  // - the TSQLRequest may be shared and prepared before the call for even
  // faster access than with a local TSQLRequest 
  // - no TSQLDataBase or higher levels objects can be used inside this method,
  // since all locking and try..finally protection is outside it
  // - can optionnaly trigger a ESQLException on any error
  TOnSQLStoredProc = procedure(Statement: TSQLRequest) of object;

  {{ TSQLDataBase.TransactionBegin can be deferred, immediate, or exclusive
   - tbDeferred means that no locks are acquired on the database until the
   database is first accessed. Thus with a deferred transaction, the BEGIN
   statement itself does nothing to the filesystem. Locks are not acquired
   until the first read or write operation. The first read operation against
................................................................................
     - SYSTEMNOCASE collation is added (our custom fast UTF-8 case insensitive compare,
       which is used also in the SQLite3UI unit for coherency and efficiency)
     - ISO8601 collation is added (TDateTime stored as ISO-8601 encoded TEXT)
     - WIN32CASE and WIN32NOCASE collations are added (use slow but accurate Win32 CompareW)
     - some additional SQl functions are registered: MOD, SOUNDEX/SOUNDEXFR/SOUNDEXES,
       RANK, CONCAT
     - initialize a TRTLCriticalSection to ensure that all access to the database is atomic
     - raise an ESQLException on any error }
    constructor Create(const aFileName: TFileName; const aPassword: RawUTF8='');
    {{ close a database and free its memory and context
      - if TransactionBegin was called but not commited, a RollBack is performed }
    destructor Destroy; override;
    {{ Execute all SQL statements in aSQL UTF-8 encoded string
     - can be prepared with TransactionBegin()
     - raise an ESQLException on any error }
    procedure ExecuteAll(const aSQL: RawUTF8);
    {{ Execute one SQL statements in aSQL UTF-8 encoded string
     - can be prepared with TransactionBegin()
     - raise an ESQLException on any error }
    procedure Execute(const aSQL: RawUTF8); overload;
    {{ Execute one SQL statement which return integers from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get a one field/column result of INTEGER
     - return result as a dynamic array of RawUTF8, as TEXT result
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLException on any error }
    function Execute(const aSQL: RawUTF8; var ID: TInt64DynArray): integer; overload;
    {{ Execute one SQL statement returning TEXT from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of TEXT
     - return result as a dynamic array of RawUTF8 in ID
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLException on any error }
    function Execute(const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer; overload;
    {{ Execute one SQL statement which return one integer from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get a one field/column result of INTEGER
     - return result as a dynamic array of RawUTF8, as TEXT result
     - raise an ESQLException on any error }
    procedure Execute(const aSQL: RawUTF8; out ID: Int64); overload;
    {{ Execute one SQL statement which return one UTF-8 encoded string value
     - Execute the first statement in aSQL
     - this statement must get a one field/column result of INTEGER
     - return result as a dynamic array of RawUTF8, as TEXT result
     - raise an ESQLException on any error }
    procedure Execute(const aSQL: RawUTF8; out ID: RawUTF8); overload;
    /// Execute one SQL statement returning its results in JSON format
    // - the BLOB data is encoded as '"\uFFF0base64encodedbinary"'
    function ExecuteJSON(const aSQL: RawUTF8; Expand: boolean=false; aResultCount: PPtrInt=nil): RawUTF8;
    {{ begin a transaction
     - Execute SQL statements with Execute() procedure below
     - must be ended with Commit on success
     - must be aborted with Rollback after an ESQLException raised
    - The default transaction behavior is tbDeferred }
    procedure TransactionBegin(aBehavior: TSQLDataBaseTransactionBehaviour = tbDeferred);
    {{ end a transaction: write all Execute() statements to the disk }
    procedure Commit;
    {{ abort a transaction: restore the previous state of the database }
    procedure RollBack;
    {{ return the last Insert Rowid }
................................................................................

    {{ open a BLOB incrementally for read[/write] access
     - find a BLOB located in row RowID, column ColumnName, table TableName
      in database DBName; in other words, the same BLOB that would be selected by:
      ! SELECT ColumnName FROM DBName.TableName WHERE rowid = RowID;
     - use after a TSQLRequest.BindZero() to reserve Blob memory
     - if RowID=-1, then the last inserted RowID is used
     - will raise an ESQLException on any error }
    function Blob(const DBName, TableName, ColumnName: RawUTF8;
      RowID: Int64=-1; ReadWrite: boolean=false): TSQLBlobStream;
    {{ backup of the opened Database into an external file name
     - don't use the experimental SQLite Online Backup API
     - database is closed, VACCUUMed, copied, then reopened: it's very fast  }
    function Backup(const BackupFileName: TFileName): boolean;

................................................................................
var
  /// in order to allow file encryption on disk, initialize this pointer
  // with SQLEncryptTableSize bytes of XOR tables
  // - you can use fixed or custom (SHA+AES) generated table
  // - using a fixed XOR table is very fast and provides strong enough encryption
  // - the first page (first 1024 bytes) is not encrypted, since its content
  // (mostly zero) can be used to easily guess the beginning of the key
  // - if the key is not correct, a ESQLException will be raised with
  // 'database disk image is malformed' (ErrorCode=SQLITE_CORRUPT)
  // - this table is common to ALL files accessed by the database engine: you
  // have maintain several XOR mask arrays, and set SQLEncryptTable before any
  // sqlite3*() call, to mix passowords or crypted and uncrypted databases
  // (see ChangeSQLEncryptTablePassWord() for multiple SQLEncryptTable use)
  // - please note that this encryption is compatible only with SQlite3 files
  // using the default page size of 1024
................................................................................
  if (aPassword<>'') and (aFileName<>':memory:') and (aFileName<>'') then begin
    SetLength(fCypherBlock,SQLEncryptTableSize);
    CreateSQLEncryptTableBytes(aPassword,pointer(fCypherBlock));
  end;
  fSQLFunctions := TObjectList.Create;
  result := DBOpen;
  if result<>SQLITE_OK then
    raise ESQLException.Create(fDB,result);
end;

destructor TSQLDataBase.Destroy;
{$ifndef INCLUDE_FTS3}
var S: TSQLite3Statement;
{$endif}
begin
................................................................................
    n: integer;
begin
  if self=nil then
    exit; // avoid GPF in case of call from a static-only server
  Lock('');
  try
    try
      R.Prepare(fDB,FormatUTF8('PRAGMA table_info(%);',[TableName])); // ESQLException
      SetLength(Names,64);
      n := 0;
      repeat
        if R.Step<>SQLITE_ROW then break;
        Names[n] := sqlite3_column_text(R.Request,1); // cid,name,type,notnull,dflt_value,pk
        inc(n);
      until n=64;
................................................................................
  fRequest := 0;
  fFieldCount := 0;
end;

procedure TSQLRequest.ExecuteAll;
begin
  if RequestDB=0 then
    raise ESQLException.Create(0,SQLITE_CANTOPEN);
  try
    repeat
      repeat
      until Step<>SQLITE_ROW; // all steps of this statement
    until PrepareNext=SQLITE_DONE; // all statements
  finally
    Close; // always release statement
  end;
end;

procedure TSQLRequest.Execute;
begin
  if RequestDB=0 then
    raise ESQLException.Create(0,SQLITE_CANTOPEN);
  try
    repeat
    until Step<>SQLITE_ROW; // Execute all steps of the first statement
  finally
    Close; // always release statement
  end;
end;

procedure TSQLRequest.ExecuteAll(aDB: TSQLite3DB; const aSQL: RawUTF8);
begin
  try
    Prepare(aDB,aSQL); // will raise an ESQLException on error
    ExecuteAll;
  finally
    Close; // always release statement, even if done normaly in EngineExecuteAll
  end;
end;

procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8);
begin
  try
    Prepare(aDB,aSQL); // will raise an ESQLException on error
    Execute;
  finally
    Close; // always release statement, even if done normaly in Execute
  end;
end;

function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var ID: TInt64DynArray): integer;
var LID, Res: integer;
begin
  result := 0;
  LID := length(ID);
  try
    Prepare(aDB,aSQL); // will raise an ESQLException on error
    if FieldCount>0 then
    repeat
      res := Step;
      if res=SQLITE_ROW then begin
        if result>=LID then begin
          inc(LID,256);
          SetLength(ID,LID);
................................................................................
  end;
end;

procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out ID: Int64);
begin
  ID := 0;
  try
    Prepare(aDB,aSQL); // will raise an ESQLException on error
    if FieldCount>0 then
    if Step=SQLITE_ROW then
      ID := sqlite3_column_int64(Request,0); // get first column value
  finally
    Close; // always release statement
  end;
end;

procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out Value: RawUTF8);
begin
  Value := '';
  try
    Prepare(aDB,aSQL); // will raise an ESQLException on error
    if FieldCount>0 then
    if Step=SQLITE_ROW then
      Value := sqlite3_column_text(Request,0); // get first column value
  finally
    Close; // always release statement
  end;
end;
................................................................................

function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer;
var LValues, Res: integer;
begin
  result := 0;
  LValues := length(Values);
  try
    Prepare(aDB,aSQL); // will raise an ESQLException on error
    if FieldCount>0 then
    repeat
      res := Step;
      if res=SQLITE_ROW then begin
        if result>=LValues then begin
          if LValues<256 then
            inc(LValues,16) else
................................................................................
    W: TJSONWriter;
begin
  result := 0;
  W := TJSONWriter.Create(JSON,Expand,false);
  try
    // prepare the SQL request
    if aSQL<>'' then // if not already prepared, reset and bound by caller
      Prepare(aDB,aSQL); // will raise an ESQLException on error
    if FieldCount<=0 then begin
      W.CancelAllVoid;
      exit;
    end;
    // get col names and types
    SetLength(W.ColNames,FieldCount);
    for i := 0 to FieldCount-1 do
................................................................................

procedure TSQLRequest.ExecuteDebug(aDB: TSQLite3DB; const aSQL: RawUTF8; const OutFile: Text);
var Res, i, n: integer;
begin
  {$I-}
  writeln;
  try
    Prepare(aDB,aSQL); // will raise an ESQLException on error
    repeat
      repeat
        Res := Step;
        if Res=SQLITE_ROW then begin
          n := FieldCount-1;
          for i := 0 to n do begin
            write(OutFile,FieldA(i));
................................................................................
  try
    try
      RowCount := Execute(aDB,aSQL,Stream,Expand); // create JSON data in Stream
      if aResultCount<>nil then
        aResultCount^ := RowCount;
      result := Stream.DataString;
    except
      on ESQLException do
        result := '';
    end;
    // Close has been called in Execute() above since aSQL<>''
  finally
    Stream.Free;
  end;
end;
................................................................................

function TSQLRequest.FieldA(Col: integer): WinAnsiString;
var P: PUTF8Char;
    L,L2: integer;
begin
  result := '';
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  P := sqlite3_column_text(Request,Col);
  L := SynCommons.StrLen(P); // faster than sqlite3_column_bytes(Request,Col)
  if L>0 then begin
    SetLength(result,L);
    L2 := UTF8ToWinPChar(pointer(result),P,L);
    if L2<>L then
      SetLength(result,L2);
................................................................................
  end;
end;

function TSQLRequest.FieldBlob(Col: integer): RawByteString;
var P: PAnsiChar;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  P := sqlite3_column_blob(Request,Col);
  SetString(result,P,sqlite3_column_bytes(Request,Col));
end;

function TSQLRequest.FieldBlobToStream(Col: integer): TStream;
begin
  result := TRawByteStringStream.Create(FieldBlob(Col));
end;

function TSQLRequest.FieldDouble(Col: integer): double;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_double(Request,Col);
end;

function TSQLRequest.FieldInt(Col: integer): Int64;
begin // internaly, SQLite always uses Int64 -> pure Integer function is useless
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_int64(Request,Col);
end;

function TSQLRequest.FieldName(Col: integer): RawUTF8;
var P: PUTF8Char;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  P := sqlite3_column_name(Request,Col);
  SetString(result,P,SynCommons.StrLen(P));
end;

function TSQLRequest.FieldIndex(const aColumnName: RawUTF8): integer;
begin
  if Request=0 then
    raise ESQLException.Create(RequestDB,SQLITE_MISUSE);
  for result := 0 to FieldCount-1 do
    if StrIComp(pointer(aColumnName),sqlite3_column_name(Request,result))=0 then
      exit;
  result := -1; // not found
end;

function TSQLRequest.FieldNull(Col: Integer): Boolean;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_type(Request,Col)=SQLITE_NULL;
end;

function TSQLRequest.FieldType(Col: Integer): integer;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_type(Request,Col);
end;

function TSQLRequest.FieldUTF8(Col: integer): RawUTF8;
var P: PUTF8Char;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  P := pointer(sqlite3_column_text(Request,Col));
  SetString(result,P,SynCommons.StrLen(P));
end;

function TSQLRequest.FieldValue(Col: integer): TSQLite3Value;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_value(Request,Col);
end;

function TSQLRequest.FieldW(Col: integer): RawUnicode;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLException.Create(RequestDB, SQLITE_RANGE);
  result := Utf8DecodeToRawUnicode(sqlite3_column_text(Request,Col),0);
end;

function TSQLRequest.Prepare(DB: TSQLite3DB; const SQL: RawUTF8): integer;
begin
  fDB := DB;
  fRequest := 0;
  if DB=0 then
    raise ESQLException.Create(DB,SQLITE_CANTOPEN);
  result := sqlite3_prepare_v2(RequestDB, pointer(SQL), length(SQL)+1, fRequest, fNextSQL);
  while (result=SQLITE_OK) and (Request=0) do // comment or white-space
    result := sqlite3_prepare_v2(RequestDB, fNextSQL, -1, fRequest, fNextSQL);
  fFieldCount := sqlite3_column_count(fRequest);
  sqlite3_check(RequestDB,result);
end;

................................................................................
      result := SQLITE_DONE; // nothing more to add
  end;
end;

function TSQLRequest.Reset: integer;
begin
  if Request=0 then
    raise ESQLException.Create(RequestDB,SQLITE_MISUSE);
  result := sqlite3_reset(Request); // no check here since it was PREVIOUS state
end;

function TSQLRequest.Step: integer;
begin
  if Request=0 then
    raise ESQLException.Create(RequestDB,SQLITE_MISUSE);
  result := sqlite3_check(RequestDB,sqlite3_step(Request));
end;

function TSQLRequest.GetReadOnly: Boolean;
begin
  if Request=0 then
    raise ESQLException.Create(RequestDB,SQLITE_MISUSE);
  result := sqlite3_stmt_readonly(Request);
end;

procedure TSQLRequest.FieldsToJSON(WR: TJSONWriter);
var i: integer;
begin
  if Request=0 then
    raise ESQLException.Create(RequestDB,SQLITE_MISUSE);
  if WR.Expand then
    WR.Add('{');
  for i := 0 to FieldCount-1 do begin
    if WR.Expand then
      WR.AddString(WR.ColNames[i]); // '"'+ColNames[]+'":'
    case sqlite3_column_type(Request,i) of // fast evaluation: type may vary
      SQLITE_BLOB: 
................................................................................
begin
  if Request=0 then
    result := 0 else
    result := sqlite3_bind_parameter_count(Request);
end;


{ ESQLException }

constructor ESQLException.Create(aDB: TSQLite3DB; aErrorCode: integer);
begin
  if aDB=0 then
    CreateFmt(sErrorSQLite3NoDB,[aErrorCode]) else
    Create(string(sqlite3_errmsg(aDB)),aErrorCode);
  DB := aDB;
end;

constructor ESQLException.Create(const aMessage: string; aErrorCode: integer);
begin
  ErrorCode := aErrorCode;
  Create(aMessage);
end;

function sqlite3_check(DB: TSQLite3DB; aResult: integer): integer;
begin
  if (DB=0) or (aResult in [SQLITE_ERROR..SQLITE_ROW-1]) then // possible error codes
    raise ESQLException.Create(DB,aResult);
  result := aResult;
end;


{ TSQLBlobStream }

constructor TSQLBlobStream.Create(aDB: TSQLite3DB; const DBName, TableName,






|
>







 







|







 







|
|












|







 







|





|





|










|




|



|




|






|





|




|






|





|







 







|






|







 







|



|



|



|



|



|








|







 







|







 







|






|



|






|






|





|





|







|







 







|







 







|







 







|







 







|







 







|













|











|









|












|







 







|












|







 







|







 







|







 







|







 







|







 







|







 







|












|






|







|







|









|






|







|







|






|








|







 







|






|






|







|







 







|

|







|








|







105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
....
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
....
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
....
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
....
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
....
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
....
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
....
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
....
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
....
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
....
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
....
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
....
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
....
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
....
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
....
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
....
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
....
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
....
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
....
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
....
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
    for an optional integer pointer, to return the count of row data
  - added an optional behavior parameter to TSQLDataBase.TransactionBegin method
  - reintroduce TSQLDataBaseSQLFunction.Create() constructor, and added some
    TSQLDataBase.RegisterSQLFunction() overloaded methods
  - fixed issue in TSQLRequest.Reset() which was triggered an error about the
    latest statement execution
  - fixed rounding issue when exporting DOUBLE columns into JSON
  - fixed issue of unraised exception in TSQLRequest.PrepareNext
  - renamed ESQLException into ESQLite3Exception
  - engine is now compiled including tracing within the FTS3 extension - added
    sqlite3_trace() function prototype to register your own tracing callback

    Todo:
    - port to systems other than Delphi+Win32 (use external DLL?)
}

................................................................................
{$endif}

{$define INCLUDE_FTS3}
{ define this if you want to include the FTS3/FTS4 feature into the library
  - FTS3 is an SQLite module implementing full-text search
  - will include also FTS4 extension module since 3.7.4
  - see http://www.sqlite.org/fts3.html for documentation
  - is defined by default, but can be unset to save about 50 KB of code size
  - should be defined for both SynSQLite3 and SQLite3 units }

{$ifdef INCLUDE_FTS3}
  {$define INCLUDE_TRACE} 
  { define this is you want to include the TRACE feature into the library
   - our C source code custom header will define SQLITE_OMIT_TRACE if FTS3/FST4
   is not defined }
................................................................................
const
  /// SQL statement to get all tables names in the current database file
  // (taken from official SQLite3 documentation)
  SQL_GET_TABLE_NAMES =
    'SELECT name FROM sqlite_master WHERE type=''table'' AND name NOT LIKE ''sqlite_%'';';

type
  /// custom SQLite3 dedicated Exception type
  ESQLite3Exception = class(ESynException)
  public
    /// the DB which raised this exception
    DB: TSQLite3DB;
    /// the corresponding error code
    ErrorCode: integer;
    /// create the exception, getting the message from DB
    constructor Create(aDB: TSQLite3DB; aErrorCode: integer); reintroduce; overload;
    /// create the exception, getting the message from caller
    constructor Create(const aMessage: string; aErrorCode: integer); reintroduce; overload;
  end;

{{ test the result state of a sqlite3_*() function
  - raise a ESQLite3Exception if the result state is an error
  - return the result state otherwize (SQLITE_OK,SQLITE_ROW,SQLITE_DONE e.g.) }
function sqlite3_check(DB: TSQLite3DB; aResult: integer): integer;

{{ Returns a pointer to a block of memory at least N bytes in length
 - should call native malloc() function, i.e. GetMem() in this unit }
function sqlite3_malloc(n: Integer): Pointer;
  {$ifndef USEFASTCALL}cdecl;{$endif} external;
................................................................................
    function GetParamCount: integer;

  // 1. general request process
  public
    {{ Prepare a UTF-8 encoded SQL statement
     - compile the SQL into byte-code
     - parameters ? ?NNN :VV @VV $VV can be bound with Bind*() functions below
     - raise an ESQLite3Exception on any error }
    function Prepare(DB: TSQLite3DB; const SQL: RawUTF8): integer;
    {{ Prepare a WinAnsi SQL statement
     - behave the same as Prepare() }
    function PrepareAnsi(DB: TSQLite3DB; const SQL: WinAnsiString): integer;
    {{ Prepare the next SQL command initialized in previous Prepare()
     - raise an ESQLite3Exception on any error }
    function PrepareNext: integer;
    {{ Evaluate An SQL Statement, returning the sqlite3_step() result status:
     - return SQLITE_ROW on success, with data ready to be retrieved via the
      Field*() methods
     - return SQLITE_DONE if the SQL commands were executed
     - raise an ESQLite3Exception on any error }
    function Step: integer;
    {{ Reset A Prepared Statement Object
     - reset a prepared statement object back to its initial state,
      ready to be re-executed.
     - any SQL statement variables that had values bound to them using the Bind*()
      function below retain their values. Use BindReset() to reset the bindings
     - return SQLITE_OK on success, or the previous Step error code }
    function Reset: integer;
    {{ Execute all SQL statements already prepared by a call to Prepare()
     - the statement is closed
     - raise an ESQLite3Exception on any error }
    procedure ExecuteAll; overload;
    {{ Execute all SQL statements in the aSQL UTF-8 encoded string
     - internaly call Prepare() then Step then PrepareNext until end of aSQL
     - Close is always called internaly
     - raise an ESQLite3Exception on any error }
    procedure ExecuteAll(aDB: TSQLite3DB; const aSQL: RawUTF8); overload;
    {{ Execute one SQL statement already prepared by a call to Prepare()
     - the statement is closed
     - raise an ESQLite3Exception on any error }
    procedure Execute; overload;
    {{ Execute one SQL statement in the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL: call Prepare() then Step once
     - Close is always called internaly
     - raise an ESQLite3Exception on any error }
    procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8); overload;
    {{ Execute a SQL statement which return integers from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of INTEGER
     - return result as a dynamic array of Int64 in ID
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLite3Exception on any error }
    function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var ID: TInt64DynArray): integer; overload;
    {{ Execute a SQL statement which return one integer from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of INTEGER
     - return result as an unique Int64 in ID
     - raise an ESQLite3Exception on any error }
    procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out ID: Int64); overload;
    {{ Execute a SQL statement which return one TEXT value from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of TEXT
     - raise an ESQLite3Exception on any error }
    procedure Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out Value: RawUTF8); overload;
    {{ Execute a SQL statement which return TEXT from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of TEXT
     - return result as a dynamic array of RawUTF8 in ID
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLite3Exception on any error }
    function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer; overload;
    /// Execute one SQL statement which return the results in JSON format
    // - JSON format is more compact than XML and well supported
    // - Execute the first statement in aSQL
    // - if SQL is '', the statement should have been prepared, reset and bound if necessary
    // - raise an ESQLite3Exception on any error
    // - JSON data is added to TStream, with UTF-8 encoding
    // - if Expand is true, JSON data is an array of objects, for direct use
    // with any Ajax or .NET client:
    // & [ {"col1":val11,"col2":"val12"},{"col1":val21,... ]
    // - if Expand is false, JSON data is serialized (used in TSQLTableJSON)
    // & { "FieldCount":1,"Values":["col1","col2",val11,"val12",val21,..] }
    // - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"'
................................................................................
    function Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; JSON: TStream;
      Expand: boolean=false): PtrInt; overload;
    /// Execute one SQL statement which return the results in JSON format
    // - use internaly Execute() above with a TRawByteStringStream, and return a string
    // - BLOB field value is saved as Base64, e.g. '"\uFFF0base64encodedbinary"'
    // - returns the number of data rows added to JSON (excluding the headers)
    // in the integer variable mapped by aResultCount (if any)
    // - if any error occurs, the ESQLite3Exception is handled and '' is returned
    function ExecuteJSON(aDB: TSQLite3DB; const aSQL: RawUTF8; Expand: boolean=false;
      aResultCount: PPtrInt=nil): RawUTF8;
    {{ Execute all SQL statements in the aSQL UTF-8 encoded string, results will
      be written as ANSI text in OutFile }
    procedure ExecuteDebug(aDB: TSQLite3DB; const aSQL: RawUTF8; const OutFile: Text);
    {{ close the Request handle
     - call it even if an ESQLite3Exception has been raised }
    procedure Close;

    {{ read-only access to the Request (SQLite3 statement) handle }
    property Request: TSQLite3Statement read fRequest;
    {{ read-only access to the SQLite3 database handle }
    property RequestDB: TSQLite3DB read fDB;
    {{ returns true if the current prepared statement makes no direct changes
................................................................................
  public
    {{ Reset All Bindings On A Prepared Statement
     - Contrary to the intuition of many, Reset() does not reset the bindings
      on a prepared statement. Use this routine to reset all host parameter }
    procedure BindReset;
    {{ bind a NULL value to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLite3Exception on any error }
    procedure BindNull(Param: Integer); 
    {{ bind an integer value to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLite3Exception on any error }
    procedure Bind(Param: Integer; Value: Int64); overload;
    {{ bind a double value to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLite3Exception on any error }
    procedure Bind(Param: Integer; Value: double); overload;
    {{ bind a UTF-8 encoded string to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLite3Exception on any error }
    procedure Bind(Param: Integer; const Value: RawUTF8); overload;
    {{ bind a Blob buffer to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLite3Exception on any error }
    procedure Bind(Param: Integer; Data: pointer; Size: integer); overload;
    {{ bind a Blob TCustomMemoryStream buffer to a parameter
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLite3Exception on any error }
    procedure Bind(Param: Integer; Data: TCustomMemoryStream); overload;
    {{ bind a ZeroBlob buffer to a parameter
     - uses a fixed amount of memory (just an integer to hold its size) while
      it is being processed. Zeroblobs are intended to serve as placeholders
      for BLOBs whose content is later written using incremental BLOB I/O routines
      (as with TSQLBlobStream created from TSQLDataBase.Blob() e.g.).
     - a negative value for the Size parameter results in a zero-length BLOB
     - the leftmost SQL parameter has an index of 1, but ?NNN may override it
     - raise an ESQLite3Exception on any error }
    procedure BindZero(Param: Integer; Size: integer);

  // 3. Field attributes after a sucessfull Step() (returned SQLITE_ROW)
  public
    {{ the field name of the current ROW  }
    function FieldName(Col: integer): RawUTF8;
    {{ the field index matching this name
................................................................................
  // - called for every row of a Statement
  // - the implementation may update the database directly by using a
  // local or shared TSQLRequest
  // - the TSQLRequest may be shared and prepared before the call for even
  // faster access than with a local TSQLRequest 
  // - no TSQLDataBase or higher levels objects can be used inside this method,
  // since all locking and try..finally protection is outside it
  // - can optionnaly trigger a ESQLite3Exception on any error
  TOnSQLStoredProc = procedure(Statement: TSQLRequest) of object;

  {{ TSQLDataBase.TransactionBegin can be deferred, immediate, or exclusive
   - tbDeferred means that no locks are acquired on the database until the
   database is first accessed. Thus with a deferred transaction, the BEGIN
   statement itself does nothing to the filesystem. Locks are not acquired
   until the first read or write operation. The first read operation against
................................................................................
     - SYSTEMNOCASE collation is added (our custom fast UTF-8 case insensitive compare,
       which is used also in the SQLite3UI unit for coherency and efficiency)
     - ISO8601 collation is added (TDateTime stored as ISO-8601 encoded TEXT)
     - WIN32CASE and WIN32NOCASE collations are added (use slow but accurate Win32 CompareW)
     - some additional SQl functions are registered: MOD, SOUNDEX/SOUNDEXFR/SOUNDEXES,
       RANK, CONCAT
     - initialize a TRTLCriticalSection to ensure that all access to the database is atomic
     - raise an ESQLite3Exception on any error }
    constructor Create(const aFileName: TFileName; const aPassword: RawUTF8='');
    {{ close a database and free its memory and context
      - if TransactionBegin was called but not commited, a RollBack is performed }
    destructor Destroy; override;
    {{ Execute all SQL statements in aSQL UTF-8 encoded string
     - can be prepared with TransactionBegin()
     - raise an ESQLite3Exception on any error }
    procedure ExecuteAll(const aSQL: RawUTF8);
    {{ Execute one SQL statements in aSQL UTF-8 encoded string
     - can be prepared with TransactionBegin()
     - raise an ESQLite3Exception on any error }
    procedure Execute(const aSQL: RawUTF8); overload;
    {{ Execute one SQL statement which return integers from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get a one field/column result of INTEGER
     - return result as a dynamic array of RawUTF8, as TEXT result
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLite3Exception on any error }
    function Execute(const aSQL: RawUTF8; var ID: TInt64DynArray): integer; overload;
    {{ Execute one SQL statement returning TEXT from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get (at least) one field/column result of TEXT
     - return result as a dynamic array of RawUTF8 in ID
     - return count of row in integer function result (may be < length(ID))
     - raise an ESQLite3Exception on any error }
    function Execute(const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer; overload;
    {{ Execute one SQL statement which return one integer from the aSQL UTF-8 encoded string
     - Execute the first statement in aSQL
     - this statement must get a one field/column result of INTEGER
     - return result as a dynamic array of RawUTF8, as TEXT result
     - raise an ESQLite3Exception on any error }
    procedure Execute(const aSQL: RawUTF8; out ID: Int64); overload;
    {{ Execute one SQL statement which return one UTF-8 encoded string value
     - Execute the first statement in aSQL
     - this statement must get a one field/column result of INTEGER
     - return result as a dynamic array of RawUTF8, as TEXT result
     - raise an ESQLite3Exception on any error }
    procedure Execute(const aSQL: RawUTF8; out ID: RawUTF8); overload;
    /// Execute one SQL statement returning its results in JSON format
    // - the BLOB data is encoded as '"\uFFF0base64encodedbinary"'
    function ExecuteJSON(const aSQL: RawUTF8; Expand: boolean=false; aResultCount: PPtrInt=nil): RawUTF8;
    {{ begin a transaction
     - Execute SQL statements with Execute() procedure below
     - must be ended with Commit on success
     - must be aborted with Rollback after an ESQLite3Exception raised
    - The default transaction behavior is tbDeferred }
    procedure TransactionBegin(aBehavior: TSQLDataBaseTransactionBehaviour = tbDeferred);
    {{ end a transaction: write all Execute() statements to the disk }
    procedure Commit;
    {{ abort a transaction: restore the previous state of the database }
    procedure RollBack;
    {{ return the last Insert Rowid }
................................................................................

    {{ open a BLOB incrementally for read[/write] access
     - find a BLOB located in row RowID, column ColumnName, table TableName
      in database DBName; in other words, the same BLOB that would be selected by:
      ! SELECT ColumnName FROM DBName.TableName WHERE rowid = RowID;
     - use after a TSQLRequest.BindZero() to reserve Blob memory
     - if RowID=-1, then the last inserted RowID is used
     - will raise an ESQLite3Exception on any error }
    function Blob(const DBName, TableName, ColumnName: RawUTF8;
      RowID: Int64=-1; ReadWrite: boolean=false): TSQLBlobStream;
    {{ backup of the opened Database into an external file name
     - don't use the experimental SQLite Online Backup API
     - database is closed, VACCUUMed, copied, then reopened: it's very fast  }
    function Backup(const BackupFileName: TFileName): boolean;

................................................................................
var
  /// in order to allow file encryption on disk, initialize this pointer
  // with SQLEncryptTableSize bytes of XOR tables
  // - you can use fixed or custom (SHA+AES) generated table
  // - using a fixed XOR table is very fast and provides strong enough encryption
  // - the first page (first 1024 bytes) is not encrypted, since its content
  // (mostly zero) can be used to easily guess the beginning of the key
  // - if the key is not correct, a ESQLite3Exception will be raised with
  // 'database disk image is malformed' (ErrorCode=SQLITE_CORRUPT)
  // - this table is common to ALL files accessed by the database engine: you
  // have maintain several XOR mask arrays, and set SQLEncryptTable before any
  // sqlite3*() call, to mix passowords or crypted and uncrypted databases
  // (see ChangeSQLEncryptTablePassWord() for multiple SQLEncryptTable use)
  // - please note that this encryption is compatible only with SQlite3 files
  // using the default page size of 1024
................................................................................
  if (aPassword<>'') and (aFileName<>':memory:') and (aFileName<>'') then begin
    SetLength(fCypherBlock,SQLEncryptTableSize);
    CreateSQLEncryptTableBytes(aPassword,pointer(fCypherBlock));
  end;
  fSQLFunctions := TObjectList.Create;
  result := DBOpen;
  if result<>SQLITE_OK then
    raise ESQLite3Exception.Create(fDB,result);
end;

destructor TSQLDataBase.Destroy;
{$ifndef INCLUDE_FTS3}
var S: TSQLite3Statement;
{$endif}
begin
................................................................................
    n: integer;
begin
  if self=nil then
    exit; // avoid GPF in case of call from a static-only server
  Lock('');
  try
    try
      R.Prepare(fDB,FormatUTF8('PRAGMA table_info(%);',[TableName])); // ESQLite3Exception
      SetLength(Names,64);
      n := 0;
      repeat
        if R.Step<>SQLITE_ROW then break;
        Names[n] := sqlite3_column_text(R.Request,1); // cid,name,type,notnull,dflt_value,pk
        inc(n);
      until n=64;
................................................................................
  fRequest := 0;
  fFieldCount := 0;
end;

procedure TSQLRequest.ExecuteAll;
begin
  if RequestDB=0 then
    raise ESQLite3Exception.Create(0,SQLITE_CANTOPEN);
  try
    repeat
      repeat
      until Step<>SQLITE_ROW; // all steps of this statement
    until PrepareNext=SQLITE_DONE; // all statements
  finally
    Close; // always release statement
  end;
end;

procedure TSQLRequest.Execute;
begin
  if RequestDB=0 then
    raise ESQLite3Exception.Create(0,SQLITE_CANTOPEN);
  try
    repeat
    until Step<>SQLITE_ROW; // Execute all steps of the first statement
  finally
    Close; // always release statement
  end;
end;

procedure TSQLRequest.ExecuteAll(aDB: TSQLite3DB; const aSQL: RawUTF8);
begin
  try
    Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    ExecuteAll;
  finally
    Close; // always release statement, even if done normaly in EngineExecuteAll
  end;
end;

procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8);
begin
  try
    Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    Execute;
  finally
    Close; // always release statement, even if done normaly in Execute
  end;
end;

function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var ID: TInt64DynArray): integer;
var LID, Res: integer;
begin
  result := 0;
  LID := length(ID);
  try
    Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    if FieldCount>0 then
    repeat
      res := Step;
      if res=SQLITE_ROW then begin
        if result>=LID then begin
          inc(LID,256);
          SetLength(ID,LID);
................................................................................
  end;
end;

procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out ID: Int64);
begin
  ID := 0;
  try
    Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    if FieldCount>0 then
    if Step=SQLITE_ROW then
      ID := sqlite3_column_int64(Request,0); // get first column value
  finally
    Close; // always release statement
  end;
end;

procedure TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; out Value: RawUTF8);
begin
  Value := '';
  try
    Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    if FieldCount>0 then
    if Step=SQLITE_ROW then
      Value := sqlite3_column_text(Request,0); // get first column value
  finally
    Close; // always release statement
  end;
end;
................................................................................

function TSQLRequest.Execute(aDB: TSQLite3DB; const aSQL: RawUTF8; var Values: TRawUTF8DynArray): integer;
var LValues, Res: integer;
begin
  result := 0;
  LValues := length(Values);
  try
    Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    if FieldCount>0 then
    repeat
      res := Step;
      if res=SQLITE_ROW then begin
        if result>=LValues then begin
          if LValues<256 then
            inc(LValues,16) else
................................................................................
    W: TJSONWriter;
begin
  result := 0;
  W := TJSONWriter.Create(JSON,Expand,false);
  try
    // prepare the SQL request
    if aSQL<>'' then // if not already prepared, reset and bound by caller
      Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    if FieldCount<=0 then begin
      W.CancelAllVoid;
      exit;
    end;
    // get col names and types
    SetLength(W.ColNames,FieldCount);
    for i := 0 to FieldCount-1 do
................................................................................

procedure TSQLRequest.ExecuteDebug(aDB: TSQLite3DB; const aSQL: RawUTF8; const OutFile: Text);
var Res, i, n: integer;
begin
  {$I-}
  writeln;
  try
    Prepare(aDB,aSQL); // will raise an ESQLite3Exception on error
    repeat
      repeat
        Res := Step;
        if Res=SQLITE_ROW then begin
          n := FieldCount-1;
          for i := 0 to n do begin
            write(OutFile,FieldA(i));
................................................................................
  try
    try
      RowCount := Execute(aDB,aSQL,Stream,Expand); // create JSON data in Stream
      if aResultCount<>nil then
        aResultCount^ := RowCount;
      result := Stream.DataString;
    except
      on ESQLite3Exception do
        result := '';
    end;
    // Close has been called in Execute() above since aSQL<>''
  finally
    Stream.Free;
  end;
end;
................................................................................

function TSQLRequest.FieldA(Col: integer): WinAnsiString;
var P: PUTF8Char;
    L,L2: integer;
begin
  result := '';
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  P := sqlite3_column_text(Request,Col);
  L := SynCommons.StrLen(P); // faster than sqlite3_column_bytes(Request,Col)
  if L>0 then begin
    SetLength(result,L);
    L2 := UTF8ToWinPChar(pointer(result),P,L);
    if L2<>L then
      SetLength(result,L2);
................................................................................
  end;
end;

function TSQLRequest.FieldBlob(Col: integer): RawByteString;
var P: PAnsiChar;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  P := sqlite3_column_blob(Request,Col);
  SetString(result,P,sqlite3_column_bytes(Request,Col));
end;

function TSQLRequest.FieldBlobToStream(Col: integer): TStream;
begin
  result := TRawByteStringStream.Create(FieldBlob(Col));
end;

function TSQLRequest.FieldDouble(Col: integer): double;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_double(Request,Col);
end;

function TSQLRequest.FieldInt(Col: integer): Int64;
begin // internaly, SQLite always uses Int64 -> pure Integer function is useless
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_int64(Request,Col);
end;

function TSQLRequest.FieldName(Col: integer): RawUTF8;
var P: PUTF8Char;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  P := sqlite3_column_name(Request,Col);
  SetString(result,P,SynCommons.StrLen(P));
end;

function TSQLRequest.FieldIndex(const aColumnName: RawUTF8): integer;
begin
  if Request=0 then
    raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
  for result := 0 to FieldCount-1 do
    if StrIComp(pointer(aColumnName),sqlite3_column_name(Request,result))=0 then
      exit;
  result := -1; // not found
end;

function TSQLRequest.FieldNull(Col: Integer): Boolean;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_type(Request,Col)=SQLITE_NULL;
end;

function TSQLRequest.FieldType(Col: Integer): integer;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_type(Request,Col);
end;

function TSQLRequest.FieldUTF8(Col: integer): RawUTF8;
var P: PUTF8Char;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  P := pointer(sqlite3_column_text(Request,Col));
  SetString(result,P,SynCommons.StrLen(P));
end;

function TSQLRequest.FieldValue(Col: integer): TSQLite3Value;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  result := sqlite3_column_value(Request,Col);
end;

function TSQLRequest.FieldW(Col: integer): RawUnicode;
begin
  if cardinal(Col)>=cardinal(FieldCount) then
    raise ESQLite3Exception.Create(RequestDB, SQLITE_RANGE);
  result := Utf8DecodeToRawUnicode(sqlite3_column_text(Request,Col),0);
end;

function TSQLRequest.Prepare(DB: TSQLite3DB; const SQL: RawUTF8): integer;
begin
  fDB := DB;
  fRequest := 0;
  if DB=0 then
    raise ESQLite3Exception.Create(DB,SQLITE_CANTOPEN);
  result := sqlite3_prepare_v2(RequestDB, pointer(SQL), length(SQL)+1, fRequest, fNextSQL);
  while (result=SQLITE_OK) and (Request=0) do // comment or white-space
    result := sqlite3_prepare_v2(RequestDB, fNextSQL, -1, fRequest, fNextSQL);
  fFieldCount := sqlite3_column_count(fRequest);
  sqlite3_check(RequestDB,result);
end;

................................................................................
      result := SQLITE_DONE; // nothing more to add
  end;
end;

function TSQLRequest.Reset: integer;
begin
  if Request=0 then
    raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
  result := sqlite3_reset(Request); // no check here since it was PREVIOUS state
end;

function TSQLRequest.Step: integer;
begin
  if Request=0 then
    raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
  result := sqlite3_check(RequestDB,sqlite3_step(Request));
end;

function TSQLRequest.GetReadOnly: Boolean;
begin
  if Request=0 then
    raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
  result := sqlite3_stmt_readonly(Request);
end;

procedure TSQLRequest.FieldsToJSON(WR: TJSONWriter);
var i: integer;
begin
  if Request=0 then
    raise ESQLite3Exception.Create(RequestDB,SQLITE_MISUSE);
  if WR.Expand then
    WR.Add('{');
  for i := 0 to FieldCount-1 do begin
    if WR.Expand then
      WR.AddString(WR.ColNames[i]); // '"'+ColNames[]+'":'
    case sqlite3_column_type(Request,i) of // fast evaluation: type may vary
      SQLITE_BLOB: 
................................................................................
begin
  if Request=0 then
    result := 0 else
    result := sqlite3_bind_parameter_count(Request);
end;


{ ESQLite3Exception }

constructor ESQLite3Exception.Create(aDB: TSQLite3DB; aErrorCode: integer);
begin
  if aDB=0 then
    CreateFmt(sErrorSQLite3NoDB,[aErrorCode]) else
    Create(string(sqlite3_errmsg(aDB)),aErrorCode);
  DB := aDB;
end;

constructor ESQLite3Exception.Create(const aMessage: string; aErrorCode: integer);
begin
  ErrorCode := aErrorCode;
  Create(aMessage);
end;

function sqlite3_check(DB: TSQLite3DB; aResult: integer): integer;
begin
  if (DB=0) or (aResult in [SQLITE_ERROR..SQLITE_ROW-1]) then // possible error codes
    raise ESQLite3Exception.Create(DB,aResult);
  result := aResult;
end;


{ TSQLBlobStream }

constructor TSQLBlobStream.Create(aDB: TSQLite3DB; const DBName, TableName,

Changes to SynSelfTests.pas.

186
187
188
189
190
191
192














193
194
195
196
197
198

199
200
201
202
203
204
205
206


207
208
209
210
211
212
213
....
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
....
1265
1266
1267
1268
1269
1270
1271













































1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299

1300
1301
1302
1303
1304
1305
1306
1307
1308




1309
1310
1311
1312
1313
1314
1315
....
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
....
1385
1386
1387
1388
1389
1390
1391
1392
1393

    /// test TSQLRecordExternal implementation via slower Virtual Table calls
    // - using the Virtual Table mechanism of SQLite3 is more than 2 times
    // slower than direct REST access
    procedure ExternalViaVirtualTable;
  end;
{$endif}















  /// a test case which will test the interface-based SOA implementation of
  // the mORMot framework
  TTestServiceOrientedArchitecture = class(TSynTestCase)
  protected
    fModel: TSQLModel;
    fClient: TSQLRestClientDB;

  public
    /// release the associated memory and object instances
    destructor Destroy; override;
  published
    /// initialize the SOA implementation
    procedure ServiceInitialization;
    /// test the server-side implementation
    procedure ServerSide;


  end;

{$endif}

implementation

uses
................................................................................
end;

{$endif LVCL}

{ TServiceCalculator }

type
  ICalculator = interface(IService)
    ['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
    function Add(n1,n2: integer): integer;
    function Multiply(n1,n2: Int64): Int64;
    function Subtract(n1,n2: double): double;
    procedure ToText(Value: Currency; var Result: RawUTF8);
    function ToTextFunc(Value: double): string;
  end;

  TServiceCalculator = class(TInterfacedObject, ICalculator)
  public
    function Add(n1,n2: integer): integer;
    function Subtract(n1,n2: double): double;
    function Multiply(n1,n2: Int64): Int64;
    procedure ToText(Value: Currency; var Result: RawUTF8);
    function ToTextFunc(Value: double): string;
................................................................................
function TServiceCalculator.ToTextFunc(Value: double): string;
begin
  result := DoubleToString(Value);
end;


{ TTestServiceOrientedArchitecture }














































destructor TTestServiceOrientedArchitecture.Destroy;
begin
  fClient.Free;
  fModel.Free;
  inherited;
end;

procedure TTestServiceOrientedArchitecture.ServerSide;
var I: ICalculator;
procedure TestI;
var s: RawUTF8;
begin
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
  CheckSame(I.Subtract(23,20),3);
  I.ToText(3.14,s);
  Check(s='3.14');
  Check(I.ToTextFunc(777)='777');
end;
function Ask(const Method, Params: RawUTF8; ExpectedResult: cardinal): RawUTF8;
var resp,head: RawUTF8;
begin
  Check(fClient.Server.URI('root/calculator','POST',
    '{"method":"'+Method+'", "params": [ '+Params+' ]}',resp,head,
    @SUPERVISOR_ACCESS_RIGHTS).Lo=ExpectedResult);
  result := JSONDecode(resp,'RESULT',nil,true);
end;

begin
  I := TServiceCalculator.Create;
  TestI;
  I := nil;
  if CheckFailed(fModel<>nil) or CheckFailed(fClient<>nil) or
     CheckFailed(fClient.Server.Services.Count=1) or
     CheckFailed(fClient.Server.Services.Service(0).FromFactory(I)) or
     CheckFailed(Assigned(I)) then exit;
  TestI;




  Check(Ask('None','1,2',400)='');
  Check(Ask('Add','1,2',200)='[3]');
  Check(Ask('Multiply','2,3',200)='[6]');
  Check(Ask('Subtract','23,20',200)='[3]');
  Check(Ask('ToText','777,"abc"',200)='["777"]'); // "abc" for var parameter
  Check(Ask('ToTextFunc','777',200)='["777"]');
end;
................................................................................
  fClient := TSQLRestClientDB.Create(fModel,nil,'test.db3',TSQLRestServerDB);
  // register TServiceCalculator as the ICalculator implementation on the server
  Check(fClient.Server.
    ServiceRegister(TServiceCalculator,[TypeInfo(ICalculator)],sicShared));
  // verify ICalculator RTTI-generated details
  Check(fClient.Server.Services<>nil);
  if CheckFailed(fClient.Server.Services.Count=1) then exit;
  S := fClient.Server.Services.Service(0);
  if CheckFailed(S<>nil) then exit;
  Check(S.InterfaceURI='Calculator');
  Check(S.InstanceCreation=sicShared);
  Check(S.InterfaceTypeInfo^.Kind=tkInterface);
  Check(S.InterfaceTypeInfo^.ShortName='ICalculator');
  Check(GUIDToString(S.InterfaceIID)='{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}');
  Check(S.InterfaceMangledURI='7chgmrLOCU6H1EoW9Jbl_g');
  fClient.Server.Services.ExpectMangledURI := true;
  Check(fClient.Server.Services.Service(S.InterfaceMangledURI)=S);
  fClient.Server.Services.ExpectMangledURI := false;
  Check(fClient.Server.Services.Service('CALCULAtor')=S);
  Check(fClient.Server.Services.Service('CALCULAtors')=nil);
  if CheckFailed(length(S.Methods)=5) then exit;
  for i := 0 to 4 do
    with S.Methods[i] do begin
      Check(CallingConvention=ccRegister);
      Check(URI=ExpectedURI[i]);
      Check(length(Args)=ExpectedParCount[i]);
      Check(ArgsUsed=ExpectedArgs[i]);
................................................................................
          Check(Args[2].ValueType=smvString);
      end;
    end;
end;

{$endif DELPHI5OROLDER}


end.







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






>








>
>







 







<
<
<
<
<
<
<
<
<







 







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









<
<
<
<
<
<
<
<
<
<
<








>


|



|

|
>
>
>
>







 







|








|

|
|







 







<

>
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
....
1230
1231
1232
1233
1234
1235
1236









1237
1238
1239
1240
1241
1242
1243
....
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333











1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
....
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
....
1432
1433
1434
1435
1436
1437
1438

1439
1440
    /// test TSQLRecordExternal implementation via slower Virtual Table calls
    // - using the Virtual Table mechanism of SQLite3 is more than 2 times
    // slower than direct REST access
    procedure ExternalViaVirtualTable;
  end;
{$endif}

  /// a test interface, used by TTestServiceOrientedArchitecture
  ICalculator = interface(IService)
    ['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
    function Add(n1,n2: integer): integer;
    function Multiply(n1,n2: Int64): Int64;
    function Subtract(n1,n2: double): double;
    procedure ToText(Value: Currency; var Result: RawUTF8);
    function ToTextFunc(Value: double): string;
  end;

const
  IID_ICalculator: TGUID = '{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}';

type
  /// a test case which will test the interface-based SOA implementation of
  // the mORMot framework
  TTestServiceOrientedArchitecture = class(TSynTestCase)
  protected
    fModel: TSQLModel;
    fClient: TSQLRestClientDB;
    procedure Test(I: ICalculator);
  public
    /// release the associated memory and object instances
    destructor Destroy; override;
  published
    /// initialize the SOA implementation
    procedure ServiceInitialization;
    /// test the server-side implementation
    procedure ServerSide;
    /// test the client-side implementation
    procedure ClientSide;
  end;

{$endif}

implementation

uses
................................................................................
end;

{$endif LVCL}

{ TServiceCalculator }

type









  TServiceCalculator = class(TInterfacedObject, ICalculator)
  public
    function Add(n1,n2: integer): integer;
    function Subtract(n1,n2: double): double;
    function Multiply(n1,n2: Int64): Int64;
    procedure ToText(Value: Currency; var Result: RawUTF8);
    function ToTextFunc(Value: double): string;
................................................................................
function TServiceCalculator.ToTextFunc(Value: double): string;
begin
  result := DoubleToString(Value);
end;


{ TTestServiceOrientedArchitecture }

procedure TTestServiceOrientedArchitecture.Test(I: ICalculator);
var s: RawUTF8;
    t: integer;
    i1,i2: integer;
    n1,n2: double;
begin
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
  CheckSame(I.Subtract(23,20),3);
  I.ToText(3.14,s);
  Check(s='3.14');
  Check(I.ToTextFunc(777)='777');
  for t := 1 to 1000 do begin
    i1 := Random(MaxInt)-Random(MaxInt);
    i2 := Random(MaxInt)-i1;
    Check(I.Add(i1,i2)=i1+i2);
    Check(I.Multiply(i1,i2)=Int64(i1)*Int64(i2));
    n1 := Random*1E-17-Random*1E-9;
    n2 := n1*Random;
    CheckSame(I.Subtract(n1,n2),n1-n2);
    Check(I.ToTextFunc(n1)=DoubleToString(n1));
  end;
end;

procedure TTestServiceOrientedArchitecture.ClientSide;
var I: ICalculator;
begin
  Check(fClient.ServiceRegister([TypeInfo(ICalculator)],sicShared));
  // once registered, can be accessed by its GUID or URI
  if CheckFailed(fClient.Services.Info(TypeInfo(ICalculator)).Get(I)) then
    exit;
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
  I := nil;
  if CheckFailed(fClient.Services.GUID(IID_ICalculator).Get(I)) then
    exit;
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
  I := nil;
  if CheckFailed(fClient.Services['Calculator'].Get(I)) then
    exit;
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
end;

destructor TTestServiceOrientedArchitecture.Destroy;
begin
  fClient.Free;
  fModel.Free;
  inherited;
end;

procedure TTestServiceOrientedArchitecture.ServerSide;











function Ask(const Method, Params: RawUTF8; ExpectedResult: cardinal): RawUTF8;
var resp,head: RawUTF8;
begin
  Check(fClient.Server.URI('root/calculator','POST',
    '{"method":"'+Method+'", "params": [ '+Params+' ]}',resp,head,
    @SUPERVISOR_ACCESS_RIGHTS).Lo=ExpectedResult);
  result := JSONDecode(resp,'RESULT',nil,true);
end;
var I: ICalculator;
begin
  I := TServiceCalculator.Create;
  Test(I);
  I := nil;
  if CheckFailed(fModel<>nil) or CheckFailed(fClient<>nil) or
     CheckFailed(fClient.Server.Services.Count=1) or
     CheckFailed(fClient.Server.Services.Index(0).Get(I)) or
     CheckFailed(Assigned(I)) then exit;
  Test(I);
  I := nil;
  if CheckFailed(fClient.Server.Services['Calculator'].Get(I)) then
    exit;
  Test(I);
  Check(Ask('None','1,2',400)='');
  Check(Ask('Add','1,2',200)='[3]');
  Check(Ask('Multiply','2,3',200)='[6]');
  Check(Ask('Subtract','23,20',200)='[3]');
  Check(Ask('ToText','777,"abc"',200)='["777"]'); // "abc" for var parameter
  Check(Ask('ToTextFunc','777',200)='["777"]');
end;
................................................................................
  fClient := TSQLRestClientDB.Create(fModel,nil,'test.db3',TSQLRestServerDB);
  // register TServiceCalculator as the ICalculator implementation on the server
  Check(fClient.Server.
    ServiceRegister(TServiceCalculator,[TypeInfo(ICalculator)],sicShared));
  // verify ICalculator RTTI-generated details
  Check(fClient.Server.Services<>nil);
  if CheckFailed(fClient.Server.Services.Count=1) then exit;
  S := fClient.Server.Services.Index(0);
  if CheckFailed(S<>nil) then exit;
  Check(S.InterfaceURI='Calculator');
  Check(S.InstanceCreation=sicShared);
  Check(S.InterfaceTypeInfo^.Kind=tkInterface);
  Check(S.InterfaceTypeInfo^.ShortName='ICalculator');
  Check(GUIDToString(S.InterfaceIID)='{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}');
  Check(S.InterfaceMangledURI='7chgmrLOCU6H1EoW9Jbl_g');
  fClient.Server.Services.ExpectMangledURI := true;
  Check(fClient.Server.Services[S.InterfaceMangledURI]=S);
  fClient.Server.Services.ExpectMangledURI := false;
  Check(fClient.Server.Services['CALCULAtor']=S);
  Check(fClient.Server.Services['CALCULAtors']=nil);
  if CheckFailed(length(S.Methods)=5) then exit;
  for i := 0 to 4 do
    with S.Methods[i] do begin
      Check(CallingConvention=ccRegister);
      Check(URI=ExpectedURI[i]);
      Check(length(Args)=ExpectedParCount[i]);
      Check(ArgsUsed=ExpectedArgs[i]);
................................................................................
          Check(Args[2].ValueType=smvString);
      end;
    end;
end;

{$endif DELPHI5OROLDER}


end.