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

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

Overview
Comment:{5875} reverted ISQLDBRows.RefCount check and introduced an explicit call to new ReleaseRows method
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a1dfe9124efb3d1bf7a440e136de7834d3377d78
User & Date: ab 2020-03-22 11:06:28
Context
2020-03-22
11:22
{5876} introducing TSQLDBStatementWithParams.ReleaseRows to release the bound parameter memory ASAP (may be huge in case of array binding or blob process) check-in: a1396fa904 user: ab tags: trunk
11:06
{5875} reverted ISQLDBRows.RefCount check and introduced an explicit call to new ReleaseRows method check-in: a1dfe9124e user: ab tags: trunk
02:37
{5874} deep refactoring of SQL parameters logging - to use less resource and be more reusable - especially for SynDBSQLite3 check-in: a4ea4462fd user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMotDB.pas.

314
315
316
317
318
319
320


321
322
323
324
325
326
327
....
1129
1130
1131
1132
1133
1134
1135

1136
1137
1138
1139
1140
1141
1142
....
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
....
1369
1370
1371
1372
1373
1374
1375

1376
1377
1378
1379
1380
1381
1382
....
1396
1397
1398
1399
1400
1401
1402

1403
1404
1405
1406
1407
1408
1409
....
1425
1426
1427
1428
1429
1430
1431

1432
1433
1434
1435
1436
1437
1438
....
1554
1555
1556
1557
1558
1559
1560


1561
1562
1563
1564
1565
1566
1567
....
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662


1663
1664
1665
1666
1667
1668
1669
....
1968
1969
1970
1971
1972
1973
1974







1975
1976
1977
1978
1979
1980
1981
  protected
    fStatement: ISQLDBStatement;
    fSQL: RawUTF8;
    fHasData: boolean;
    // on exception, release fStatement and optionally clear the pool
    procedure HandleClearPoolOnConnectionIssue;
  public


    /// called to begin a search in the virtual table, creating a SQL query
    // - the TSQLVirtualTablePrepared parameters were set by
    // TSQLVirtualTable.Prepare and will contain both WHERE and ORDER BY statements
    // (retrieved by x_BestIndex from a TSQLite3IndexInfo structure)
    // - Prepared will contain all prepared constraints and the corresponding
    // expressions in the Where[].Value field
    // - will move cursor to first row of matching data
................................................................................
          mPost, mPut:
            for i := 0 to high(Values) do
              Query.BindArray(i+1,Types[i],Values[i],n);
          mDelete:
            Query.BindArray(1,ftInt64,Values[0],n);
          end;
          Query.ExecutePrepared;

          Query := nil;
        end;
      except
        Query := nil;
        HandleClearPoolOnConnectionIssue;
        raise;
      end;
................................................................................
      result := copy(result,2,length(result)-3);
  except
    Stmt := nil;
    HandleClearPoolOnConnectionIssue;
  end;
end;

function TSQLRestStorageExternal.EngineExecute(
  const aSQL: RawUTF8): boolean;
begin
  if aSQL='' then
    result := false else
    result := ExecuteInlined(aSQL,false)<>nil;
end;

function TSQLRestStorageExternal.TableHasRows(Table: TSQLRecordClass): boolean;
................................................................................
    exit;
  with fStoredClassMapping^ do
    rows := ExecuteDirect('select % from % where %=?',[InternalToExternal(BlobField^.Name),
      fTableName,RowIDFieldName],[aID],{results=}true);
  if (rows<>nil) and rows.Step then
  try
    BlobData := rows.ColumnBlob(0);

    rows := nil;
    result := true; // success
  except
    rows := nil;
    HandleClearPoolOnConnectionIssue;
  end;
end;
................................................................................
      [Value.ID],true);
    if (rows<>nil) and rows.Step then
    try
      for f := 0 to High(BlobFields) do begin
        rows.ColumnToSQLVar(f,data,temp);
        BlobFields[f].SetFieldSQLVar(Value,data);
      end;

      rows := nil;
      result := true; // success
    except
      HandleClearPoolOnConnectionIssue;
    end;
  end;
end;
................................................................................
          rows := ExecuteInlined('select % from % where %=:(%):',
            [RowIDFieldName,fTableName,ExtWhereFieldName,WhereValue],true);
          if rows=nil then
            exit;
          JSONEncodeNameSQLValue(SetFieldName,SetValue,JSON);
          while rows.Step do
            Owner.InternalUpdateEvent(seUpdate,TableModelIndex,rows.ColumnInt(0),JSON,nil);

        end;
        Owner.FlushInternalDBCache;
      end;
    end;
end;

function TSQLRestStorageExternal.EngineUpdateFieldIncrement(TableModelIndex: integer;
................................................................................
  try
    stmt := fProperties.PrepareInlined(aSQL,ExpectResults);
    if stmt=nil then
      exit;
    if ExpectResults and (sftDateTimeMS in fStoredClassRecordProps.HasTypeFields) then
      stmt.ForceDateWithMS := true;
    stmt.ExecutePrepared;


    result := stmt;
  except
    stmt := nil;
    HandleClearPoolOnConnectionIssue; // leave result=nil to notify error
  end;
end;

................................................................................
    rows: ISQLDBRows;
begin
  result := false;
  try
    n := 0;
    rows := ExecuteDirect('select % from % where %=?',
      [fStoredClassMapping^.RowIDFieldName,fTableName,FieldName],FieldValue,true);
    if rows<>nil then
      while rows.Step do
        AddInt64(TInt64DynArray(ResultID),n,rows.ColumnInt(0));


    SetLength(ResultID,n);
    result := n>0;
  except
    rows := nil;
    HandleClearPoolOnConnectionIssue; // leave result=false to notify error
  end;
end;
................................................................................
procedure TSQLVirtualTableCursorExternal.HandleClearPoolOnConnectionIssue;
begin
  fStatement := nil;
  fHasData := false;
  if (self<>nil) and (Table<>nil) and (Table.Static<>nil) then
    (Table.Static as TSQLRestStorageExternal).HandleClearPoolOnConnectionIssue;
end;








function TSQLVirtualTableCursorExternal.Column(aColumn: integer;
  var aResult: TSQLVar): boolean;
var n: cardinal;
begin
  result := false;
  if (self<>nil) and (fStatement<>nil) then






>
>







 







>







 







|
<







 







>







 







>







 







>







 







>
>







 







|


>
>







 







>
>
>
>
>
>
>







314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
....
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
....
1326
1327
1328
1329
1330
1331
1332
1333

1334
1335
1336
1337
1338
1339
1340
....
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
....
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
....
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
....
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
....
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
....
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
  protected
    fStatement: ISQLDBStatement;
    fSQL: RawUTF8;
    fHasData: boolean;
    // on exception, release fStatement and optionally clear the pool
    procedure HandleClearPoolOnConnectionIssue;
  public
    /// finalize the external cursor by calling ReleaseRows
    destructor Destroy; override;
    /// called to begin a search in the virtual table, creating a SQL query
    // - the TSQLVirtualTablePrepared parameters were set by
    // TSQLVirtualTable.Prepare and will contain both WHERE and ORDER BY statements
    // (retrieved by x_BestIndex from a TSQLite3IndexInfo structure)
    // - Prepared will contain all prepared constraints and the corresponding
    // expressions in the Where[].Value field
    // - will move cursor to first row of matching data
................................................................................
          mPost, mPut:
            for i := 0 to high(Values) do
              Query.BindArray(i+1,Types[i],Values[i],n);
          mDelete:
            Query.BindArray(1,ftInt64,Values[0],n);
          end;
          Query.ExecutePrepared;
          Query.ReleaseRows;
          Query := nil;
        end;
      except
        Query := nil;
        HandleClearPoolOnConnectionIssue;
        raise;
      end;
................................................................................
      result := copy(result,2,length(result)-3);
  except
    Stmt := nil;
    HandleClearPoolOnConnectionIssue;
  end;
end;

function TSQLRestStorageExternal.EngineExecute(const aSQL: RawUTF8): boolean;

begin
  if aSQL='' then
    result := false else
    result := ExecuteInlined(aSQL,false)<>nil;
end;

function TSQLRestStorageExternal.TableHasRows(Table: TSQLRecordClass): boolean;
................................................................................
    exit;
  with fStoredClassMapping^ do
    rows := ExecuteDirect('select % from % where %=?',[InternalToExternal(BlobField^.Name),
      fTableName,RowIDFieldName],[aID],{results=}true);
  if (rows<>nil) and rows.Step then
  try
    BlobData := rows.ColumnBlob(0);
    rows.ReleaseRows;
    rows := nil;
    result := true; // success
  except
    rows := nil;
    HandleClearPoolOnConnectionIssue;
  end;
end;
................................................................................
      [Value.ID],true);
    if (rows<>nil) and rows.Step then
    try
      for f := 0 to High(BlobFields) do begin
        rows.ColumnToSQLVar(f,data,temp);
        BlobFields[f].SetFieldSQLVar(Value,data);
      end;
      rows.ReleaseRows;
      rows := nil;
      result := true; // success
    except
      HandleClearPoolOnConnectionIssue;
    end;
  end;
