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

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

Overview
Comment:
  • added RaiseExceptionOnError: boolean=false optional parameter to TSQLDBConnection.NewStatementPrepared() method, and TSQLDBConnection.LastErrorMessage and LastErrorException properties, to retrieve the error when NewStatementPrepared() returned nil
  • TSQLDBConnectionProperties.Execute/ExecuteNoResult methods will now use prepared statements cache (if any)
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 5f4d9a1c8b9409a7111bfb3b34afa35f2f032641
User & Date: abouchez 2013-01-30 13:32:33
Context
2013-01-30
15:12
fixed CORS implementation to work with all methods check-in: aa1fa79b05 user: abouchez tags: trunk
13:32
  • added RaiseExceptionOnError: boolean=false optional parameter to TSQLDBConnection.NewStatementPrepared() method, and TSQLDBConnection.LastErrorMessage and LastErrorException properties, to retrieve the error when NewStatementPrepared() returned nil
  • TSQLDBConnectionProperties.Execute/ExecuteNoResult methods will now use prepared statements cache (if any)
check-in: 5f4d9a1c8b user: abouchez tags: trunk
12:16
  • added TSQLRestClientURI.LastErrorCode/LastErrorMessage/LastErrorException properties, to retrieve additional information about remote URL() execution
  • exposed StatusCodeToErrorMsg() function
check-in: f2733cdb53 user: abouchez tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SynDB.pas.

145
146
147
148
149
150
151




152
153
154
155
156
157
158
...
660
661
662
663
664
665
666


667
668
669
670
671
672
673
...
763
764
765
766
767
768
769
770
771



772
773
774
775
776
777
778
779



780
781
782
783
784
785

786
787
788
789
790
791
792
793
794
...
977
978
979
980
981
982
983

984
985
986
987
988
989
990
991
....
1007
1008
1009
1010
1011
1012
1013
1014
1015



1016
1017
1018
1019
1020
1021
1022
1023
1024
....
1027
1028
1029
1030
1031
1032
1033

1034
1035
1036
1037
1038
1039
1040
....
1046
1047
1048
1049
1050
1051
1052
1053
1054


1055
1056
1057
1058
1059
1060
1061
....
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
....
1453
1454
1455
1456
1457
1458
1459




1460
1461
1462
1463
1464
1465
1466
....
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
....
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
....
2778
2779
2780
2781
2782
2783
2784
2785
2786




2787
2788
2789
2790
2791
2792
2793
....
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891

2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912

2913
2914
2915
2916
2917
2918
2919
....
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
....
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
  Version 1.18
  - SQL statements are now cached by default - in some cases, it will increase
    individual reading or writing speed by a factor of 4x
  - introducing new ISQLDBStatement interface, used by SQL statement cache
  - avoid syntax error for some engines which do not accept an ending ';' in
    SQL statements




  - added TSQLDBConnectionProperties.ForcedSchemaName optional property
  - added TSQLDBConnectionProperties.SQLGetIndex() and GetIndexes() methods
    to retrieve advanced information about database indexes (e.g. for indexes
    created after multiple columns)
  - added TSQLDBConnectionProperties.SQLTableName() method
  - added TSQLDBConnectionPropertiesThreadSafe.ForceOnlyOneSharedConnection
    property to by-pass internal thread-pool (e.g. for embedded engines)
................................................................................
    // - by now, only SynDBOracle unit implements an array bind
    procedure BindArray(Param: Integer; const Values: array of RawUTF8); overload;
    {/ Execute a prepared SQL statement
     - parameters marked as ? should have been already bound with Bind*() functions
     - should raise an Exception on any error
     - after execution, you can access any returned data via ISQLDBRows methods }
    procedure ExecutePrepared;


  end;

  {$M+} { published properties to be logged as JSON }

  TSQLDBConnection = class;

  /// abstract class used to set Database-related properties
................................................................................
    // connection pool, and allow automatic reconnection
    procedure ClearConnectionPool; virtual;
    /// create a new thread-safe statement
    // - this method will call ThreadSafeConnection.NewStatement 
    function NewThreadSafeStatement: TSQLDBStatement;
    /// create a new thread-safe statement from an internal cache (if any)
    // - will call ThreadSafeConnection.NewStatementPrepared
    // - this method should return nil in case of error, or a prepared statement
    // instance in case of success



    function NewThreadSafeStatementPrepared(const aSQL: RawUTF8;
       ExpectResults: Boolean): ISQLDBStatement; overload;
    /// create a new thread-safe statement from an internal cache (if any)
    // - this method will call the NewThreadSafeStatementPrepared method
    // - here Args[] array does not refer to bound parameters, but to values
    // to be changed within SQLFormat in place of '%' characters (this method
    // will call FormatUTF8() internaly); parameters will be bound directly
    // on the returned TSQLDBStatement instance



    function NewThreadSafeStatementPrepared(SQLFormat: PUTF8Char;
      const Args: array of const; ExpectResults: Boolean): ISQLDBStatement; overload;
    /// execute a SQL query, returning a statement interface instance to retrieve
    // the result rows
    // - will call NewThreadSafeStatement method to retrieve a thread-safe
    // statement instance, then run the corresponding Execute() method

    // - returns an ISQLDBRows to access any resulting rows (if
    // ExpectResults is TRUE), and provide basic garbage collection, as such:
    // ! procedure WriteFamily(const aName: RawUTF8);
    // ! var I: ISQLDBRows;
    // ! begin
    // !   I := MyConnProps.Execute('select * from table where name=?',[aName]);
    // !   while I.Step do
    // !     writeln(I['FirstName'],' ',DateToStr(I['BirthDate']));
    // ! end;
