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

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

Overview
Comment:allow compilation with Delphi 5
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 7bd212de4359e70217654bf5f03e68bf55a5126f
User & Date: ab 2012-09-07 13:48:35
Context
2012-09-07
14:11
updated SQLite3 engine to latest version 3.7.14 check-in: c4897fe8b8 user: ab tags: trunk
13:48
allow compilation with Delphi 5 check-in: 7bd212de43 user: ab tags: trunk
13:36
comments modifications check-in: ebc0856b21 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SynSQLite3.pas.

115
116
117
118
119
120
121

122
123
124
125
126
127
128
....
2895
2896
2897
2898
2899
2900
2901

2902

2903
2904
2905
2906
2907
2908
2909
....
3610
3611
3612
3613
3614
3615
3616




3617


3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629




3630


3631
3632
3633
3634
3635
3636
3637
3638


3639


3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
....
3700
3701
3702
3703
3704
3705
3706

3707
3708
3709

3710


3711
3712

3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723

3724

3725
3726
3727
3728
3729
3730
3731
....
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
....
3759
3760
3761
3762
3763
3764
3765

3766

3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779

3780

3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793

3794


3795
3796

3797

3798
3799
3800
3801
3802
3803
3804
....
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
....
3879
3880
3881
3882
3883
3884
3885




3886


3887
3888
3889
3890
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
....
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
    with no data (as expected by TSQLRestClientURI.UpdateFromServer)
  - 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

  Version 1.17
  - updated SQLite3 engine to version 3.7.13

  - added TSQLDataBase.CacheFlush method (needed by SQLite3DB)
  - added TSQLDataBase.Synchronous and TSQLDataBase.WALMode properties
  - added TSQLDataBase.ExecuteNoException() overloaded methods
  - fixed ticket [8dc4d49ea9] in TSQLDataBase.GetFieldNames()about result array
    truncated to 64

    Todo:
................................................................................
end;

// we override default WinRead() and WinWrite() functions below, in order
// to add our proprietary (but efficient) encryption engine
// - should be modified to match other Operating Systems than Windows

type

{$A4} // bcc32 default alignment is 4 bytes

  TSQLFile = record // called winFile (expand sqlite3_file) in sqlite3.c
    pMethods: pointer;     // sqlite3_io_methods_ptr
    pVfs: pointer;         // The VFS used to open this file (new in version 3.7)
    h: THandle;            // Handle for accessing the file
    bulk: cardinal;        // lockType+sharedLockByte are word-aligned
    lastErrno: cardinal;   // The Windows errno from the last I/O error
    // asm code generated from c is [esi+16] for lastErrNo -> OK
................................................................................
function TSQLDataBase.LastInsertRowID: Int64;
begin
  if (self=nil) or (DB=0) then
    result := 0 else
    try
      Lock('');
      result := sqlite3_last_insert_rowid(DB);




      fLog.Log(sllDB,'LastInsertRowID=%',result,self);


    finally
      UnLock;
    end;
end;

function TSQLDataBase.LastChangeCount: integer;
begin
  if (self=nil) or (DB=0) then
    result := 0 else
    try
      Lock('');
      result := sqlite3_changes(DB);




      fLog.Log(sllDB,'LastChangeCount=%',result,self);


    finally
      UnLock;
    end;
end;

procedure TSQLDataBase.GetTableNames(var Names: TRawUTF8DynArray);
begin // SQL statement taken from official SQLite3 FAQ
  SetLength(Names,Execute(SQL_GET_TABLE_NAMES,Names));


  fLog.Log(sllDebug,'TableNames',TypeInfo(TRawUTF8DynArray),Names,self);


end;

procedure TSQLDataBase.GetFieldNames(var Names: TRawUTF8DynArray; const TableName: RawUTF8);
var R: TSQLRequest;
    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
      n := 0;
      repeat
        if R.Step<>SQLITE_ROW then break;
        if n=length(Names) then
          SetLength(Names,n+MAX_SQLFIELDS);
        Names[n] := sqlite3_column_text(R.Request,1); // cid,name,type,notnull,dflt_value,pk
        inc(n);