end;
................................................................................
          rows := ExecuteInlined('select % from % where %=:(%):',
            [RowIDFieldName,fTableName,ExtWhereFieldName,WhereValue],true);
          if rows=nil then
            exit;
          JSONEncodeNameSQLValue(SetFieldName,SetValue,JSON);
          while rows.Step do
            Owner.InternalUpdateEvent(seUpdate,TableModelIndex,rows.ColumnInt(0),JSON,nil);
          rows.ReleaseRows;
        end;
        Owner.FlushInternalDBCache;
      end;
    end;
end;

function TSQLRestStorageExternal.EngineUpdateFieldIncrement(TableModelIndex: integer;
................................................................................
  try
    stmt := fProperties.PrepareInlined(aSQL,ExpectResults);
    if stmt=nil then
      exit;
    if ExpectResults and (sftDateTimeMS in fStoredClassRecordProps.HasTypeFields) then
      stmt.ForceDateWithMS := true;
    stmt.ExecutePrepared;
    if not ExpectResults then
      stmt.ReleaseRows;
    result := stmt;
  except
    stmt := nil;
    HandleClearPoolOnConnectionIssue; // leave result=nil to notify error
  end;
end;

................................................................................
    rows: ISQLDBRows;
begin
  result := false;
  try
    n := 0;
    rows := ExecuteDirect('select % from % where %=?',
      [fStoredClassMapping^.RowIDFieldName,fTableName,FieldName],FieldValue,true);
    if rows<>nil then begin
      while rows.Step do
        AddInt64(TInt64DynArray(ResultID),n,rows.ColumnInt(0));
      rows.ReleaseRows;
    end;
    SetLength(ResultID,n);
    result := n>0;
  except
    rows := nil;
    HandleClearPoolOnConnectionIssue; // leave result=false to notify error
  end;
end;
................................................................................
procedure TSQLVirtualTableCursorExternal.HandleClearPoolOnConnectionIssue;
begin
  fStatement := nil;
  fHasData := false;
  if (self<>nil) and (Table<>nil) and (Table.Static<>nil) then
    (Table.Static as TSQLRestStorageExternal).HandleClearPoolOnConnectionIssue;
end;

destructor TSQLVirtualTableCursorExternal.Destroy;
begin
  if fStatement <> nil then
    fStatement.ReleaseRows;
  inherited Destroy;
end;

function TSQLVirtualTableCursorExternal.Column(aColumn: integer;
  var aResult: TSQLVar): boolean;
var n: cardinal;
begin
  result := false;
  if (self<>nil) and (fStatement<>nil) then

Changes to SynCommons.pas.

7086
7087
7088
7089
7090
7091
7092
7093

7094
7095
7096
7097

7098
7099
7100
7101
7102
7103
7104
// so use PIntegerArray(Indexes.buf) to access the values
// - caller should always make Indexes.Done once done
procedure DynArraySortIndexed(Values: pointer; ElemSize, Count: Integer;
  out Indexes: TSynTempBuffer; Compare: TDynArraySortCompare);

/// compare two TGUID values
// - this version is faster than the one supplied by SysUtils
function IsEqualGUID(const guid1, guid2: TGUID): Boolean; overload; {$ifdef HASINLINE}inline;{$endif}


/// compare two TGUID values
// - this version is faster than the one supplied by SysUtils
function IsEqualGUID(guid1, guid2: PGUID): Boolean; overload; {$ifdef HASINLINE}inline;{$endif}


/// returns the index of a matching TGUID in an array
// - returns -1 if no item matched
function IsEqualGUIDArray(const guid: TGUID; const guids: array of TGUID): integer;

/// check if a TGUID value contains only 0 bytes
// - this version is faster than the one supplied by SysUtils






|
>



|
>







7086
7087
7088
7089
7090
7091
7092
7093
7094
7095
7096
7097
7098
7099
7100
7101
7102
7103
7104
7105
7106
// so use PIntegerArray(Indexes.buf) to access the values
// - caller should always make Indexes.Done once done
procedure DynArraySortIndexed(Values: pointer; ElemSize, Count: Integer;
  out Indexes: TSynTempBuffer; Compare: TDynArraySortCompare);

/// compare two TGUID values
// - this version is faster than the one supplied by SysUtils
function IsEqualGUID(const guid1, guid2: TGUID): Boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// compare two TGUID values
// - this version is faster than the one supplied by SysUtils
function IsEqualGUID(guid1, guid2: PGUID): Boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// returns the index of a matching TGUID in an array
// - returns -1 if no item matched
function IsEqualGUIDArray(const guid: TGUID; const guids: array of TGUID): integer;

/// check if a TGUID value contains only 0 bytes
// - this version is faster than the one supplied by SysUtils

Changes to SynDB.pas.

350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370

371
372


373
374






375
376
377
378
379
380
381
...
470
471
472
473
474
475
476

477
478
479
480
481
482
483
...
489
490
491
492
493
494
495

496
497
498
499
500
501
502
....
1132
1133
1134
1135
1136
1137
1138

1139
1140
1141
1142
1143
1144
1145
1146
1147


1148
1149
1150
1151
1152
1153
1154
....
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
....
1837
1838
1839
1840
1841
1842
1843






1844
1845
1846
1847
1848
1849
1850
....
1927
1928
1929
1930
1931
1932
1933

1934
1935
1936
1937
1938
1939
1940
....
2032
2033
2034
2035
2036
2037
2038

2039
2040
2041
2042
2043
2044
2045
....
4175
4176
4177
4178
4179
4180
4181

4182
4183
4184
4185
4186
4187
4188
....
6855
6856
6857
6858
6859
6860
6861

6862
6863
6864
6865
6866
6867
6868
....
6936
6937
6938
6939
6940
6941
6942

6943
6944
6945
6946
6947
6948
6949
....
7064
7065
7066
7067
7068
7069
7070

7071
7072
7073
7074
7075
7076
7077
....
7174
7175
7176
7177
7178
7179
7180
7181
7182
7183
7184
7185
7186
7187
7188
7189
7190
7191
7192
7193
7194
....
7368
7369
7370
7371
7372
7373
7374
7375
7376
7377
7378
7379
7380
7381
7382
  /// generic interface to access a SQL query result rows
  // - not all TSQLDBStatement methods are available, but only those to retrieve
  // data from a statement result: the purpose of this interface is to make
  // easy access to result rows, not provide all available features - therefore
  // you only have access to the Step() and Column*() methods
  ISQLDBRows = interface
    ['{11291095-9C15-4984-9118-974F1926DB9F}']
    /// After a prepared statement has been prepared returning a ISQLDBRows
    // interface, this method must be called one or more times to evaluate it
    // - you shall call this method before calling any Column*() methods
    // - return TRUE on success, with data ready to be retrieved by Column*()
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - should raise an Exception on any error
    // - typical use may be:
    // ! var Customer: Variant;
    // ! begin
    // !   with Props.Execute( 'select * from Sales.Customer where AccountNumber like ?', ['AW000001%'],@Customer) do

    // !     while Step do //  loop through all matching data rows
    // !       assert(Copy(Customer.AccountNumber,1,8)='AW000001');


    // ! end;
    function Step(SeekFirst: boolean=false): boolean;







    /// the column/field count of the current Row
    function ColumnCount: integer;
    /// the Column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
    // - it's up to the implementation to ensure than all column names are unique
    function ColumnName(Col: integer): RawUTF8;
................................................................................
    // - this default property can be used to write simple code like this:
    // ! 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;
    // - of course, using a variant and a column name will be a bit slower than
    // direct access via the Column*() dedicated methods, but resulting code
    // is fast in practice
    property Column[const ColName: RawUTF8]: Variant read GetColumnVariant; default;
    {$ifndef DELPHI5OROLDER}
    /// create a TSQLDBRowVariantType able to access any field content via late binding
................................................................................
    // - typical use is:
    // ! var Row: Variant;
    // ! (...)
    // !  with MyConnProps.Execute('select * from table where name=?',[aName]) do begin
    // !    Row := RowData;
    // !    while Step do
    // !      writeln(Row.FirstName,Row.BirthDate);

    // !  end;
    function RowData: Variant;
    /// create a TDocVariant custom variant containing all columns values
    // - will create a "fast" TDocVariant object instance with all fields
    procedure RowDocVariant(out aDocument: variant;
      aOptions: TDocVariantOptions=JSON_OPTIONS_FAST);
    {$endif DELPHI5OROLDER}
................................................................................
    // 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;
    // - if RowsVariant is set, you can use it to row column access via late
    // binding, as such:
    // ! procedure WriteFamily(const aName: RawUTF8);
    // ! var R: Variant;
    // ! begin
    // !   with MyConnProps.Execute('select * from table where name=?',[aName],@R) do
    // !   while Step do
    // !     writeln(R.FirstName,' ',DateToStr(R.BirthDate));


    // ! end;
    // - you can any BLOB field to be returned as null with the ForceBlobAsNull
    // optional parameter
    function Execute(const aSQL: RawUTF8; const Params: array of const
      {$ifndef LVCL}{$ifndef DELPHI5OROLDER}; RowsVariant: PVariant=nil{$endif}{$endif};
      ForceBlobAsNull: boolean=false): ISQLDBRows;
    /// execute a SQL query, without returning any rows
................................................................................
    /// return a Column as a variant
    function GetColumnVariant(const ColName: RawUTF8): Variant;
    {$endif}
    /// return the associated statement instance for a ISQLDBRows interface
    function Instance: TSQLDBStatement;
    /// a wrapper to compute sllSQL context and start a local timer
    function GetSQLLog(var timer: TPrecisionTimer; SQL: PRawUTF8 = nil): TSynLog;
    /// when RefCount=1, call Reset to release DB cursor resources
    {$ifdef FPC}
    function _Release: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    {$else}
    function _Release: Integer; stdcall;
    {$endif}
    /// override to free cursor memory when ISQLDBStatement is back in cache
    procedure ReleaseResources; virtual;
  public
    /// create a statement instance
    constructor Create(aConnection: TSQLDBConnection); virtual;

    /// bind a NULL value to a parameter
    // - the leftmost SQL parameter has an index of 1
    // - some providers (e.g. OleDB during MULTI INSERT statements) expect the
................................................................................
    // already connected
    procedure Prepare(const aSQL: RawUTF8; ExpectResults: Boolean); overload; virtual;
    /// Execute a prepared SQL statement
    // - parameters marked as ? should have been already bound with Bind*() functions
    // - should raise an Exception on any error
    // - this void default implementation will call set fConnection.fLastAccess
    procedure ExecutePrepared; virtual;






    /// Reset the previous prepared statement
    // - some drivers expect an explicit reset before binding parameters and
    // executing the statement another time
    // - this default implementation will just do nothing
    procedure Reset; virtual;
    /// Prepare and Execute an UTF-8 encoded SQL statement
    // - parameters marked as ? should have been already bound with Bind*()
................................................................................
    // ! var Query: ISQLDBStatement;
    // ! begin
    // !   Query := Props.NewThreadSafeStatementPrepared('select AccountNumber from Sales.Customer where AccountNumber like ?', ['AW000001%'],true);
    // !   if Query<>nil then begin
    // !     assert(SameTextU(Query.ColumnName(0),'AccountNumber'));
    // !     while Query.Step do //  loop through all matching data rows
    // !       assert(Copy(Query.ColumnUTF8(0),1,8)='AW000001');

    // !   end;
    // ! end;
    function Step(SeekFirst: boolean=false): boolean; virtual; abstract;
    /// the column/field count of the current Row
    function ColumnCount: integer;
    /// the Column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
................................................................................
    // - typical use is:
    // ! var Row: Variant;
    // ! (...)
    // !  with MyConnProps.Execute('select * from table where name=?',[aName]) do begin
    // !    Row := RowDaa;
    // !    while Step do
    // !      writeln(Row.FirstName,Row.BirthDate);

    // !  end;
    function RowData: Variant; virtual;
    /// create a TDocVariant custom variant containing all columns values
    // - will create a "fast" TDocVariant object instance with all fields
    procedure RowDocVariant(out aDocument: variant;
      aOptions: TDocVariantOptions=JSON_OPTIONS_FAST); virtual;
    {$endif}
................................................................................
          Properties.GetTableNames(Tables);
          if FindRawUTF8(Tables,TableName,false)<0 then
            with Properties do
              ExecuteNoResult(SQLCreate(aTableName,Fields,false),[]);
          Ins := NewStatement;
          Ins.Prepare(SQL,false);
        end;

        // write row data
        Ins.BindFromRows(ColumnForcedTypes,Rows);
        Ins.ExecutePrepared;
        Ins.Reset;
        inc(result);
      end;
      if WithinTransaction then
................................................................................
      W.Add('[');
    // write rows data
    while Step do begin
      ColumnsToJSON(W);
      W.Add(',');
      inc(result);
    end;

    if (result=0) and W.Expand then begin
      // we want the field names at least, even with no data (RowCount=0)
      W.Expand := false; //  {"FieldCount":2,"Values":["col1","col2"]}
      W.CancelAll;
      for col := 0 to ColumnCount-1 do
        W.ColNames[col] := ColumnName(col); // previous W.AddColumns did add ""
      W.AddColumns;
................................................................................
        end;
        if F=FMax then
          W.AddCR else
          W.Add(CommaSep);
      end;
      inc(result);
    end;

    W.FlushFinal;
  finally
    W.Free;
  end;
end;

function TSQLDBStatement.FetchAllAsJSON(Expanded: boolean;
................................................................................
          W.Write1(0); // = W.WriteVarUInt32(0)
        // then write data values
        ColumnsToBinary(W,pointer(Null),ColTypes);
        inc(result);
        if (MaxRowCount>0) and (result>=MaxRowCount) then
          break;
      until not Step;

    end;
    W.Write(@result,SizeOf(result)); // fixed size at the end for row count
    W.Flush;
  finally
    W.Free;
  end;
end;
................................................................................
end;

function TSQLDBStatement.Instance: TSQLDBStatement;
begin
  Result := Self;
end;

function TSQLDBStatement._Release: {$ifdef FPC}longint{$else}integer{$endif};
begin
  result := inherited _Release;
  if result=1 then
    ReleaseResources;
end;

function TSQLDBStatement.GetSQLLog(var timer: TPrecisionTimer; SQL: PRawUTF8): TSynLog;
var level: TSynLogInfo;
begin
  result := SynDBLog.Add;
  if result = nil then
    exit;
  if SQL = nil then
................................................................................

procedure TSQLDBStatement.Reset;
begin
  fSQLWithInlinedParams := '';
  // a do-nothing default method (used e.g. for OCI)
end;

procedure TSQLDBStatement.ReleaseResources;
begin
  fSQLWithInlinedParams := '';
end;

function TSQLDBStatement.ColumnsToSQLInsert(const TableName: RawUTF8;
  var Fields: TSQLDBColumnCreateDynArray): RawUTF8;
var F,size: integer;






|












|
>


>
>


>
>
>
>
>
>







 







>







 







>







 







>






|
|
|
>
>







 







<
<
<
<
<
<
<
<







 







>
>
>
>
>
>







 







>







 







>







 







>







 







>







 







>







 







>







 







<
<
<
<
<
<
<







 







|







350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
...
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
...
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
....
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
....
1691
1692
1693
1694
1695
1696
1697








1698
1699
1700
1701
1702
1703
1704
....
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
....
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
....
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
....
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
....
6870
6871
6872
6873
6874
6875
6876
6877
6878
6879
6880
6881
6882
6883
6884
....
6952
6953
6954
6955
6956
6957
6958
6959
6960
6961
6962
6963
6964
6965
6966
....
7081
7082
7083
7084
7085
7086
7087
7088
7089
7090
7091
7092
7093
7094
7095
....
7192
7193
7194
7195
7196
7197
7198







7199
7200
7201
7202
7203
7204
7205
....
7379
7380
7381
7382
7383
7384
7385
7386
7387
7388
7389
7390
7391
7392
7393
  /// generic interface to access a SQL query result rows
  // - not all TSQLDBStatement methods are available, but only those to retrieve
  // data from a statement result: the purpose of this interface is to make
  // easy access to result rows, not provide all available features - therefore
  // you only have access to the Step() and Column*() methods
  ISQLDBRows = interface
    ['{11291095-9C15-4984-9118-974F1926DB9F}']
    /// after a prepared statement has been prepared returning a ISQLDBRows
    // interface, this method must be called one or more times to evaluate it
    // - you shall call this method before calling any Column*() methods
    // - return TRUE on success, with data ready to be retrieved by Column*()
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - should raise an Exception on any error
    // - typical use may be:
    // ! var Customer: Variant;
    // ! begin
    // !   with Props.Execute( 'select * from Sales.Customer where AccountNumber like ?',
    // !       ['AW000001%'],@Customer) do begin
    // !     while Step do //  loop through all matching data rows
    // !       assert(Copy(Customer.AccountNumber,1,8)='AW000001');
    // !     ReleaseRows;
    // !   end;
    // ! end;
    function Step(SeekFirst: boolean=false): boolean;
    /// release cursor memory and resources once Step loop is finished
    // - this method call is optional, but is better be used if the ISQLDBRows
    // statement from taken from cache, and returned a lot of content which
    // may still be in client (and server) memory
    // - will also free all temporary memory used for optional logging
    procedure ReleaseRows;

    /// the column/field count of the current Row
    function ColumnCount: integer;
    /// the Column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
    // - it's up to the implementation to ensure than all column names are unique
    function ColumnName(Col: integer): RawUTF8;
................................................................................
    // - this default property can be used to write simple code like this:
    // ! 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']));
    // !   I.ReleaseRows;
    // ! end;
    // - of course, using a variant and a column name will be a bit slower than
    // direct access via the Column*() dedicated methods, but resulting code
    // is fast in practice
    property Column[const ColName: RawUTF8]: Variant read GetColumnVariant; default;
    {$ifndef DELPHI5OROLDER}
    /// create a TSQLDBRowVariantType able to access any field content via late binding
................................................................................
    // - typical use is:
    // ! var Row: Variant;
    // ! (...)
    // !  with MyConnProps.Execute('select * from table where name=?',[aName]) do begin
    // !    Row := RowData;
    // !    while Step do
    // !      writeln(Row.FirstName,Row.BirthDate);
    // !    ReleaseRows;
    // !  end;
    function RowData: Variant;
    /// create a TDocVariant custom variant containing all columns values
    // - will create a "fast" TDocVariant object instance with all fields
    procedure RowDocVariant(out aDocument: variant;
      aOptions: TDocVariantOptions=JSON_OPTIONS_FAST);
    {$endif DELPHI5OROLDER}
................................................................................
    // 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']));
    // !   I.ReleaseRows;
    // ! end;
    // - if RowsVariant is set, you can use it to row column access via late
    // binding, as such:
    // ! procedure WriteFamily(const aName: RawUTF8);
    // ! var R: Variant;
    // ! begin
    // !   with MyConnProps.Execute('select * from table where name=?',[aName],@R) do begin
    // !     while Step do
    // !       writeln(R.FirstName,' ',DateToStr(R.BirthDate));
    // !     ReleaseRows;
    // !   end;
    // ! end;
    // - you can any BLOB field to be returned as null with the ForceBlobAsNull
    // optional parameter
    function Execute(const aSQL: RawUTF8; const Params: array of const
      {$ifndef LVCL}{$ifndef DELPHI5OROLDER}; RowsVariant: PVariant=nil{$endif}{$endif};
      ForceBlobAsNull: boolean=false): ISQLDBRows;
    /// execute a SQL query, without returning any rows
................................................................................
    /// return a Column as a variant
    function GetColumnVariant(const ColName: RawUTF8): Variant;
    {$endif}
    /// return the associated statement instance for a ISQLDBRows interface
    function Instance: TSQLDBStatement;
    /// a wrapper to compute sllSQL context and start a local timer
    function GetSQLLog(var timer: TPrecisionTimer; SQL: PRawUTF8 = nil): TSynLog;








  public
    /// create a statement instance
    constructor Create(aConnection: TSQLDBConnection); virtual;

    /// bind a NULL value to a parameter
    // - the leftmost SQL parameter has an index of 1
    // - some providers (e.g. OleDB during MULTI INSERT statements) expect the
................................................................................
    // already connected
    procedure Prepare(const aSQL: RawUTF8; ExpectResults: Boolean); overload; virtual;
    /// Execute a prepared SQL statement
    // - parameters marked as ? should have been already bound with Bind*() functions
    // - should raise an Exception on any error
    // - this void default implementation will call set fConnection.fLastAccess
    procedure ExecutePrepared; virtual;
    /// release cursor memory and resources once Step loop is finished
    // - this method call is optional, but is better be used if the ISQLDBRows
    // statement from taken from cache, and returned a lot of content which
    // may still be in client (and server) memory
    // - override to free cursor memory when ISQLDBStatement is back in cache
    procedure ReleaseRows; virtual;
    /// Reset the previous prepared statement
    // - some drivers expect an explicit reset before binding parameters and
    // executing the statement another time
    // - this default implementation will just do nothing
    procedure Reset; virtual;
    /// Prepare and Execute an UTF-8 encoded SQL statement
    // - parameters marked as ? should have been already bound with Bind*()
................................................................................
    // ! var Query: ISQLDBStatement;
    // ! begin
    // !   Query := Props.NewThreadSafeStatementPrepared('select AccountNumber from Sales.Customer where AccountNumber like ?', ['AW000001%'],true);
    // !   if Query<>nil then begin
    // !     assert(SameTextU(Query.ColumnName(0),'AccountNumber'));
    // !     while Query.Step do //  loop through all matching data rows
    // !       assert(Copy(Query.ColumnUTF8(0),1,8)='AW000001');
    // !     Query.ReleaseRows;
    // !   end;
    // ! end;
    function Step(SeekFirst: boolean=false): boolean; virtual; abstract;
    /// the column/field count of the current Row
    function ColumnCount: integer;
    /// the Column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
................................................................................
    // - typical use is:
    // ! var Row: Variant;
    // ! (...)
    // !  with MyConnProps.Execute('select * from table where name=?',[aName]) do begin
    // !    Row := RowDaa;
    // !    while Step do
    // !      writeln(Row.FirstName,Row.BirthDate);
    // !    ReleaseRows;
    // !  end;
    function RowData: Variant; virtual;
    /// create a TDocVariant custom variant containing all columns values
    // - will create a "fast" TDocVariant object instance with all fields
    procedure RowDocVariant(out aDocument: variant;
      aOptions: TDocVariantOptions=JSON_OPTIONS_FAST); virtual;
    {$endif}
................................................................................
          Properties.GetTableNames(Tables);
          if FindRawUTF8(Tables,TableName,false)<0 then
            with Properties do
              ExecuteNoResult(SQLCreate(aTableName,Fields,false),[]);
          Ins := NewStatement;
          Ins.Prepare(SQL,false);
        end;
        Rows.ReleaseRows;
        // write row data
        Ins.BindFromRows(ColumnForcedTypes,Rows);
        Ins.ExecutePrepared;
        Ins.Reset;
        inc(result);
      end;
      if WithinTransaction then
................................................................................
      W.Add('[');
    // write rows data
    while Step do begin
      ColumnsToJSON(W);
      W.Add(',');
      inc(result);
    end;
    ReleaseRows;
    if (result=0) and W.Expand then begin
      // we want the field names at least, even with no data (RowCount=0)
      W.Expand := false; //  {"FieldCount":2,"Values":["col1","col2"]}
      W.CancelAll;
      for col := 0 to ColumnCount-1 do
        W.ColNames[col] := ColumnName(col); // previous W.AddColumns did add ""
      W.AddColumns;
................................................................................
        end;
        if F=FMax then
          W.AddCR else
          W.Add(CommaSep);
      end;
      inc(result);
    end;
    ReleaseRows;
    W.FlushFinal;
  finally
    W.Free;
  end;
end;

function TSQLDBStatement.FetchAllAsJSON(Expanded: boolean;
................................................................................
          W.Write1(0); // = W.WriteVarUInt32(0)
        // then write data values
        ColumnsToBinary(W,pointer(Null),ColTypes);
        inc(result);
        if (MaxRowCount>0) and (result>=MaxRowCount) then
          break;
      until not Step;
      ReleaseRows;
    end;
    W.Write(@result,SizeOf(result)); // fixed size at the end for row count
    W.Flush;
  finally
    W.Free;
  end;
end;
................................................................................
end;

function TSQLDBStatement.Instance: TSQLDBStatement;
begin
  Result := Self;
end;








function TSQLDBStatement.GetSQLLog(var timer: TPrecisionTimer; SQL: PRawUTF8): TSynLog;
var level: TSynLogInfo;
begin
  result := SynDBLog.Add;
  if result = nil then
    exit;
  if SQL = nil then
................................................................................

procedure TSQLDBStatement.Reset;
begin
  fSQLWithInlinedParams := '';
  // a do-nothing default method (used e.g. for OCI)
end;

procedure TSQLDBStatement.ReleaseRows;
begin
  fSQLWithInlinedParams := '';
end;

function TSQLDBStatement.ColumnsToSQLInsert(const TableName: RawUTF8;
  var Fields: TSQLDBColumnCreateDynArray): RawUTF8;
var F,size: integer;

Changes to SynDBDataset.pas.

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
...
180
181
182
183
184
185
186
187
188
189
190
191
192
193


194
195
196
197
198
199
200
...
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    // - if fDatasetSupportBatchBinding=true, should use array DML binding
    // - SQL Parameter to bind is aParam
    procedure DataSetBindSQLParam(const aArrayIndex, aParamIndex: integer;
      const aParam: TSQLDBParam); virtual; abstract;
    /// set the returned parameter after a stored proc execution
    procedure DataSetOutSQLParam(const aParamIndex: integer;
      var aParam: TSQLDBParam); virtual; abstract;
    /// close the associated TQuery when ISQLDBStatement is back in cache
    procedure ReleaseResources; override;
  public
    /// create a statement instance
    constructor Create(aConnection: TSQLDBConnection); override;
    /// release the prepared statement
    destructor Destroy; override;

    /// Prepare an UTF-8 encoded SQL statement
................................................................................
    // - raise an ESQLDBDataset on any error
    procedure ExecutePrepared; override;
    /// Reset the previous prepared statement
    // - this overridden implementation will reset all bindings and the cursor state
    // - raise an ESQLDBDataset on any error
    procedure Reset; override;

    /// Access the next or first row of data from the SQL Statement result
    // - return true on success, with data ready to be retrieved by Column*() methods
    // - return false if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - if SeekFirst is TRUE, will put the cursor on the first row of results
    // - raise an ESQLDBDataset on any error
    function Step(SeekFirst: boolean = false): boolean; override;


    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: Integer): Int64; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: Integer): boolean; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: Integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
  end else
    fCurrentRow := 1;
  result := not fQuery.Eof;
end;

procedure TSQLDBDatasetStatementAbstract.Reset;
begin
  ReleaseResources;
  inherited Reset;
end;

procedure TSQLDBDatasetStatementAbstract.ReleaseResources;
begin
  if (fQuery<>nil) and fQuery.Active then
    fQuery.Close;
  inherited ReleaseResources;
end;

function TSQLDBDatasetStatementAbstract.SQLParamTypeToDBParamType(IO: TSQLDBParamInOutType): TParamType;
begin
  case IO of
    paramIn:    result := ptInput;
    paramOut:   result := ptOutput;






<
<







 







|






>
>







 







|



|



|







152
153
154
155
156
157
158


159
160
161
162
163
164
165
...
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
...
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    // - if fDatasetSupportBatchBinding=true, should use array DML binding
    // - SQL Parameter to bind is aParam
    procedure DataSetBindSQLParam(const aArrayIndex, aParamIndex: integer;
      const aParam: TSQLDBParam); virtual; abstract;
    /// set the returned parameter after a stored proc execution
    procedure DataSetOutSQLParam(const aParamIndex: integer;
      var aParam: TSQLDBParam); virtual; abstract;


  public
    /// create a statement instance
    constructor Create(aConnection: TSQLDBConnection); override;
    /// release the prepared statement
    destructor Destroy; override;

    /// Prepare an UTF-8 encoded SQL statement
................................................................................
    // - raise an ESQLDBDataset on any error
    procedure ExecutePrepared; override;
    /// Reset the previous prepared statement
    // - this overridden implementation will reset all bindings and the cursor state
    // - raise an ESQLDBDataset on any error
    procedure Reset; override;

    /// access the next or first row of data from the SQL Statement result
    // - return true on success, with data ready to be retrieved by Column*() methods
    // - return false if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - if SeekFirst is TRUE, will put the cursor on the first row of results
    // - raise an ESQLDBDataset on any error
    function Step(SeekFirst: boolean = false): boolean; override;
    /// close the associated TQuery when ISQLDBStatement is back in cache
    procedure ReleaseRows; override;
    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: Integer): Int64; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: Integer): boolean; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: Integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
  end else
    fCurrentRow := 1;
  result := not fQuery.Eof;