................................................................................
  // - more than one TSQLDBConnection instance can be run for the same
  // TSQLDBConnectionProperties
  TSQLDBConnection = class
  private
    function GetInTransaction: boolean;
  protected
    fProperties: TSQLDBConnectionProperties;

    fErrorMessage: string;
    fInfoMessage: string;
    fTransactionCount: integer;
    fServerTimeStampOffset: TDateTime;
    fCache: TRawUTF8ListHashed;
    function GetServerTimeStamp: TTimeLog; virtual;
    /// raise an exception
    procedure CheckConnection;
................................................................................
    // - the caller should free the instance after use
    function NewStatement: TSQLDBStatement; virtual; abstract;
    /// initialize a new SQL query statement for the given connection
    // - this default implementation will call the NewStatement method, and
    // implement handle statement caching is UseCache=true - in this case,
    // the TSQLDBStatement.Reset method shall have been overriden to allow
    // binding and execution of the very same prepared statement
    // - this method should return nil in case of error, or a prepared statement
    // instance in case of success



    function NewStatementPrepared(const aSQL: RawUTF8;
      ExpectResults: Boolean): ISQLDBStatement; virtual;
    /// begin a Transaction for this connection
    // - this default implementation will check and set TransactionCount
    procedure StartTransaction; virtual;
    /// commit changes of a Transaction for this connection
    // - StartTransaction method must have been called before
    // - this default implementation will check and set TransactionCount
    procedure Commit; virtual;
................................................................................
    // - this default implementation will check and set TransactionCount
    procedure Rollback; virtual;

    /// direct export of a DB statement rows into a new table of this database
    // - the corresponding table will be created within the current connection,
    // if it does not exist
    // - INSERTs will be nested within a transaction if WithinTransaction is TRUE

    function NewTableFromRows(const TableName: RawUTF8;
      Rows: TSQLDBStatement; WithinTransaction: boolean): integer;

    /// number of nested StartTransaction calls
    // - equals 0 if no transaction is active
    property TransactionCount: integer read fTransactionCount;
    /// TRUE if StartTransaction has been called
................................................................................
    // - default implementation will return the executable time, i.e. Iso8601Now
    property ServerTimeStamp: TTimeLog read GetServerTimeStamp;
  published { to be logged as JSON }
    /// the associated database properties
    property Properties: TSQLDBConnectionProperties read fProperties;
    /// returns TRUE if the connection was set
    property Connected: boolean read IsConnected;
    /// some information message, as retrieved during execution
    property LastErrorMessage: string read fErrorMessage;


    /// some information message, as retrieved during execution
    property InfoMessage: string read fInfoMessage;
  end;

  /// generic abstract class to implement a prepared SQL query
  // - inherited classes should implement the DB-specific connection in its
  // overriden methods, especially Bind*(), Prepare(), ExecutePrepared, Step()
................................................................................
    fSQL: RawUTF8;
    fExpectResults: boolean;
    fParamCount: integer;
    fColumnCount: integer;
    fTotalRowsRetrieved: Integer;
    fCurrentRow: Integer;
    function GetSQLWithInlinedParams: RawUTF8;
    /// default implementation returns 0
    function GetUpdateCount: integer; virtual;
    /// raise an exception if Col is out of range according to fColumnCount
    procedure CheckCol(Col: integer); {$ifdef HASINLINE}inline;{$endif}
    {/ will set a Int64/Double/Currency/TDateTime/RawUTF8/TBlobData Dest variable
      from a given column value
     - internal conversion will use a temporary Variant and ColumnToVariant method
     - expects Dest to be of the exact type (e.g. Int64, not Integer) }
    function ColumnToTypedValue(Col: integer; DestType: TSQLDBFieldType; var Dest): TSQLDBFieldType;
................................................................................
    // - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"'
    // format and contains true BLOB data
    // - if ReturnedRowCount points to an integer variable, it will be filled with
    // the number of row data returned (excluding field names)
    // - similar to corresponding TSQLRequest.Execute method in SynSQLite3 unit
    function FetchAllAsJSON(Expanded: boolean; ReturnedRowCount: PPtrInt=nil): RawUTF8;





    /// the associated database connection
    property Connection: TSQLDBConnection read fConnection;
    /// the prepared SQL statement, as supplied to Prepare() method
    property SQL: RawUTF8 read fSQL;
    /// the prepared SQL statement, with all '?' changed into the supplied
    // parameter values
    property SQLWithInlinedParams: RawUTF8 read GetSQLWithInlinedParams;
................................................................................
    /// the current row after Execute call, corresponding to Column*() methods
    // - contains 0 in case of no (more) available data, or a number >=1
    property CurrentRow: Integer read fCurrentRow;
    /// the total number of data rows retrieved by this instance
    // - is not reset when there is no more row of available data (Step returns
    // false), or when Step() is called with SeekFirst=true
    property TotalRowsRetrieved: Integer read fTotalRowsRetrieved;
    /// gets a number of updates made by latest executed statement
    property UpdateCount: Integer read GetUpdateCount;
  end;

  /// abstract connection created from TSQLDBConnectionProperties
  // - this overriden class will defined an hidden thread ID, to ensure
  // that one connection will be create per thread
  // - e.g. OleDB, ODBC and Oracle connections will inherit from this class
  TSQLDBConnectionThreadSafe = class(TSQLDBConnection)
................................................................................
    if fServerTimeStampOffset=0 then
      fServerTimeStampOffset := 0.0001; // request server only once
  end;
  PIso8601(@result)^.From(Current+fServerTimeStampOffset);