................................................................................
begin
  if self=nil then
    exit; // avoid GPF in case of call from a static-only server
  EnterCriticalSection(fLock); // cache access is also protected by fLock
  if isSelect(pointer(aSQL)) then begin
    result := fCache.Find(aSQL,aResultCount); // try to get JSON result from cache
    if result<>'' then begin

      fLog.Log(sllSQL,aSQL,self);
      fLog.Log(sllCache,'from cache',self);
      fLog.Log(sllResult,result,self);

      LeaveCriticalSection(fLock); // found in cache -> leave critical section


    end else
      fLog.Log(sllCache,'not in cache',self);

  end else begin
    // UPDATE, INSERT or any non SELECT statement
    CacheFlush;
    result := '';
  end;
end;

procedure TSQLDataBase.UnLockJSON(const aJSONResult: RawUTF8; aResultCount: PtrInt);
begin
  if self=nil then
    exit; // avoid GPF in case of call from a static-only server

  fLog.Log(sllResult,aJSONResult,self);

  fCache.Add(aJSONResult,aResultCount); // if a reset was made just before, Add() does nothing
  UnLock; // leave fLock
end;

function TSQLDataBase.Backup(const BackupFileName: TFileName): boolean;
begin
  fLog.Enter(self);
................................................................................
    try
      fLog.Log(sllTrace,'close',self);
      DBClose;
      fLog.Log(sllTrace,'copy file',self);
      result := CopyFile(pointer(fFileName),pointer(BackupFileName),false);
    finally
      fLog.Log(sllTrace,'reopen',self);
      DBOpen; 
    end;
  finally
    UnLock;
  end;
end;

procedure TSQLDataBase.DBClose;
................................................................................
  fLog.Enter;
  if Cyphers<>nil then
    i := Cypher.Find(PSQLDBStruct(fDB)^.DB0^.Btree^.pBt^.pPager^.fd^.h) else
    i := -1;
  sqlite3_close(fDB);
  if i>=0 then begin
    Cypher.Delete(i); // do it after file closing

    fLog.Log(sllDB,'end of encryption');

  end;
  fDB := 0;
end;

function TSQLDataBase.DBOpen: integer;
var utf8: RawUTF8;
    i: integer;
    Cyph: TSQLCypher;
begin
  fLog.Enter;
  utf8 := StringToUTF8(fFileName);
  result := sqlite3_open(pointer(utf8),fDB);
  if result<>SQLITE_OK then begin

    fLog.Log(sllError,'open("%") failed',utf8,self);

    sqlite3_close(fDB); // should always be closed, even on failure
    fDB := 0;
    exit;
  end;
  Cyph.Handle := PSQLDBStruct(fDB)^.DB0^.Btree^.pBt^.pPager^.fd^.h;
  fLog.Log(sllDB,'open("%") with handle=%',[utf8,Cyph.Handle],self);
  if fCypherBlock<>'' then begin
    if Cyphers=nil then begin
      Cypher.Init(TypeInfo(TSQLCypherDynArray),Cyphers,@CypherCount);
      Cypher.Compare := SortDynArrayInteger;
    end;
    i := Cypher.Find(Cyph.Handle);
    if i>=0 then

      fLog.Log(sllError,'Handle reused for %',utf8) else begin


      Cyph.CypherBuf := fCypherBlock;
      Cypher.Add(Cyph);

      fLog.Log(sllDB,'encryption enabled');

    end;
  end;
  // the SQLite3 standard NOCASE collation is used for AnsiString and is very fast
  // our custom fast UTF-8 case insensitive compare, using NormToUpper[] for all 8 bits values
  sqlite3_create_collation(DB,'SYSTEMNOCASE',SQLITE_UTF8,nil,Utf8SQLCompNoCase);
  // our custom fast ISO-8601 date time encoded
  sqlite3_create_collation(DB,'ISO8601',SQLITE_UTF8,nil,Utf8SQLDateTime);
................................................................................
begin
  ExecuteNoException('PRAGMA user_version',tmp);
  result := tmp;
end;

procedure TSQLDataBase.SetUserVersion(const Value: cardinal);
begin
  ExecuteNoException(FormatUTF8('PRAGMA user_version=%;',[Value]));
end;

procedure TSQLDataBase.SetSynchronous(const Value: TSQLSynchronousMode);
begin
  ExecuteNoException(FormatUTF8('PRAGMA synchronous=%;',[ord(Value)]));
end;