end;

procedure TSQLDBDatasetStatementAbstract.Reset;
begin
  ReleaseRows;
  inherited Reset;
end;

procedure TSQLDBDatasetStatementAbstract.ReleaseRows;
begin
  if (fQuery<>nil) and fQuery.Active then
    fQuery.Close;
  inherited ReleaseRows;
end;

function TSQLDBDatasetStatementAbstract.SQLParamTypeToDBParamType(IO: TSQLDBParamInOutType): TParamType;
begin
  case IO of
    paramIn:    result := ptInput;
    paramOut:   result := ptOutput;

Changes to SynDBODBC.pas.

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
...
257
258
259
260
261
262
263


264
265
266
267
268
269
270
....
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
....
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
    fSQLW: RawUnicode;
    procedure AllocStatement;
    procedure DeallocStatement;
    procedure BindColumns;
    procedure GetData(var Col: TSQLDBColumnProperty; ColIndex: integer);
    function GetCol(Col: integer; ExpectedType: TSQLDBFieldType): TSQLDBStatementGetCol;
    function MoreResults: boolean;
    procedure ReleaseResources; 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;
................................................................................
    // - return FALSE if no more row is available (e.g. if the SQL statement
    //  is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    //   if SeekFirst is TRUE, will put the cursor on the first row of results,
    //   otherwise, it will fetch one row of data, to be called within a loop
    // - raise an EODBCException or ESQLDBException exception on any error
    function Step(SeekFirst: boolean=false): boolean; override;


    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: integer): boolean; override;
    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: integer): Int64; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: integer): double; override;
    /// return a Column floating point value of the current Row, first Col is 0