end;

function TSQLDBConnection.NewStatementPrepared(const aSQL: RawUTF8;
  ExpectResults: Boolean): ISQLDBStatement;
var Stmt: TSQLDBStatement;
    ToCache: boolean;
    ndx: integer;
begin
  ToCache := fProperties.IsCachable(Pointer(aSQL));
  if ToCache and (fCache<>nil) then begin
    ndx := fCache.IndexOf(aSQL);
................................................................................
      if fCache=nil then
        fCache := TRawUTF8ListHashed.Create(true);
      fCache.AddObject(aSQL,Stmt);
      Stmt._AddRef;
    end;
    result := Stmt;
  except
    on Exception do begin
      Stmt.Free;




      result := nil;
    end;
  end;
end;  

procedure TSQLDBConnection.Rollback;
begin
................................................................................
   fMainConnection.Free;
  inherited;
end;

function TSQLDBConnectionProperties.Execute(const aSQL: RawUTF8;
  const Params: array of const
  {$ifndef LVCL}{$ifndef DELPHI5OROLDER}; RowsVariant: PVariant=nil{$endif}{$endif}): ISQLDBRows;
var Query: TSQLDBStatement;
begin
  Query := NewThreadSafeStatement;
  try
    Query.Execute(aSQL,true,Params);

    {$ifndef LVCL}
    {$ifndef DELPHI5OROLDER}
    if RowsVariant<>nil then
      RowsVariant^ := Query.RowData;
    {$endif}
    {$endif}
    result := Query;
  except
    on Exception do begin
      Query.Free;
      raise;
    end;
  end;
end;

function TSQLDBConnectionProperties.ExecuteNoResult(const aSQL: RawUTF8;
  const Params: array of const): integer;
begin
  with NewThreadSafeStatement do
  try
    Execute(aSQL,false,Params);

    result := UpdateCount;
  finally
    Free;
  end;
end;

function TSQLDBConnectionProperties.GetMainConnection: TSQLDBConnection;
................................................................................

function TSQLDBConnectionProperties.NewThreadSafeStatement: TSQLDBStatement;
begin
  result := ThreadSafeConnection.NewStatement;
end;

function TSQLDBConnectionProperties.NewThreadSafeStatementPrepared(
  const aSQL: RawUTF8; ExpectResults: Boolean): ISQLDBStatement;
begin
  result := ThreadSafeConnection.NewStatementPrepared(aSQL,ExpectResults);
end;

function TSQLDBConnectionProperties.NewThreadSafeStatementPrepared(
  SQLFormat: PUTF8Char; const Args: array of const; ExpectResults: Boolean): ISQLDBStatement;
begin
  result := NewThreadSafeStatementPrepared(FormatUTF8(SQLFormat,Args),ExpectResults);
end;
................................................................................

procedure TSQLDBStatement.Execute(SQLFormat: PUTF8Char;
  ExpectResults: Boolean; const Args, Params: array of const);
begin
  Execute(FormatUTF8(SQLFormat,Args),ExpectResults,Params);
end;

function TSQLDBStatement.GetUpdateCount: integer;
begin
  result := 0;
end;

function TSQLDBStatement.ColumnString(Col: integer): string;
begin
  Result := UTF8ToString(ColumnUTF8(Col));






>
>
>
>







 







>
>







 







|
|
>
>
>

|

|




>
>
>






>
|
|







 







>
|







 







|
|
>
>
>

|







 







>







 







|
|
>
>







 







<
<







 







>
>
>
>







 







<
<







 







|







 







|

>
>
>
>







 







|

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





|

|
>







 







|

|







 







|







145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
...
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
...
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
...
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
....
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
....
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
....
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
....
1086
1087
1088
1089
1090
1091
1092


1093
1094
1095
1096
1097
1098
1099
....
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
....
1489
1490
1491
1492
1493
1494
1495


1496
1497
1498
1499
1500
1501
1502
....
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
....
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
....
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922







2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
....
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
....
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
  Version 1.18
  - SQL statements are now cached by default - in some cases, it will increase
    individual reading or writing speed by a factor of 4x
  - introducing new ISQLDBStatement interface, used by SQL statement cache
  - avoid syntax error for some engines which do not accept an ending ';' in
    SQL statements
  - added RaiseExceptionOnError: boolean=false optional parameter to
    TSQLDBConnection.NewStatementPrepared() method
  - added TSQLDBConnection.LastErrorMessage and LastErrorException properties,
    to retrieve the error when NewStatementPrepared() returned nil
  - added TSQLDBConnectionProperties.ForcedSchemaName optional property
  - added TSQLDBConnectionProperties.SQLGetIndex() and GetIndexes() methods
    to retrieve advanced information about database indexes (e.g. for indexes
    created after multiple columns)
  - added TSQLDBConnectionProperties.SQLTableName() method
  - added TSQLDBConnectionPropertiesThreadSafe.ForceOnlyOneSharedConnection
    property to by-pass internal thread-pool (e.g. for embedded engines)
................................................................................
    // - by now, only SynDBOracle unit implements an array bind
    procedure BindArray(Param: Integer; const Values: array of RawUTF8); overload;
    {/ Execute a prepared SQL statement
     - parameters marked as ? should have been already bound with Bind*() functions
     - should raise an Exception on any error
     - after execution, you can access any returned data via ISQLDBRows methods }
    procedure ExecutePrepared;
    /// gets a number of updates made by latest executed statement
    function UpdateCount: Integer;
  end;

  {$M+} { published properties to be logged as JSON }

  TSQLDBConnection = class;

  /// abstract class used to set Database-related properties