function TSQLDataBase.GetSynchronous: TSQLSynchronousMode;
var tmp: Int64;
begin
  ExecuteNoException('PRAGMA synchronous ',tmp);
  result := TSQLSynchronousMode(tmp);
................................................................................
  result := IdemPropNameU(tmp,'wal');
end;

procedure TSQLDataBase.SetBusyTimeout(const ms: Integer);
begin
  if self=nil then
    exit;




  fLog.Log(sllDB,'SetBusyTimeout=%',ms,self);


  sqlite3_busy_timeout(DB,ms);
  fBusyTimeout := ms;
end;

procedure TSQLDataBase.CacheFlush;
begin
  if self=nil then
    exit;
  if InternalState<>nil then
    inc(InternalState^);
  if fCache.Reset then

    fLog.Log(sllCache,'cache flushed',self);

end;

procedure TSQLDataBase.RegisterSQLFunction(aFunction: TSQLDataBaseSQLFunction);
var i: integer;
begin
  if (self=nil) or (aFunction=nil) then
    exit;
................................................................................
  for i := 0 to fSQLFunctions.Count-1 do
    with TSQLDataBaseSQLFunction(fSQLFunctions.List[i]) do
    if (FunctionParametersCount=aFunction.FunctionParametersCount) and
       IdemPropNameU(FunctionName,aFunction.FunctionName) then begin
      aFunction.Free;
      exit; // already registered with the same name and parameters count
    end;

  fLog.Log(sllDB,'RegisterSQLFunction "%"',aFunction.FunctionName,self);

  fSQLFunctions.Add(aFunction);
  if DB<>0 then
    // DB already opened -> register this custom function
    aFunction.CreateFunction(DB);
end;

procedure TSQLDataBase.RegisterSQLFunction(aDynArrayTypeInfo: pointer;
................................................................................
function TSQLDataBaseSQLFunction.CreateFunction(DB: TSQLite3DB): Integer;
begin
  if self<>nil then begin
    result := sqlite3_create_function_v2(DB,pointer(fSQLName),
      FunctionParametersCount,SQLITE_ANY,self,fInternalFunction,nil,nil,nil);
    {$ifdef WITHLOG}
    if result<>SQLITE_OK then 
      SynSQLite3Log.Add.Log(sllError,'register SQL function %() failed',FunctionName,self);
    {$endif}
  end else
    result := SQLITE_ERROR;
end;


{ TSQLDataBaseSQLFunctionDynArray }






>







 







>

>







 







>
>
>
>

>
>












>
>
>
>

>
>








>
>

>
>











|







 







>



>

>
>
|

>











>

>







 







|







 







>

>













>
|
>





|






|
>
|
>
>


>

>







 







|




|







 







>
>
>
>

>
>











>

>







 







>
|
>







 







|







115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
....
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
....
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
....
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
....
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
....
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
....
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
....
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
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
....
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
    with no data (as expected by TSQLRestClientURI.UpdateFromServer)
  - 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

  Version 1.17
  - updated SQLite3 engine to version 3.7.13
  - allow compilation with Delphi 5
  - added TSQLDataBase.CacheFlush method (needed by SQLite3DB)
  - added TSQLDataBase.Synchronous and TSQLDataBase.WALMode properties
  - added TSQLDataBase.ExecuteNoException() overloaded methods
  - fixed ticket [8dc4d49ea9] in TSQLDataBase.GetFieldNames()about result array
    truncated to 64

    Todo:
................................................................................
end;

// we override default WinRead() and WinWrite() functions below, in order
// to add our proprietary (but efficient) encryption engine
// - should be modified to match other Operating Systems than Windows

type
{$ifndef DELPHI5OROLDER} // Delphi 5 is already aligning records by 4 bytes
{$A4} // bcc32 default alignment is 4 bytes
{$endif}
  TSQLFile = record // called winFile (expand sqlite3_file) in sqlite3.c
    pMethods: pointer;     // sqlite3_io_methods_ptr
    pVfs: pointer;         // The VFS used to open this file (new in version 3.7)
    h: THandle;            // Handle for accessing the file
    bulk: cardinal;        // lockType+sharedLockByte are word-aligned
    lastErrno: cardinal;   // The Windows errno from the last I/O error
    // asm code generated from c is [esi+16] for lastErrNo -> OK
................................................................................
function TSQLDataBase.LastInsertRowID: Int64;
begin
  if (self=nil) or (DB=0) then
    result := 0 else
    try
      Lock('');
      result := sqlite3_last_insert_rowid(DB);
      {$ifdef WITHLOG}
      {$ifdef DELPHI5OROLDER}
      fLog.Log(sllDB,'LastInsertRowID='+Int64ToUTF8(result),self);
      {$else}
      fLog.Log(sllDB,'LastInsertRowID=%',result,self);
      {$endif}
      {$endif}
    finally
      UnLock;
    end;
end;

function TSQLDataBase.LastChangeCount: integer;
begin
  if (self=nil) or (DB=0) then
    result := 0 else
    try
      Lock('');
      result := sqlite3_changes(DB);
      {$ifdef WITHLOG}
      {$ifdef DELPHI5OROLDER}
      fLog.Log(sllDB,'LastChangeCount='+Int64ToUTF8(result),self);
      {$else}
      fLog.Log(sllDB,'LastChangeCount=%',result,self);
      {$endif}
      {$endif}
    finally
      UnLock;
    end;
end;

procedure TSQLDataBase.GetTableNames(var Names: TRawUTF8DynArray);
begin // SQL statement taken from official SQLite3 FAQ
  SetLength(Names,Execute(SQL_GET_TABLE_NAMES,Names));
  {$ifdef WITHLOG}
  {$ifndef DELPHI5OROLDER}
  fLog.Log(sllDebug,'TableNames',TypeInfo(TRawUTF8DynArray),Names,self);
  {$endif}
  {$endif}
end;

procedure TSQLDataBase.GetFieldNames(var Names: TRawUTF8DynArray; const TableName: RawUTF8);
var R: TSQLRequest;
    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,'PRAGMA table_info('+TableName+');'); // ESQLite3Exception
      n := 0;
      repeat
        if R.Step<>SQLITE_ROW then break;
        if n=length(Names) then
          SetLength(Names,n+MAX_SQLFIELDS);
        Names[n] := sqlite3_column_text(R.Request,1); // cid,name,type,notnull,dflt_value,pk
        inc(n);