................................................................................

procedure TODBCStatement.BindColumns;
var nCols, NameLength, DataType, DecimalDigits, Nullable: SqlSmallint;
    ColumnSize: SqlULen;
    c, siz: integer;
    Name: array[byte] of WideChar;
begin
  ReleaseResources;
  with ODBC do begin
    Check(nil,self,NumResultCols(fStatement,nCols),SQL_HANDLE_STMT,fStatement);
    SetLength(fColData,nCols);
    fColumn.Capacity := nCols;
    for c := 1 to nCols do begin
      Check(nil,self,DescribeColW(fStatement,c,Name,256,NameLength,DataType,ColumnSize,
        DecimalDigits,Nullable),SQL_HANDLE_STMT,fStatement);
................................................................................
  if log <> nil then
    log.Log(sllSQL, 'ExecutePrepared: % %', [timer.Stop, logsql], self);
end;

procedure TODBCStatement.Reset;
begin
  if fStatement<>nil then begin
    ReleaseResources;
    if fParamCount>0 then
      ODBC.Check(nil,self,ODBC.FreeStmt(fStatement,SQL_RESET_PARAMS),SQL_HANDLE_STMT,fStatement);
  end;
  inherited Reset;
end;

procedure TODBCStatement.ReleaseResources;
begin
  fColData := nil;
  if fColumnCount>0 then begin
    if fStatement<>nil then
      ODBC.CloseCursor(fStatement); // no check needed
    fColumn.Clear;
    fColumn.ReHash;
  end;
  inherited ReleaseResources;