................................................................................
    // connection pool, and allow automatic reconnection
    procedure ClearConnectionPool; virtual;
    /// create a new thread-safe statement
    // - this method will call ThreadSafeConnection.NewStatement 
    function NewThreadSafeStatement: TSQLDBStatement;
    /// create a new thread-safe statement from an internal cache (if any)
    // - will call ThreadSafeConnection.NewStatementPrepared
    // - this method should return a prepared statement instance on success
    // - on error, returns nil and you can check Connnection.LastErrorMessage /
    // Connection.LastErrorException to retrieve corresponding error information
    // (if RaiseExceptionOnError is left to default FALSE value, otherwise, it will
    // raise an exception)
    function NewThreadSafeStatementPrepared(const aSQL: RawUTF8;
       ExpectResults: Boolean; RaiseExceptionOnError: Boolean=false): ISQLDBStatement; overload;
    /// create a new thread-safe statement from an internal cache (if any)
    // - this method will call the overloaded NewThreadSafeStatementPrepared method
    // - here Args[] array does not refer to bound parameters, but to values
    // to be changed within SQLFormat in place of '%' characters (this method
    // will call FormatUTF8() internaly); parameters will be bound directly
    // on the returned TSQLDBStatement instance
    // - this method should return a prepared statement instance on success
    // - on error, returns nil and you can check Connnection.LastErrorMessage /
    // Connection.LastErrorException to retrieve correspnding error information
    function NewThreadSafeStatementPrepared(SQLFormat: PUTF8Char;
      const Args: array of const; ExpectResults: Boolean): ISQLDBStatement; overload;
    /// execute a SQL query, returning a statement interface instance to retrieve
    // the result rows
    // - will call NewThreadSafeStatement method to retrieve a thread-safe
    // statement instance, then run the corresponding Execute() method
    // - raise an exception on error
    // - returns an ISQLDBRows to access any resulting rows (if ExpectResults is
    // TRUE), and provide basic garbage collection, as such:
    // ! procedure WriteFamily(const aName: RawUTF8);
    // ! var I: ISQLDBRows;
    // ! begin
    // !   I := MyConnProps.Execute('select * from table where name=?',[aName]);
    // !   while I.Step do
    // !     writeln(I['FirstName'],' ',DateToStr(I['BirthDate']));
    // ! end;
................................................................................
  // - more than one TSQLDBConnection instance can be run for the same
  // TSQLDBConnectionProperties
  TSQLDBConnection = class
  private
    function GetInTransaction: boolean;
  protected
    fProperties: TSQLDBConnectionProperties;
    fErrorException: ExceptClass;
    fErrorMessage: RawUTF8;
    fInfoMessage: string;
    fTransactionCount: integer;
    fServerTimeStampOffset: TDateTime;
    fCache: TRawUTF8ListHashed;
    function GetServerTimeStamp: TTimeLog; virtual;
    /// raise an exception
    procedure CheckConnection;
................................................................................
    // - the caller should free the instance after use
    function NewStatement: TSQLDBStatement; virtual; abstract;
    /// initialize a new SQL query statement for the given connection
    // - this default implementation will call the NewStatement method, and
    // implement handle statement caching is UseCache=true - in this case,
    // the TSQLDBStatement.Reset method shall have been overriden to allow
    // binding and execution of the very same prepared statement
    // - this method should return a prepared statement instance on success
    // - on error, if RaiseExceptionOnError=false (by default), it returns nil
    // and you can check LastErrorMessage and LastErrorException properties to
    // retrieve correspnding error information
    // - on error, if RaiseExceptionOnError=true, an exception is raised
    function NewStatementPrepared(const aSQL: RawUTF8;
      ExpectResults: Boolean; RaiseExceptionOnError: Boolean=false): ISQLDBStatement; virtual;
    /// begin a Transaction for this connection
    // - this default implementation will check and set TransactionCount
    procedure StartTransaction; virtual;
    /// commit changes of a Transaction for this connection
    // - StartTransaction method must have been called before
    // - this default implementation will check and set TransactionCount
    procedure Commit; virtual;
................................................................................
    // - this default implementation will check and set TransactionCount
    procedure Rollback; virtual;

    /// direct export of a DB statement rows into a new table of this database
    // - the corresponding table will be created within the current connection,
    // if it does not exist
    // - INSERTs will be nested within a transaction if WithinTransaction is TRUE
    // - will raise an Exception in case of error
    function NewTableFromRows(const TableName: RawUTF8;
      Rows: TSQLDBStatement; WithinTransaction: boolean): integer;

    /// number of nested StartTransaction calls
    // - equals 0 if no transaction is active
    property TransactionCount: integer read fTransactionCount;
    /// TRUE if StartTransaction has been called
................................................................................
    // - default implementation will return the executable time, i.e. Iso8601Now
    property ServerTimeStamp: TTimeLog read GetServerTimeStamp;
  published { to be logged as JSON }
    /// the associated database properties
    property Properties: TSQLDBConnectionProperties read fProperties;
    /// returns TRUE if the connection was set
    property Connected: boolean read IsConnected;
    /// some error message, e.g. during execution of NewStatementPrepared
    property LastErrorMessage: RawUTF8 read fErrorMessage;
    /// some error exception, e.g. during execution of NewStatementPrepared
    property LastErrorException: ExceptClass read fErrorException;
    /// some information message, as retrieved during execution
    property InfoMessage: string read fInfoMessage;
  end;

  /// generic abstract class to implement a prepared SQL query
  // - inherited classes should implement the DB-specific connection in its
  // overriden methods, especially Bind*(), Prepare(), ExecutePrepared, Step()