................................................................................
begin
  if self=nil then
    exit; // avoid GPF in case of call from a static-only server
  EnterCriticalSection(fLock); // cache access is also protected by fLock
  if isSelect(pointer(aSQL)) then begin
    result := fCache.Find(aSQL,aResultCount); // try to get JSON result from cache
    if result<>'' then begin
      {$ifdef WITHLOG}
      fLog.Log(sllSQL,aSQL,self);
      fLog.Log(sllCache,'from cache',self);
      fLog.Log(sllResult,result,self);
      {$endif}
      LeaveCriticalSection(fLock); // found in cache -> leave critical section
    end
    {$ifdef WITHLOG}
    else
      fLog.Log(sllCache,'not in cache',self);
    {$endif}
  end else begin
    // UPDATE, INSERT or any non SELECT statement
    CacheFlush;
    result := '';
  end;
end;

procedure TSQLDataBase.UnLockJSON(const aJSONResult: RawUTF8; aResultCount: PtrInt);
begin
  if self=nil then
    exit; // avoid GPF in case of call from a static-only server
  {$ifdef WITHLOG}
  fLog.Log(sllResult,aJSONResult,self);
  {$endif}
  fCache.Add(aJSONResult,aResultCount); // if a reset was made just before, Add() does nothing
  UnLock; // leave fLock
end;

function TSQLDataBase.Backup(const BackupFileName: TFileName): boolean;
begin
  fLog.Enter(self);
................................................................................
    try
      fLog.Log(sllTrace,'close',self);
      DBClose;
      fLog.Log(sllTrace,'copy file',self);
      result := CopyFile(pointer(fFileName),pointer(BackupFileName),false);
    finally
      fLog.Log(sllTrace,'reopen',self);
      DBOpen;
    end;
  finally
    UnLock;
  end;
end;

procedure TSQLDataBase.DBClose;
................................................................................
  fLog.Enter;
  if Cyphers<>nil then
    i := Cypher.Find(PSQLDBStruct(fDB)^.DB0^.Btree^.pBt^.pPager^.fd^.h) else
    i := -1;
  sqlite3_close(fDB);
  if i>=0 then begin
    Cypher.Delete(i); // do it after file closing
    {$ifdef WITHLOG}
    fLog.Log(sllDB,'end of encryption');
    {$endif}
  end;
  fDB := 0;