end;

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






<







 







>
>







 







|







 







|






|








|







218
219
220
221
222
223
224

225
226
227
228
229
230
231
...
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
....
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
....
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
    fSQLW: RawUnicode;
    procedure AllocStatement;
    procedure DeallocStatement;
    procedure BindColumns;
    procedure GetData(var Col: TSQLDBColumnProperty; ColIndex: integer);
    function GetCol(Col: integer; ExpectedType: TSQLDBFieldType): TSQLDBStatementGetCol;
    function MoreResults: boolean;

  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;
................................................................................
    // - return FALSE if no more row is available (e.g. if the SQL statement
    //  is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    //   if SeekFirst is TRUE, will put the cursor on the first row of results,
    //   otherwise, it will fetch one row of data, to be called within a loop
    // - raise an EODBCException or ESQLDBException exception on any error
    function Step(SeekFirst: boolean=false): boolean; override;
    /// close the ODBC statement cursor resources
    procedure ReleaseRows; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: integer): boolean; override;
    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: integer): Int64; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: integer): double; override;
    /// return a Column floating point value of the current Row, first Col is 0
................................................................................

procedure TODBCStatement.BindColumns;
var nCols, NameLength, DataType, DecimalDigits, Nullable: SqlSmallint;
    ColumnSize: SqlULen;
    c, siz: integer;
    Name: array[byte] of WideChar;
begin
  ReleaseRows;
  with ODBC do begin
    Check(nil,self,NumResultCols(fStatement,nCols),SQL_HANDLE_STMT,fStatement);
    SetLength(fColData,nCols);
    fColumn.Capacity := nCols;
    for c := 1 to nCols do begin
      Check(nil,self,DescribeColW(fStatement,c,Name,256,NameLength,DataType,ColumnSize,
        DecimalDigits,Nullable),SQL_HANDLE_STMT,fStatement);
................................................................................
  if log <> nil then
    log.Log(sllSQL, 'ExecutePrepared: % %', [timer.Stop, logsql], self);
end;

procedure TODBCStatement.Reset;
begin
  if fStatement<>nil then begin
    ReleaseRows;
    if fParamCount>0 then
      ODBC.Check(nil,self,ODBC.FreeStmt(fStatement,SQL_RESET_PARAMS),SQL_HANDLE_STMT,fStatement);
  end;
  inherited Reset;
end;

procedure TODBCStatement.ReleaseRows;
begin
  fColData := nil;
  if fColumnCount>0 then begin
    if fStatement<>nil then
      ODBC.CloseCursor(fStatement); // no check needed
    fColumn.Clear;
    fColumn.ReHash;
  end;
  inherited ReleaseRows;
end;

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

Changes to SynDBOracle.pas.

283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
...
326
327
328
329
330
331
332


333
334
335
336
337
338
339
....
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
    fBoundCursor: array of pointer;
    fInternalBufferSize: cardinal;
    // warning: shall be 32 bits aligned!
    fTimeElapsed: TPrecisionTimer;
    fUseServerSideStatementCache: boolean;
    function DateTimeToDescriptor(aDateTime: TDateTime): pointer;
    procedure FreeHandles(AfterError: boolean);
    procedure ReleaseResources; override;
    procedure FetchTest(Status: integer);
    /// Col=0...fColumnCount-1
    function GetCol(Col: Integer; out Column: PSQLDBColumnProperty): pointer;
    // called by Prepare and CreateFromExistingStatement
    procedure SetColumnsForPreparedStatement;
    // called by Step and CreateFromExistingStatement
    procedure FetchRows;
................................................................................
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - raise an ESQLDBOracle on any error
    function Step(SeekFirst: boolean=false): boolean; override;


    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: integer): boolean; override;
    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: integer): Int64; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
  if Frac(aDateTime)=0 then begin
    HH := 0; MM := 0; SS := 0;
  end else
    DecodeTime(aDateTime,HH,MM,SS,MS);
  OCI.Check(nil,nil,OCI.DateTimeConstruct(env,fError,result,Y,M,D,HH,MM,SS,0,nil,0),fError);
end;

procedure TSQLDBOracleStatement.ReleaseResources;
begin // not implemented yet
  inherited ReleaseResources;
end;

procedure TSQLDBOracleStatement.FreeHandles(AfterError: boolean);
const // see http://gcov.php.net/PHP_5_3/lcov_html/ext/oci8/oci8_statement.c.gcov.php
  RELEASE_MODE: array[boolean] of integer = (OCI_DEFAULT,OCI_STMTCACHE_DELETE);
var i,j: integer;
    P: PPointer;






<







 







>
>







 







|

|







283
284
285
286
287
288
289

290
291
292
293
294
295
296
...
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
....
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
    fBoundCursor: array of pointer;
    fInternalBufferSize: cardinal;
    // warning: shall be 32 bits aligned!
    fTimeElapsed: TPrecisionTimer;
    fUseServerSideStatementCache: boolean;
    function DateTimeToDescriptor(aDateTime: TDateTime): pointer;
    procedure FreeHandles(AfterError: boolean);

    procedure FetchTest(Status: integer);
    /// Col=0...fColumnCount-1
    function GetCol(Col: Integer; out Column: PSQLDBColumnProperty): pointer;
    // called by Prepare and CreateFromExistingStatement
    procedure SetColumnsForPreparedStatement;
    // called by Step and CreateFromExistingStatement
    procedure FetchRows;