................................................................................
    fSQL: RawUTF8;
    fExpectResults: boolean;
    fParamCount: integer;
    fColumnCount: integer;
    fTotalRowsRetrieved: Integer;
    fCurrentRow: Integer;
    function GetSQLWithInlinedParams: RawUTF8;


    /// raise an exception if Col is out of range according to fColumnCount
    procedure CheckCol(Col: integer); {$ifdef HASINLINE}inline;{$endif}
    {/ will set a Int64/Double/Currency/TDateTime/RawUTF8/TBlobData Dest variable
      from a given column value
     - internal conversion will use a temporary Variant and ColumnToVariant method
     - expects Dest to be of the exact type (e.g. Int64, not Integer) }
    function ColumnToTypedValue(Col: integer; DestType: TSQLDBFieldType; var Dest): TSQLDBFieldType;
................................................................................
    // - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"'
    // format and contains true BLOB data
    // - if ReturnedRowCount points to an integer variable, it will be filled with
    // the number of row data returned (excluding field names)
    // - similar to corresponding TSQLRequest.Execute method in SynSQLite3 unit
    function FetchAllAsJSON(Expanded: boolean; ReturnedRowCount: PPtrInt=nil): RawUTF8;

    /// gets a number of updates made by latest executed statement
    // - default implementation returns 0
    function UpdateCount: integer; virtual;

    /// the associated database connection
    property Connection: TSQLDBConnection read fConnection;
    /// the prepared SQL statement, as supplied to Prepare() method
    property SQL: RawUTF8 read fSQL;
    /// the prepared SQL statement, with all '?' changed into the supplied
    // parameter values
    property SQLWithInlinedParams: RawUTF8 read GetSQLWithInlinedParams;
................................................................................
    /// the current row after Execute call, corresponding to Column*() methods
    // - contains 0 in case of no (more) available data, or a number >=1
    property CurrentRow: Integer read fCurrentRow;
    /// the total number of data rows retrieved by this instance
    // - is not reset when there is no more row of available data (Step returns
    // false), or when Step() is called with SeekFirst=true
    property TotalRowsRetrieved: Integer read fTotalRowsRetrieved;


  end;

  /// abstract connection created from TSQLDBConnectionProperties
  // - this overriden class will defined an hidden thread ID, to ensure
  // that one connection will be create per thread
  // - e.g. OleDB, ODBC and Oracle connections will inherit from this class
  TSQLDBConnectionThreadSafe = class(TSQLDBConnection)
................................................................................
    if fServerTimeStampOffset=0 then
      fServerTimeStampOffset := 0.0001; // request server only once
  end;
  PIso8601(@result)^.From(Current+fServerTimeStampOffset);
end;

function TSQLDBConnection.NewStatementPrepared(const aSQL: RawUTF8;
  ExpectResults: Boolean; RaiseExceptionOnError: Boolean=false): ISQLDBStatement;
var Stmt: TSQLDBStatement;
    ToCache: boolean;
    ndx: integer;
begin
  ToCache := fProperties.IsCachable(Pointer(aSQL));
  if ToCache and (fCache<>nil) then begin
    ndx := fCache.IndexOf(aSQL);
................................................................................
      if fCache=nil then
        fCache := TRawUTF8ListHashed.Create(true);
      fCache.AddObject(aSQL,Stmt);
      Stmt._AddRef;
    end;
    result := Stmt;
  except
    on E: Exception do begin
      Stmt.Free;
      if RaiseExceptionOnError then
        raise;
      StringToUTF8(E.Message,fErrorMessage);
      fErrorException := PPointer(E)^;
      result := nil;
    end;
  end;
end;  

procedure TSQLDBConnection.Rollback;
begin
................................................................................
   fMainConnection.Free;
  inherited;
end;

function TSQLDBConnectionProperties.Execute(const aSQL: RawUTF8;
  const Params: array of const
  {$ifndef LVCL}{$ifndef DELPHI5OROLDER}; RowsVariant: PVariant=nil{$endif}{$endif}): ISQLDBRows;
var Stmt: ISQLDBStatement;
begin
  Stmt := NewThreadSafeStatementPrepared(aSQL,true,true);
  Stmt.Bind(Params);
  Stmt.ExecutePrepared;
  result := Stmt;
  {$ifndef LVCL}
  {$ifndef DELPHI5OROLDER}
  if RowsVariant<>nil then
    RowsVariant^ := result.RowData;
  {$endif}
  {$endif}







end;

function TSQLDBConnectionProperties.ExecuteNoResult(const aSQL: RawUTF8;
  const Params: array of const): integer;
begin
  with NewThreadSafeStatementPrepared(aSQL,false,true) do
  try
    Bind(Params);
    ExecutePrepared;
    result := UpdateCount;
  finally
    Free;
  end;
end;

function TSQLDBConnectionProperties.GetMainConnection: TSQLDBConnection;
................................................................................

function TSQLDBConnectionProperties.NewThreadSafeStatement: TSQLDBStatement;
begin
  result := ThreadSafeConnection.NewStatement;
end;

function TSQLDBConnectionProperties.NewThreadSafeStatementPrepared(
  const aSQL: RawUTF8; ExpectResults: Boolean; RaiseExceptionOnError: Boolean=false): ISQLDBStatement;
begin
  result := ThreadSafeConnection.NewStatementPrepared(aSQL,ExpectResults,RaiseExceptionOnError);
end;