end;

function TSQLDataBase.DBOpen: integer;
var utf8: RawUTF8;
    i: integer;
    Cyph: TSQLCypher;
begin
  fLog.Enter;
  utf8 := StringToUTF8(fFileName);
  result := sqlite3_open(pointer(utf8),fDB);
  if result<>SQLITE_OK then begin
    {$ifdef WITHLOG}
    fLog.Log(sllError,'open("'+utf8+'") failed',self);
    {$endif}
    sqlite3_close(fDB); // should always be closed, even on failure
    fDB := 0;
    exit;
  end;
  Cyph.Handle := PSQLDBStruct(fDB)^.DB0^.Btree^.pBt^.pPager^.fd^.h;
  //fLog.Log(sllDB,'open("%") with handle=%',[utf8,Cyph.Handle],self);
  if fCypherBlock<>'' then begin
    if Cyphers=nil then begin
      Cypher.Init(TypeInfo(TSQLCypherDynArray),Cyphers,@CypherCount);
      Cypher.Compare := SortDynArrayInteger;
    end;
    i := Cypher.Find(Cyph.Handle);
    if i>=0 then begin
      {$ifdef WITHLOG}
      fLog.Log(sllError,'Handle reused for '+utf8);
      {$endif}
    end else begin
      Cyph.CypherBuf := fCypherBlock;
      Cypher.Add(Cyph);
      {$ifdef WITHLOG}
      fLog.Log(sllDB,'encryption enabled');
      {$endif}
    end;
  end;
  // the SQLite3 standard NOCASE collation is used for AnsiString and is very fast
  // our custom fast UTF-8 case insensitive compare, using NormToUpper[] for all 8 bits values
  sqlite3_create_collation(DB,'SYSTEMNOCASE',SQLITE_UTF8,nil,Utf8SQLCompNoCase);
  // our custom fast ISO-8601 date time encoded
  sqlite3_create_collation(DB,'ISO8601',SQLITE_UTF8,nil,Utf8SQLDateTime);
................................................................................
begin
  ExecuteNoException('PRAGMA user_version',tmp);
  result := tmp;
end;

procedure TSQLDataBase.SetUserVersion(const Value: cardinal);
begin
  ExecuteNoException('PRAGMA user_version='+Int32ToUTF8(Value));
end;

procedure TSQLDataBase.SetSynchronous(const Value: TSQLSynchronousMode);
begin
  ExecuteNoException('PRAGMA synchronous='+Int32ToUTF8(ord(Value)));
end;

function TSQLDataBase.GetSynchronous: TSQLSynchronousMode;
var tmp: Int64;
begin
  ExecuteNoException('PRAGMA synchronous ',tmp);
  result := TSQLSynchronousMode(tmp);
................................................................................
  result := IdemPropNameU(tmp,'wal');
end;

procedure TSQLDataBase.SetBusyTimeout(const ms: Integer);
begin
  if self=nil then
    exit;
  {$ifdef WITHLOG}
  {$ifdef DELPHI5OROLDER}
  fLog.Log(sllDB,'SetBusyTimeout='+Int32ToUTF8(ms),self);
  {$else}
  fLog.Log(sllDB,'SetBusyTimeout=%',ms,self);
  {$endif}
  {$endif}
  sqlite3_busy_timeout(DB,ms);
  fBusyTimeout := ms;
end;

procedure TSQLDataBase.CacheFlush;
begin
  if self=nil then
    exit;
  if InternalState<>nil then
    inc(InternalState^);
  if fCache.Reset then
  {$ifdef WITHLOG}
    fLog.Log(sllCache,'cache flushed',self);
  {$endif}
end;

procedure TSQLDataBase.RegisterSQLFunction(aFunction: TSQLDataBaseSQLFunction);
var i: integer;
begin
  if (self=nil) or (aFunction=nil) then
    exit;
................................................................................
  for i := 0 to fSQLFunctions.Count-1 do
    with TSQLDataBaseSQLFunction(fSQLFunctions.List[i]) do
    if (FunctionParametersCount=aFunction.FunctionParametersCount) and
       IdemPropNameU(FunctionName,aFunction.FunctionName) then begin
      aFunction.Free;
      exit; // already registered with the same name and parameters count
    end;
  {$ifdef WITHLOG}
  fLog.Log(sllDB,'RegisterSQLFunction '+aFunction.FunctionName,self);
  {$endif}
  fSQLFunctions.Add(aFunction);
  if DB<>0 then
    // DB already opened -> register this custom function
    aFunction.CreateFunction(DB);