................................................................................
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - raise an ESQLDBOracle on any error
    function Step(SeekFirst: boolean=false): boolean; override;
    /// finalize the OCI cursor resources - not implemented yet
    procedure ReleaseRows; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: integer): boolean; override;
    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: integer): Int64; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
  if Frac(aDateTime)=0 then begin
    HH := 0; MM := 0; SS := 0;
  end else
    DecodeTime(aDateTime,HH,MM,SS,MS);
  OCI.Check(nil,nil,OCI.DateTimeConstruct(env,fError,result,Y,M,D,HH,MM,SS,0,nil,0),fError);
end;

procedure TSQLDBOracleStatement.ReleaseRows;
begin // not implemented yet
  inherited ReleaseRows;
end;

procedure TSQLDBOracleStatement.FreeHandles(AfterError: boolean);
const // see http://gcov.php.net/PHP_5_3/lcov_html/ext/oci8/oci8_statement.c.gcov.php
  RELEASE_MODE: array[boolean] of integer = (OCI_DEFAULT,OCI_STMTCACHE_DELETE);
var i,j: integer;
    P: PPointer;

Changes to SynDBPostgres.pas.

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
...
176
177
178
179
180
181
182


183
184
185
186
187
188
189
...
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906










907
908
909
910
911
912
913
    // non zero for binary params
    fPGparamLengths: TIntegerDynArray;
    /// define the result columns name and content
    procedure BindColumns;
    /// raise an exception if Col is out of range according to fColumnCount
    // or rowset is not initialized
    procedure CheckColAndRowset(const Col: integer);
    /// Clear(fRes) when ISQLDBStatement is back in cache
    procedure ReleaseResources; override;
  public
    /// finalize the statement for a given connection
    destructor Destroy; override;
    /// Prepare an UTF-8 encoded SQL statement
    // - parameters marked as ? will be bound later, before ExecutePrepared call
    // - if ExpectResults is TRUE, then Step() and Column*() methods are available
    // to retrieve the data rows
................................................................................
    /// Access the next or first row of data from the SQL Statement result
    // - return true on success, with data ready to be retrieved by Column*() methods
    // - return false if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - if SeekFirst is TRUE, will put the cursor on the first row of results
    // - raise an ESQLDBPostgres on any error
    function Step(SeekFirst: boolean = False): boolean; override;


    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: integer): int64; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: integer): boolean; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
function TSQLDBPostgresStatement.UpdateCount: integer;
begin
  result := GetCardinalDef(PQ.cmdTuples(fRes), 0);
end;

procedure TSQLDBPostgresStatement.Reset;
begin
  ReleaseResources;
  fResStatus := PGRES_EMPTY_QUERY;
  inherited Reset;
end;

procedure TSQLDBPostgresStatement.ReleaseResources;
begin
  if fRes = nil then
    exit;
  PQ.clear(fRes);
  fRes := nil;
  inherited ReleaseResources;
end;

function TSQLDBPostgresStatement.Step(SeekFirst: boolean): boolean;
begin
  if (fRes = nil) or (fResStatus <> PGRES_TUPLES_OK) then
    raise ESQLDBPostgres.CreateUTF8('%.Execute should be called before Step', [self]);
  if SeekFirst then
    fCurrentRow := -1;
  result := fCurrentRow + 1 < fTotalRowsRetrieved;
  if not result then
    exit;
  inc(fCurrentRow);
end;











function TSQLDBPostgresStatement.ColumnInt(Col: integer): int64;
begin
  CheckColAndRowset(Col);
  result := GetInt64(PQ.GetValue(fRes, fCurrentRow, Col));
end;







<
<







 







>
>







 







|




<
<
<
<
<
<
<
<
<











>
>
>
>
>
>
>
>
>
>







144
145
146
147
148
149
150


151
152
153
154
155
156
157
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
...
875
876
877
878
879
880
881
882
883
884
885
886









887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
    // non zero for binary params
    fPGparamLengths: TIntegerDynArray;
    /// define the result columns name and content
    procedure BindColumns;
    /// raise an exception if Col is out of range according to fColumnCount
    // or rowset is not initialized
    procedure CheckColAndRowset(const Col: integer);


  public
    /// finalize the statement for a given connection
    destructor Destroy; override;
    /// Prepare an UTF-8 encoded SQL statement
    // - parameters marked as ? will be bound later, before ExecutePrepared call
    // - if ExpectResults is TRUE, then Step() and Column*() methods are available
    // to retrieve the data rows
................................................................................
    /// Access the next or first row of data from the SQL Statement result
    // - return true on success, with data ready to be retrieved by Column*() methods
    // - return false if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - if SeekFirst is TRUE, will put the cursor on the first row of results
    // - raise an ESQLDBPostgres on any error
    function Step(SeekFirst: boolean = False): boolean; override;
    /// clear(fRes) when ISQLDBStatement is back in cache
    procedure ReleaseRows; override;
    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: integer): int64; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: integer): boolean; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
function TSQLDBPostgresStatement.UpdateCount: integer;
begin
  result := GetCardinalDef(PQ.cmdTuples(fRes), 0);
end;

procedure TSQLDBPostgresStatement.Reset;
begin
  ReleaseRows;
  fResStatus := PGRES_EMPTY_QUERY;
  inherited Reset;
end;










function TSQLDBPostgresStatement.Step(SeekFirst: boolean): boolean;
begin
  if (fRes = nil) or (fResStatus <> PGRES_TUPLES_OK) then
    raise ESQLDBPostgres.CreateUTF8('%.Execute should be called before Step', [self]);
  if SeekFirst then
    fCurrentRow := -1;
  result := fCurrentRow + 1 < fTotalRowsRetrieved;
  if not result then
    exit;
  inc(fCurrentRow);
end;

procedure TSQLDBPostgresStatement.ReleaseRows;
begin
  if fRes <> nil then
  begin
    PQ.clear(fRes);
    fRes := nil;
  end;
  inherited ReleaseRows;
end;

function TSQLDBPostgresStatement.ColumnInt(Col: integer): int64;
begin
  CheckColAndRowset(Col);
  result := GetInt64(PQ.GetValue(fRes, fCurrentRow, Col));
end;

Changes to SynDBSQLite3.pas.

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
...
262
263
264
265
266
267
268


269
270
271
272
273
274
275
...
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
    fStatement: TSQLRequest;
    fShouldLogSQL: boolean; // sllSQL in SynDBLog.Level -> set fLogSQLValues[]
    fLogSQLValues: TVariantDynArray;
    fUpdateCount: integer;
    // retrieve the inlined value of a given parameter, e.g. 1 or 'name'
    procedure AddParamValueAsText(Param: integer; Dest: TTextWriter;
      MaxCharCount: integer); override;
    procedure ReleaseResources; override;
  public
    /// create a SQLite3 statement instance, from an existing SQLite3 connection
    // - the Execute method can be called once per TSQLDBSQLite3Statement 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;
................................................................................
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - raise an ESQLite3Exception exception on any error
    function Step(SeekFirst: boolean=false): boolean; override;


    /// retrieve a column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
    // - it's up to the implementation to ensure than all column names are unique
    function ColumnName(Col: integer): RawUTF8; override;
    /// returns the Column index of a given Column name
    // - Columns numeration (i.e. Col value) starts with 0
    // - returns -1 if the Column name is not found (via case insensitive search)
................................................................................
  fUpdateCount := 0;
  // fStatement.BindReset; // slow down the process, and is not mandatory
  VariantDynArrayClear(fLogSQLValues);
  SetLength(fLogSQLValues,fParamCount);
  inherited Reset;
end;

procedure TSQLDBSQLite3Statement.ReleaseResources;
begin
  VariantDynArrayClear(fLogSQLValues);
  inherited ReleaseResources;
end;

function TSQLDBSQLite3Statement.Step(SeekFirst: boolean): boolean;
begin
  if SeekFirst then begin
    if fCurrentRow>0 then
      raise ESQLDBException.CreateUTF8('%.Step(SeekFirst=true) not implemented',[self]);






<







 







>
>







 







|


|







182
183
184
185
186
187
188

189
190
191
192
193
194
195
...
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
...
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
    fStatement: TSQLRequest;
    fShouldLogSQL: boolean; // sllSQL in SynDBLog.Level -> set fLogSQLValues[]
    fLogSQLValues: TVariantDynArray;
    fUpdateCount: integer;
    // retrieve the inlined value of a given parameter, e.g. 1 or 'name'
    procedure AddParamValueAsText(Param: integer; Dest: TTextWriter;
      MaxCharCount: integer); override;

  public
    /// create a SQLite3 statement instance, from an existing SQLite3 connection
    // - the Execute method can be called once per TSQLDBSQLite3Statement 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;
................................................................................
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - raise an ESQLite3Exception exception on any error
    function Step(SeekFirst: boolean=false): boolean; override;
    /// finalize the cursor
    procedure ReleaseRows; override;
    /// retrieve a column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
    // - it's up to the implementation to ensure than all column names are unique
    function ColumnName(Col: integer): RawUTF8; override;
    /// returns the Column index of a given Column name
    // - Columns numeration (i.e. Col value) starts with 0
    // - returns -1 if the Column name is not found (via case insensitive search)