function TSQLDBConnectionProperties.NewThreadSafeStatementPrepared(
  SQLFormat: PUTF8Char; const Args: array of const; ExpectResults: Boolean): ISQLDBStatement;
begin
  result := NewThreadSafeStatementPrepared(FormatUTF8(SQLFormat,Args),ExpectResults);
end;
................................................................................

procedure TSQLDBStatement.Execute(SQLFormat: PUTF8Char;
  ExpectResults: Boolean; const Args, Params: array of const);
begin
  Execute(FormatUTF8(SQLFormat,Args),ExpectResults,Params);
end;

function TSQLDBStatement.UpdateCount: integer;
begin
  result := 0;
end;

function TSQLDBStatement.ColumnString(Col: integer): string;
begin
  Result := UTF8ToString(ColumnUTF8(Col));

Changes to SynDBFirebird.pas.

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
253
254
255
256
257
258
259


260
261
262
263
264
265
266
...
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
...
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
  protected
    fMain: TSQLDBFirebirdInternalStatement;
    fAutoCommit: TSQLDBFirebirdInternalStatement;
    fCurrent: ^TSQLDBFirebirdInternalStatement;
    fInput: TByteDynArray;
    fOutput: TByteDynArray;
    fStatus: TSQLDBFirebirdStatus;
    function GetUpdateCount: integer; override;
    function ReleaseMainStatementIfAny: boolean;
  public
    {{ create a Firebird statement instance, from an existing Firebird connection
     - the Execute method can be called once per TSQLDBFirebirdStatement instance,
       but you can use the Prepare once followed by several ExecutePrepared methods
     - if the supplied connection is not of TOleDBConnection type, will raise
       an exception }
................................................................................
    function ColumnBlob(Col: integer): RawByteString; override;
    {{ append all columns values of the current Row to a JSON stream
     - will use WR.Expand to guess the expected output format
     - fast overriden implementation with no temporary variable
     - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
       format and contains true BLOB data }
    procedure ColumnsToJSON(WR: TJSONWriter); override;


  end;

const
  FBLIBNAME: array[boolean] of TFileName =
  {$ifdef MSWINDOWS}
    ('gds32.dll','fbembed.dll');
  {$endif}
................................................................................
        Check(isc_rollback_transaction(fStatus,fAutoCommit.Transaction),fStatus);
    end;
  finally
    inherited Destroy;
  end;
end;

function TSQLDBFirebirdStatement.GetUpdateCount: integer;
begin
end;

procedure TSQLDBFirebirdStatement.Prepare(const aSQL: RawUTF8; ExpectResults: Boolean);
var Log: ISynLog;
    Conn: TSQLDBFirebirdConnection;
begin
................................................................................


{ TFirebirdLib }

procedure TFirebirdLib.Check(Status: ISCStatus; var PrivateStatus: TSQLDBFirebirdStatus);

  procedure RaiseEFirebirdException;
  var Code, len: integer;
      sql,tmp: array[byte] of AnsiChar;
      Vec: pointer;
      msg: AnsiString;
  begin
    Code := isc_sqlcode(PrivateStatus);
    isc_sql_interprete(Code,sql,sizeof(sql));
    Vec := @PrivateStatus;






<







 







>
>







 







|







 







|







190
191
192
193
194
195
196

197
198
199
200
201
202
203
...
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
...
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
...
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
  protected
    fMain: TSQLDBFirebirdInternalStatement;
    fAutoCommit: TSQLDBFirebirdInternalStatement;
    fCurrent: ^TSQLDBFirebirdInternalStatement;
    fInput: TByteDynArray;
    fOutput: TByteDynArray;
    fStatus: TSQLDBFirebirdStatus;

    function ReleaseMainStatementIfAny: boolean;
  public
    {{ create a Firebird statement instance, from an existing Firebird connection
     - the Execute method can be called once per TSQLDBFirebirdStatement instance,
       but you can use the Prepare once followed by several ExecutePrepared methods
     - if the supplied connection is not of TOleDBConnection type, will raise
       an exception }
................................................................................
    function ColumnBlob(Col: integer): RawByteString; override;
    {{ append all columns values of the current Row to a JSON stream
     - will use WR.Expand to guess the expected output format
     - fast overriden implementation with no temporary variable
     - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
       format and contains true BLOB data }
    procedure ColumnsToJSON(WR: TJSONWriter); override;
    /// returns the number of rows updated by the execution of this statement
    function UpdateCount: integer; override;
  end;

const
  FBLIBNAME: array[boolean] of TFileName =
  {$ifdef MSWINDOWS}
    ('gds32.dll','fbembed.dll');
  {$endif}
................................................................................
        Check(isc_rollback_transaction(fStatus,fAutoCommit.Transaction),fStatus);
    end;
  finally
    inherited Destroy;
  end;
end;

function TSQLDBFirebirdStatement.UpdateCount: integer;
begin
end;

procedure TSQLDBFirebirdStatement.Prepare(const aSQL: RawUTF8; ExpectResults: Boolean);
var Log: ISynLog;
    Conn: TSQLDBFirebirdConnection;
begin
................................................................................


{ TFirebirdLib }

procedure TFirebirdLib.Check(Status: ISCStatus; var PrivateStatus: TSQLDBFirebirdStatus);

  procedure RaiseEFirebirdException;
  var Code: integer;
      sql,tmp: array[byte] of AnsiChar;
      Vec: pointer;
      msg: AnsiString;
  begin
    Code := isc_sqlcode(PrivateStatus);
    isc_sql_interprete(Code,sql,sizeof(sql));
    Vec := @PrivateStatus;

Changes to SynDBODBC.pas.

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
...
234
235
236
237
238
239
240


241
242
243
244
245
246
247
....
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
    fColTmpLen: cardinal;
    fGetColIndicator: PtrInt; // as PtrInt=SqlLen
    fGetColStatus: SmallInt; // as SqlReturn
    fSQLW: RawUnicode;
    procedure AllocStatement;
    procedure BindColumns;
    function GetCol(Col: integer; ExpectedType: TSQLDBFieldType): TSQLDBStatementGetCol;
    function GetUpdateCount: integer; override;
  public
    {{ create a ODBC statement instance, from an existing ODBC connection
     - the Execute method can be called once per TODBCStatement instance,
       but you can use the Prepare once followed by several ExecutePrepared methods
     - if the supplied connection is not of TOleDBConnection type, will raise
       an exception }
    constructor Create(aConnection: TSQLDBConnection); override;
................................................................................
    function ColumnBlob(Col: integer): RawByteString; override;
    {{ append all columns values of the current Row to a JSON stream
     - will use WR.Expand to guess the expected output format
     - fast overriden implementation with no temporary variable
     - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
       format and contains true BLOB data }
    procedure ColumnsToJSON(WR: TJSONWriter); override;


  end;


implementation


{ -------------- ODBC library interfaces, constants and types }
................................................................................
      Check(CloseCursor(fStatement),SQL_HANDLE_STMT,fStatement);
    if fParamCount>0 then
      Check(FreeStmt(fStatement,SQL_RESET_PARAMS),SQL_HANDLE_STMT,fStatement);
  end;
  inherited Reset;
end;

function TODBCStatement.GetUpdateCount: integer;
var RowCount: SqlLen;
begin
  if (fStatement<>nil) and not fExpectResults then
    ODBC.Check(ODBC.RowCount(fStatement,RowCount),SQL_HANDLE_STMT,fStatement) else
    RowCount := 0;
  result := RowCount;
end;






<







 







>
>







 







|







172
173
174
175
176
177
178

179
180
181
182
183
184
185
...
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
....
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
    fColTmpLen: cardinal;
    fGetColIndicator: PtrInt; // as PtrInt=SqlLen
    fGetColStatus: SmallInt; // as SqlReturn
    fSQLW: RawUnicode;
    procedure AllocStatement;
    procedure BindColumns;
    function GetCol(Col: integer; ExpectedType: TSQLDBFieldType): TSQLDBStatementGetCol;

  public
    {{ create a ODBC statement instance, from an existing ODBC connection
     - the Execute method can be called once per TODBCStatement instance,
       but you can use the Prepare once followed by several ExecutePrepared methods
     - if the supplied connection is not of TOleDBConnection type, will raise
       an exception }
    constructor Create(aConnection: TSQLDBConnection); override;
................................................................................
    function ColumnBlob(Col: integer): RawByteString; override;
    {{ append all columns values of the current Row to a JSON stream
     - will use WR.Expand to guess the expected output format
     - fast overriden implementation with no temporary variable
     - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
       format and contains true BLOB data }
    procedure ColumnsToJSON(WR: TJSONWriter); override;
    /// returns the number of rows updated by the execution of this statement
    function UpdateCount: integer; override;
  end;


implementation


{ -------------- ODBC library interfaces, constants and types }
................................................................................
      Check(CloseCursor(fStatement),SQL_HANDLE_STMT,fStatement);
    if fParamCount>0 then
      Check(FreeStmt(fStatement,SQL_RESET_PARAMS),SQL_HANDLE_STMT,fStatement);
  end;
  inherited Reset;
end;

function TODBCStatement.UpdateCount: integer;
var RowCount: SqlLen;
begin
  if (fStatement<>nil) and not fExpectResults then
    ODBC.Check(ODBC.RowCount(fStatement,RowCount),SQL_HANDLE_STMT,fStatement) else
    RowCount := 0;
  result := RowCount;
end;

Changes to SynDBOracle.pas.

253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
...
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
...
379
380
381
382
383
384
385


386
387
388
389
390
391
392
....
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608


1609
1610
1611
1612
1613










1614
1615
1616
1617
1618
1619
1620
....
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
    // - the caller should free the instance after use
    function NewStatement: TSQLDBStatement; override;
    /// initialize a new SQL query statement for the given connection
    // - if UseCache=true, this overriden implementation will use server-side
    // Oracle statement cache - in this case, StatementCacheSize will define
    // how many statements are to be cached
    // - this method should return nil in case of error, or a prepared statement
    // instance in case of success
    function NewStatementPrepared(const aSQL: RawUTF8;
      ExpectResults: Boolean): ISQLDBStatement; override; 
    /// begin a Transaction for this connection
    // - current implementation do not support nested transaction with those
    // methods: exception will be raised in such case
    // - by default, TSQLDBOracleStatement works in AutoCommit mode, unless
    // StartTransaction is called
    procedure StartTransaction; override;
    /// commit changes of a Transaction for this connection
................................................................................
    fRowFetchedEnded: boolean;
    fRowBuffer: TByteDynArray;
    fInternalBufferSize: cardinal;
    fUseServerSideStatementCache: boolean;
{$ifndef DELPHI5OROLDER}
    fTimeElapsed: TPrecisionTimer;
{$endif}
    function GetUpdateCount: integer; override;
    procedure FreeHandles;
    procedure FetchTest(Status: integer);
    /// Col=0...fColumnCount-1
    function GetCol(Col: Integer; out Column: PSQLDBColumnProperty): pointer;
  public
    {{ create an OCI statement instance, from an existing OCI connection
     - the Execute method can be called once per TSQLDBOracleStatement instance,
................................................................................
    {{ append all columns values of the current Row to a JSON stream
     - will use WR.Expand to guess the expected output format
     - fast overriden implementation with no temporary variable (about 20%
       faster when run over high number of data rows) 
     - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
       format and contains true BLOB data }
    procedure ColumnsToJSON(WR: TJSONWriter); override;


  end;


implementation

{ TOracleDate }

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

function TSQLDBOracleConnection.NewStatement: TSQLDBStatement;
begin
  result := TSQLDBOracleStatement.Create(self);
end;

function TSQLDBOracleConnection.NewStatementPrepared(const aSQL: RawUTF8;
  ExpectResults: Boolean): ISQLDBStatement;
var Stmt: TSQLDBOracleStatement;
begin


  Stmt := TSQLDBOracleStatement.Create(self);
  if fProperties.IsCachable(pointer(aSQL)) then
    Stmt.fUseServerSideStatementCache := true;
  Stmt.Prepare(aSQL,ExpectResults);
  result := Stmt;










end;

procedure TSQLDBOracleConnection.Rollback;
begin
  inherited;
  if fTrans=nil then
    raise ESQLDBOracle.Create('Invalid RollBack call');
................................................................................
    // 0:OK, >0:untruncated length, -1:NULL, -2:truncated (length>32KB)
   -1: result := nil; // NULL
    0: exit;
    else LogTruncatedColumn(Column^);
  end;
end;

function TSQLDBOracleStatement.GetUpdateCount: integer;
begin
  result := 0;
  if fStatement<>nil then
    OCI.AttrGet(fStatement,OCI_HTYPE_STMT,@result,nil,OCI_ATTR_ROW_COUNT,fError);
end;

const






|

|







 







<







 







>
>







 







|


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







 







|







253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
...
290
291
292
293
294
295
296

297
298
299
300
301
302
303
...
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
....
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
....
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
    // - the caller should free the instance after use
    function NewStatement: TSQLDBStatement; override;
    /// initialize a new SQL query statement for the given connection
    // - if UseCache=true, this overriden implementation will use server-side
    // Oracle statement cache - in this case, StatementCacheSize will define
    // how many statements are to be cached
    // - this method should return nil in case of error, or a prepared statement
    // instance in case of success (with default RaiseExceptionOnError=false)
    function NewStatementPrepared(const aSQL: RawUTF8;
      ExpectResults: Boolean; RaiseExceptionOnError: Boolean=false): ISQLDBStatement; override;
    /// begin a Transaction for this connection
    // - current implementation do not support nested transaction with those
    // methods: exception will be raised in such case
    // - by default, TSQLDBOracleStatement works in AutoCommit mode, unless
    // StartTransaction is called
    procedure StartTransaction; override;
    /// commit changes of a Transaction for this connection
................................................................................
    fRowFetchedEnded: boolean;
    fRowBuffer: TByteDynArray;
    fInternalBufferSize: cardinal;
    fUseServerSideStatementCache: boolean;
{$ifndef DELPHI5OROLDER}
    fTimeElapsed: TPrecisionTimer;
{$endif}

    procedure FreeHandles;
    procedure FetchTest(Status: integer);
    /// Col=0...fColumnCount-1
    function GetCol(Col: Integer; out Column: PSQLDBColumnProperty): pointer;
  public
    {{ create an OCI statement instance, from an existing OCI connection
     - the Execute method can be called once per TSQLDBOracleStatement instance,
................................................................................
    {{ append all columns values of the current Row to a JSON stream
     - will use WR.Expand to guess the expected output format
     - fast overriden implementation with no temporary variable (about 20%
       faster when run over high number of data rows) 
     - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
       format and contains true BLOB data }
    procedure ColumnsToJSON(WR: TJSONWriter); override;
    /// returns the number of rows updated by the execution of this statement
    function UpdateCount: integer; override;
  end;


implementation

{ TOracleDate }

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

function TSQLDBOracleConnection.NewStatement: TSQLDBStatement;
begin
  result := TSQLDBOracleStatement.Create(self);
end;

function TSQLDBOracleConnection.NewStatementPrepared(const aSQL: RawUTF8;
  ExpectResults: Boolean; RaiseExceptionOnError: Boolean=false): ISQLDBStatement;
var Stmt: TSQLDBOracleStatement;
begin
  Stmt := nil;
  try
    Stmt := TSQLDBOracleStatement.Create(self);
    if fProperties.IsCachable(pointer(aSQL)) then
      Stmt.fUseServerSideStatementCache := true;
    Stmt.Prepare(aSQL,ExpectResults);
    result := Stmt;
  except
    on E: Exception do begin
      Stmt.Free;
      if RaiseExceptionOnError then
        raise;
      fErrorException := PPointer(E)^;
      StringToUTF8(E.Message,fErrorMessage);
      result := nil;
    end;
  end;
end;

procedure TSQLDBOracleConnection.Rollback;
begin
  inherited;
  if fTrans=nil then
    raise ESQLDBOracle.Create('Invalid RollBack call');
................................................................................
    // 0:OK, >0:untruncated length, -1:NULL, -2:truncated (length>32KB)
   -1: result := nil; // NULL
    0: exit;
    else LogTruncatedColumn(Column^);
  end;
end;

function TSQLDBOracleStatement.UpdateCount: integer;
begin
  result := 0;
  if fStatement<>nil then
    OCI.AttrGet(fStatement,OCI_HTYPE_STMT,@result,nil,OCI_ATTR_ROW_COUNT,fError);
end;

const