end;

procedure TSQLDataBase.RegisterSQLFunction(aDynArrayTypeInfo: pointer;
................................................................................
function TSQLDataBaseSQLFunction.CreateFunction(DB: TSQLite3DB): Integer;
begin
  if self<>nil then begin
    result := sqlite3_create_function_v2(DB,pointer(fSQLName),
      FunctionParametersCount,SQLITE_ANY,self,fInternalFunction,nil,nil,nil);
    {$ifdef WITHLOG}
    if result<>SQLITE_OK then 
      SynSQLite3Log.Add.Log(sllError,'register SQL function failed: '+FunctionName,self);
    {$endif}
  end else
    result := SQLITE_ERROR;
end;


{ TSQLDataBaseSQLFunctionDynArray }

Changes to SynSelfTests.pas.

714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
....
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
    {$ifndef PUREPASCAL}
    comp1,comp2,dec1,dec2: array of byte;
    complen1,complen2: integer;
    {$endif}
begin
  for i := 0 to 1000 do begin
    t := RandomString(i*8);
    s := t;
    UniqueString(AnsiString(s));
    Check(CompressSynLZ(s,true)='synlz');
    Check(CompressSynLZ(s,false)='synlz');
    Check(s=t);
    {$ifndef PUREPASCAL}
    SetLength(comp1,SynLZcompressdestlen(length(s)));
    complen1 := SynLZcompress1asm(Pointer(s),length(s),pointer(comp1));
    Check(complen1<length(comp1));
................................................................................
procedure TTestServiceOrientedArchitecture.ServiceInitialization;
  function Ask(Method, Params: RawUTF8; ExpectedResult: cardinal): RawUTF8;
  var resp,data: RawUTF8;
  begin
    Params := ' [ '+Params+' ]';
    case fClient.Server.ServicesRouting of
    rmREST: begin
      data := Params; UniqueString(AnsiString(data));
      Check(fClient.URI('root/calculator.'+Method,'POST',@resp,nil,@Params).Lo=ExpectedResult);
      if ExpectedResult=200 then begin
        Check(fClient.URI('root/CALCulator.'+Method+'?'+UrlEncode(data),'POST',@data).Lo=ExpectedResult);
        Check(data=resp,'optional URI-encoded-inlined parameters use');
      end;
    end;
    rmJSON_RPC: begin






|
<







 







|







714
715
716
717
718
719
720
721

722
723
724
725
726
727
728
....
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
    {$ifndef PUREPASCAL}
    comp1,comp2,dec1,dec2: array of byte;
    complen1,complen2: integer;
    {$endif}
begin
  for i := 0 to 1000 do begin
    t := RandomString(i*8);
    SetString(s,PAnsiChar(pointer(t)),length(t)); // =UniqueString

    Check(CompressSynLZ(s,true)='synlz');
    Check(CompressSynLZ(s,false)='synlz');
    Check(s=t);
    {$ifndef PUREPASCAL}
    SetLength(comp1,SynLZcompressdestlen(length(s)));
    complen1 := SynLZcompress1asm(Pointer(s),length(s),pointer(comp1));
    Check(complen1<length(comp1));
................................................................................
procedure TTestServiceOrientedArchitecture.ServiceInitialization;
  function Ask(Method, Params: RawUTF8; ExpectedResult: cardinal): RawUTF8;
  var resp,data: RawUTF8;
  begin
    Params := ' [ '+Params+' ]';
    case fClient.Server.ServicesRouting of
    rmREST: begin
      SetString(data,PAnsiChar(pointer(Params)),length(Params)); // =UniqueString
      Check(fClient.URI('root/calculator.'+Method,'POST',@resp,nil,@Params).Lo=ExpectedResult);
      if ExpectedResult=200 then begin
        Check(fClient.URI('root/CALCulator.'+Method+'?'+UrlEncode(data),'POST',@data).Lo=ExpectedResult);
        Check(data=resp,'optional URI-encoded-inlined parameters use');
      end;
    end;
    rmJSON_RPC: begin