................................................................................
  fUpdateCount := 0;
  // fStatement.BindReset; // slow down the process, and is not mandatory
  VariantDynArrayClear(fLogSQLValues);
  SetLength(fLogSQLValues,fParamCount);
  inherited Reset;
end;

procedure TSQLDBSQLite3Statement.ReleaseRows;
begin
  VariantDynArrayClear(fLogSQLValues);
  inherited ReleaseRows;
end;

function TSQLDBSQLite3Statement.Step(SeekFirst: boolean): boolean;
begin
  if SeekFirst then begin
    if fCurrentRow>0 then
      raise ESQLDBException.CreateUTF8('%.Step(SeekFirst=true) not implemented',[self]);

Changes to SynDBZeos.pas.

322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
...
360
361
362
363
364
365
366


367
368
369
370
371
372
373
....
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
  /// implements a statement via a ZEOS database connection
  TSQLDBZEOSStatement = class(TSQLDBStatementWithParamsAndColumns)
  protected
    fStatement: IZPreparedStatement;
    fResultSet: IZResultSet;
    fResultInfo: IZResultSetMetaData;
    /// set fResultSet/fResultInfo=nil when ISQLDBStatement is back in cache
    procedure ReleaseResources; override;
  public
    /// Prepare an UTF-8 encoded SQL statement
    // - parameters marked as ? will be bound later, before ExecutePrepared call
    // - if ExpectResults is TRUE, then Step() and Column*() methods are available
    // to retrieve the data rows
    // - raise an ESQLDBZeos on any error
    procedure Prepare(const aSQL: RawUTF8; ExpectResults: boolean = false); overload; override;
................................................................................
    /// Access the next or first row of data from the SQL Statement result
    // - return true on success, with data ready to be retrieved by Column*() methods
    // - return false if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - if SeekFirst is TRUE, will put the cursor on the first row of results
    // - raise an ESQLDBZeos on any error
    function Step(SeekFirst: boolean = false): boolean; override;


    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: Integer): Int64; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: Integer): boolean; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: Integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
  {$endif}
  if log <> nil then
    log.Log(sllSQL, 'ExecutePrepared: % %', [timer.Stop, logsql], self);
end;

procedure TSQLDBZEOSStatement.Reset;
begin
  ReleaseResources;
  if fStatement<>nil then
    fStatement.ClearParameters;
  inherited Reset;
end;

procedure TSQLDBZEOSStatement.ReleaseResources;
begin
  if fResultSet<>nil then begin
    fResultInfo := nil;
    fResultSet := nil;
  end;
  inherited ReleaseResources;
end;

function TSQLDBZEOSStatement.Step(SeekFirst: boolean): boolean;
begin
  if fColumnCount=0 then // no row returned
    result := false else
  if fResultSet=nil then






<
<







 







>
>







 







|





|





|







322
323
324
325
326
327
328


329
330
331
332
333
334
335
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
....
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
  /// implements a statement via a ZEOS database connection
  TSQLDBZEOSStatement = class(TSQLDBStatementWithParamsAndColumns)
  protected
    fStatement: IZPreparedStatement;
    fResultSet: IZResultSet;
    fResultInfo: IZResultSetMetaData;


  public
    /// Prepare an UTF-8 encoded SQL statement
    // - parameters marked as ? will be bound later, before ExecutePrepared call
    // - if ExpectResults is TRUE, then Step() and Column*() methods are available
    // to retrieve the data rows
    // - raise an ESQLDBZeos on any error
    procedure Prepare(const aSQL: RawUTF8; ExpectResults: boolean = false); overload; override;
................................................................................
    /// Access the next or first row of data from the SQL Statement result
    // - return true on success, with data ready to be retrieved by Column*() methods
    // - return false if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - if SeekFirst is TRUE, will put the cursor on the first row of results
    // - raise an ESQLDBZeos on any error
    function Step(SeekFirst: boolean = false): boolean; override;
    /// free IZResultSet/IZResultSetMetaData when ISQLDBStatement is back in cache
    procedure ReleaseRows; override;
    /// return a Column integer value of the current Row, first Col is 0
    function ColumnInt(Col: Integer): Int64; override;
    /// returns TRUE if the column contains NULL
    function ColumnNull(Col: Integer): boolean; override;
    /// return a Column floating point value of the current Row, first Col is 0
    function ColumnDouble(Col: Integer): double; override;
    /// return a Column date and time value of the current Row, first Col is 0
................................................................................
  {$endif}
  if log <> nil then
    log.Log(sllSQL, 'ExecutePrepared: % %', [timer.Stop, logsql], self);
end;

procedure TSQLDBZEOSStatement.Reset;
begin
  ReleaseRows;
  if fStatement<>nil then
    fStatement.ClearParameters;
  inherited Reset;
end;

procedure TSQLDBZEOSStatement.ReleaseRows;
begin
  if fResultSet<>nil then begin
    fResultInfo := nil;
    fResultSet := nil;
  end;
  inherited ReleaseRows;
end;

function TSQLDBZEOSStatement.Step(SeekFirst: boolean): boolean;
begin
  if fColumnCount=0 then // no row returned
    result := false else
  if fResultSet=nil then

Changes to SynOleDB.pas.

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
....
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097


1098
1099
1100
1101
1102
1103
1104
....
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
....
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
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
    // - set Column to the corresponding fColumns[] item
    // - return a pointer to status-data[-length] in fRowSetData[], or
    // nil if status states this column is NULL
    function GetCol(Col: integer; out Column: PSQLDBColumnProperty): pointer;
    procedure GetCol64(Col: integer; DestType: TSQLDBFieldType; var Dest);
      {$ifdef HASINLINE}inline;{$endif}
    procedure FlushRowSetData;
    procedure ReleaseRows;
    procedure CloseRowSet;
    ///  retrieve column information, and initialize Bindings[]
    // - add the high-level column information in Column[], initializes
    // OleDB Bindings array and returns the row size (in bytes)
    function BindColumns(ColumnInfo: IColumnsInfo; var Column: TDynArrayHashed;
      out Bindings: TDBBindingDynArray): integer;
    procedure LogStatusError(Status: integer; Column: PSQLDBColumnProperty);
    /// clear result rows when ISQLDBStatement is back in cache
    procedure ReleaseResources; override;
  public
    /// create an OleDB statement instance, from an OleDB connection
    // - the Execute method can be called only once per TOleDBStatement instance
    // - if the supplied connection is not of TOleDBConnection type, will raise
    // an exception
    constructor Create(aConnection: TSQLDBConnection); override;
    /// release all associated memory and COM objects
................................................................................
    // - the leftmost SQL parameter has an index of 1
    // - to be used e.g. with stored procedures
    // - any TEXT parameter will be retrieved as WideString Variant (i.e. as
    // stored in TOleDBStatementParam)
    function ParamToVariant(Param: Integer; var Value: Variant;
      CheckIsOutParameter: boolean=true): TSQLDBFieldType; override;

    /// After a statement has been prepared via Prepare() + ExecutePrepared() or
    // Execute(), this method must be called one or more times to evaluate it
    // - you shall call this method before calling any Column*() methods
    // - return TRUE on success, with data ready to be retrieved by Column*()
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - raise an ESQLEOleDBException on any error
    function Step(SeekFirst: boolean=false): boolean; override;


    /// retrieve a column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
    // - it's up to the implementation to ensure than all column names are unique
    function ColumnName(Col: integer): RawUTF8; override;
    /// returns the Column index of a given Column name
    // - Columns numeration (i.e. Col value) starts with 0
    // - returns -1 if the Column name is not found (via case insensitive search)
................................................................................
      pointer(fColumnBindings),fRowSize,fRowSetAccessor,pointer(Status)),Status);
    fRowStepHandleRetrieved := 0;
    fRowStepHandleCurrent := 0;
    fRowStepResult := 0;
  end else
  if SeekFirst then begin
    // rewind to first row
    ReleaseRows;
    OleDBConnection.OleDBCheck(self,fRowSet.RestartPosition(DB_NULL_HCHAPTER));
    fRowStepResult := 0;
  end else
    FlushRowSetData;
  if fRowStepHandleCurrent>=fRowStepHandleRetrieved then begin
    ReleaseRows;
    if fRowStepResult=DB_S_ENDOFROWSET then
      exit; // no more row available -> return false
    fRowStepResult := fRowSet.GetNextRows(DB_NULL_HCHAPTER,0,length(fRowStepHandles),
      fRowStepHandleRetrieved,pointer(fRowStepHandles));
    OleDBConnection.OleDBCheck(self,fRowStepResult);
    fRowStepHandleCurrent := 0;
    if fRowStepHandleRetrieved=0 then
................................................................................
procedure TOleDBStatement.SetRowBufferSize(Value: integer);
begin
  if Value<4096 then
    Value := 4096;
  fRowBufferSize := Value;
end;

procedure TOleDBStatement.ReleaseRows;
begin
  FlushRowSetData;
  if fRowStepHandleRetrieved<>0 then begin
    fRowSet.ReleaseRows(fRowStepHandleRetrieved,Pointer(fRowStepHandles),nil,nil,nil);
    fRowStepHandleRetrieved := 0;
  end;
  fCurrentRow := 0;
end;

procedure TOleDBStatement.CloseRowSet;
begin
  if not Assigned(fRowSet) then
    exit;
  ReleaseRows;
  if fRowSetAccessor<>0 then begin
    (fRowSet as IAccessor).ReleaseAccessor(fRowSetAccessor,nil);
    fRowSetAccessor := 0;
  end;
  fRowSet := nil;
end;

procedure TOleDBStatement.Reset;
begin
  ReleaseResources;
  if fColumnCount>0 then begin
    fColumn.Clear;
    fColumn.ReHash;
    // faster if full command is re-prepared!
    fCommand := nil;
    Prepare(fSQL,fExpectResults);
  end;
  fUpdateCount := 0;
  inherited Reset;
end;


procedure TOleDBStatement.ReleaseResources;
begin
  if fParamCount>0 then
    fParam.Clear;
  fParamBindings := nil;
  CloseRowSet;
  fColumnBindings := nil;
  inherited ReleaseResources;
end;

function TOleDBStatement.UpdateCount: integer;
begin
  if not fExpectResults then
    result := fUpdateCount else
    result := 0;






|







<
<







 







|










>
>







 







|





|







 







|













|









|











<
|






|







958
959
960
961
962
963
964
965
966
967
968
969
970
971
972


973
974
975
976
977
978
979
....
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
....
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
....
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
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
    // - set Column to the corresponding fColumns[] item
    // - return a pointer to status-data[-length] in fRowSetData[], or
    // nil if status states this column is NULL
    function GetCol(Col: integer; out Column: PSQLDBColumnProperty): pointer;
    procedure GetCol64(Col: integer; DestType: TSQLDBFieldType; var Dest);
      {$ifdef HASINLINE}inline;{$endif}
    procedure FlushRowSetData;
    procedure ReleaseRowSetDataAndRows;
    procedure CloseRowSet;
    ///  retrieve column information, and initialize Bindings[]
    // - add the high-level column information in Column[], initializes
    // OleDB Bindings array and returns the row size (in bytes)
    function BindColumns(ColumnInfo: IColumnsInfo; var Column: TDynArrayHashed;
      out Bindings: TDBBindingDynArray): integer;
    procedure LogStatusError(Status: integer; Column: PSQLDBColumnProperty);


  public
    /// create an OleDB statement instance, from an OleDB connection
    // - the Execute method can be called only once per TOleDBStatement instance
    // - if the supplied connection is not of TOleDBConnection type, will raise
    // an exception
    constructor Create(aConnection: TSQLDBConnection); override;
    /// release all associated memory and COM objects
................................................................................
    // - the leftmost SQL parameter has an index of 1
    // - to be used e.g. with stored procedures
    // - any TEXT parameter will be retrieved as WideString Variant (i.e. as
    // stored in TOleDBStatementParam)
    function ParamToVariant(Param: Integer; var Value: Variant;
      CheckIsOutParameter: boolean=true): TSQLDBFieldType; override;

    /// after a statement has been prepared via Prepare() + ExecutePrepared() or
    // Execute(), this method must be called one or more times to evaluate it
    // - you shall call this method before calling any Column*() methods
    // - return TRUE on success, with data ready to be retrieved by Column*()
    // - return FALSE if no more row is available (e.g. if the SQL statement
    // is not a SELECT but an UPDATE or INSERT command)
    // - access the first or next row of data from the SQL Statement result:
    // if SeekFirst is TRUE, will put the cursor on the first row of results,
    // otherwise, it will fetch one row of data, to be called within a loop
    // - raise an ESQLEOleDBException on any error
    function Step(SeekFirst: boolean=false): boolean; override;
    /// clear result rowset when ISQLDBStatement is back in cache
    procedure ReleaseRows; override;
    /// retrieve a column name of the current Row
    // - Columns numeration (i.e. Col value) starts with 0
    // - it's up to the implementation to ensure than all column names are unique
    function ColumnName(Col: integer): RawUTF8; override;
    /// returns the Column index of a given Column name
    // - Columns numeration (i.e. Col value) starts with 0
    // - returns -1 if the Column name is not found (via case insensitive search)
................................................................................
      pointer(fColumnBindings),fRowSize,fRowSetAccessor,pointer(Status)),Status);
    fRowStepHandleRetrieved := 0;
    fRowStepHandleCurrent := 0;
    fRowStepResult := 0;
  end else
  if SeekFirst then begin
    // rewind to first row
    ReleaseRowSetDataAndRows;
    OleDBConnection.OleDBCheck(self,fRowSet.RestartPosition(DB_NULL_HCHAPTER));
    fRowStepResult := 0;
  end else
    FlushRowSetData;
  if fRowStepHandleCurrent>=fRowStepHandleRetrieved then begin
    ReleaseRowSetDataAndRows;
    if fRowStepResult=DB_S_ENDOFROWSET then
      exit; // no more row available -> return false
    fRowStepResult := fRowSet.GetNextRows(DB_NULL_HCHAPTER,0,length(fRowStepHandles),
      fRowStepHandleRetrieved,pointer(fRowStepHandles));
    OleDBConnection.OleDBCheck(self,fRowStepResult);
    fRowStepHandleCurrent := 0;
    if fRowStepHandleRetrieved=0 then
................................................................................
procedure TOleDBStatement.SetRowBufferSize(Value: integer);
begin
  if Value<4096 then
    Value := 4096;
  fRowBufferSize := Value;
end;

procedure TOleDBStatement.ReleaseRowSetDataAndRows;
begin
  FlushRowSetData;
  if fRowStepHandleRetrieved<>0 then begin
    fRowSet.ReleaseRows(fRowStepHandleRetrieved,Pointer(fRowStepHandles),nil,nil,nil);
    fRowStepHandleRetrieved := 0;
  end;
  fCurrentRow := 0;
end;

procedure TOleDBStatement.CloseRowSet;
begin
  if not Assigned(fRowSet) then
    exit;
  ReleaseRowSetDataAndRows;
  if fRowSetAccessor<>0 then begin
    (fRowSet as IAccessor).ReleaseAccessor(fRowSetAccessor,nil);
    fRowSetAccessor := 0;
  end;
  fRowSet := nil;
end;

procedure TOleDBStatement.Reset;
begin
  ReleaseRows;
  if fColumnCount>0 then begin
    fColumn.Clear;
    fColumn.ReHash;
    // faster if full command is re-prepared!
    fCommand := nil;
    Prepare(fSQL,fExpectResults);
  end;
  fUpdateCount := 0;
  inherited Reset;
end;


procedure TOleDBStatement.ReleaseRows;
begin
  if fParamCount>0 then
    fParam.Clear;
  fParamBindings := nil;
  CloseRowSet;
  fColumnBindings := nil;
  inherited ReleaseRows;
end;

function TOleDBStatement.UpdateCount: integer;
begin
  if not fExpectResults then
    result := fUpdateCount else
    result := 0;

Changes to SynSelfTests.pas.

15632
15633
15634
15635
15636
15637
15638
15639
15640
15641
15642
15643
15644
15645
15646
15647
15648
15649
15650
15651
15652

15653
15654
15655
15656
15657
15658
15659
    end;
    Check(n=DoCount);
    n1 := n;
    n := 0;
    {$ifndef LVCL}
    Row := res.RowData;
    {$endif}
    if res.Step(true) then
    repeat
      {$ifdef LVCL}
      Check(res.ColumnInt('ID')>0);
      Check(res.ColumnInt('YearOfDeath')=1519);
      {$else}
      Check(Row.ID>0);
      Check(Row.YearOfDeath=1519);
      res.RowDocVariant(RowDoc);
      Check(RowDoc.ID=Row.ID);
      Check(_Safe(RowDoc)^.I['YearOfDeath']=1519);
      {$endif}
      inc(n);
    until not res.Step;

    Check(n=n1);
    SetLength(IDs,50);
    FillIncreasing(pointer(IDs),50000,length(IDs));
    proxy.ThreadSafeConnection.StartTransaction;
    DoInsert;
    proxy.ThreadSafeConnection.Rollback;
    Check(DoCount=n);






|













>







15632
15633
15634
15635
15636
15637
15638
15639
15640
15641
15642
15643
15644
15645
15646
15647
15648
15649
15650
15651
15652
15653
15654
15655
15656
15657
15658
15659
15660
    end;
    Check(n=DoCount);
    n1 := n;
    n := 0;
    {$ifndef LVCL}
    Row := res.RowData;
    {$endif}
    if res.Step({rewind=}true) then
    repeat
      {$ifdef LVCL}
      Check(res.ColumnInt('ID')>0);
      Check(res.ColumnInt('YearOfDeath')=1519);
      {$else}
      Check(Row.ID>0);
      Check(Row.YearOfDeath=1519);
      res.RowDocVariant(RowDoc);
      Check(RowDoc.ID=Row.ID);
      Check(_Safe(RowDoc)^.I['YearOfDeath']=1519);
      {$endif}
      inc(n);
    until not res.Step;
    res.ReleaseRows;
    Check(n=n1);
    SetLength(IDs,50);
    FillIncreasing(pointer(IDs),50000,length(IDs));
    proxy.ThreadSafeConnection.StartTransaction;
    DoInsert;
    proxy.ThreadSafeConnection.Rollback;
    Check(DoCount=n);

Changes to SynopseCommit.inc.

1
'1.18.5874'
|
1
'1.18.5875'