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

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

Overview
Comment:added aUseBatchMode optional parameter to TSQLRecordMany.ManyDelete() method
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: c0c4838f266af1732b00c6552ab808b22f67423f
User & Date: ab 2012-08-20 07:48:24
Context
2012-08-20
13:00
  • fixed regression issue introduced with resolution of ticket [e5ad3684b2] about some .map files parsing in TSynMapFile: no source code line where added in the .log file any more
  • TSynLog stack tracing uses low-level RtlCaptureStackBackTrace() API on CPU64
check-in: 275c27a721 user: ab tags: trunk
07:48
added aUseBatchMode optional parameter to TSQLRecordMany.ManyDelete() method check-in: c0c4838f26 user: ab tags: trunk
2012-08-18
06:31
updated documentation check-in: 6fda439201 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/SQLite3Commons.pas.

644
645
646
647
648
649
650

651
652
653
654
655
656
657
....
4265
4266
4267
4268
4269
4270
4271



4272

4273
4274
4275
4276



4277

4278
4279
4280
4281
4282
4283
4284
.....
22800
22801
22802
22803
22804
22805
22806
22807

22808
22809
22810
22811
22812
22813
22814


22815
22816
22817
22818

22819
22820
22821
22822
22823
22824
22825
22826
22827
22828
22829
      parameters wrapper for both method callbacks and interface-based services:
      now aParams.Context.ID is to be used insted of aParams.ID 
    - added TJSONObjectDecoder record/object helper for JSON object decoding
      (used e.g. by GetJSONObjectAsSQL() function, and for SQlite3DB process)
    - removed TSQLRecordExternal class type, to allow any TSQLRecord (e.g.
      TSQLRecordMany) to be used with VirtualTableExternalRegister() - there was
      indeed no implementation requirement to force a specific class type

    - now JSON parser will handle #1..' ' chars as whitespace (not only ' ')
    - now huge service JSON response is truncated (to default 20 KB) in logs


  String usage in the Synopse SQLite3 database framework:
    - RawUTF8 is used for every internal data usage, since both SQLite3 and
      JSON do expect UTF-8 encoding
................................................................................
    // - source ID is taken from the fSourceID field (set by TSQLRecord.Create)
    // - note that if the Source record has just been added, fSourceID is not
    // set, so this method will fail: please call the other overloaded method
    function ManyAdd(aClient: TSQLRest; aDestID: Integer;
      NoDuplicates: boolean=false): boolean; overload;
    /// will delete the record associated with a particular Source/Dest pair
    // - will return TRUE if the pair was found and successfully deleted



    function ManyDelete(aClient: TSQLRest; aSourceID, aDestID: Integer): boolean; overload;

    /// will delete the record associated with the current source and a specified Dest
    // - source ID is taken from the fSourceID field (set by TSQLRecord.Create)
    // - note that if the Source record has just been added, fSourceID is not
    // set, so this method will fail: please call the other overloaded method



    function ManyDelete(aClient: TSQLRest; aDestID: Integer): boolean; overload;

    /// will retrieve the record associated with a particular Source/Dest pair
    // - will return TRUE if the pair was found
    // - in this case, all "through" columns are available in the TSQLRecordMany
    // field instance
    function ManySelect(aClient: TSQLRest; aSourceID, aDestID: Integer): boolean; overload;
    /// will retrieve the record associated with the current source and a specified Dest
    // - source ID is taken from the fSourceID field (set by TSQLRecord.Create)
................................................................................
begin
  if fSourceID=nil then
    result := false else // avoid GPF
    result := DestGet(aClient,fSourceID^,DestIDs);
   // fSourceID has been set by TSQLRecord.Create
end;

function TSQLRecordMany.ManyDelete(aClient: TSQLRest; aSourceID, aDestID: Integer): boolean;

var aID: integer;
begin
  result := false;
  if (self=nil) or (aClient=nil) or (aSourceID=0) or (aDestID=0) then
    exit;
  aID := InternalIDFromSourceDest(aClient,aSourceID,aDestID);
  if aID<>0 then


    result := aClient.Delete(RecordClass,aID);
end;

function TSQLRecordMany.ManyDelete(aClient: TSQLRest; aDestID: Integer): boolean;

begin
  if fSourceID=nil then
    result := false else // avoid GPF
    result := ManyDelete(aClient,fSourceID^,aDestID);
end;

function TSQLRecordMany.ManySelect(aClient: TSQLRest; aSourceID, aDestID: Integer): boolean;
begin
  if (self=nil) or (aClient=nil) or (aSourceID=0) or (aDestID=0) then
    result := false else // invalid parameters
    result := aClient.Retrieve(FormatUTF8('Source=:(%): AND Dest=:(%):',






>







 







>
>
>
|
>




>
>
>
|
>







 







|
>







>
>
|


|
>



|







644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
....
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
.....
22809
22810
22811
22812
22813
22814
22815
22816
22817
22818
22819
22820
22821
22822
22823
22824
22825
22826
22827
22828
22829
22830
22831
22832
22833
22834
22835
22836
22837
22838
22839
22840
22841
22842
      parameters wrapper for both method callbacks and interface-based services:
      now aParams.Context.ID is to be used insted of aParams.ID 
    - added TJSONObjectDecoder record/object helper for JSON object decoding
      (used e.g. by GetJSONObjectAsSQL() function, and for SQlite3DB process)
    - removed TSQLRecordExternal class type, to allow any TSQLRecord (e.g.
      TSQLRecordMany) to be used with VirtualTableExternalRegister() - there was
      indeed no implementation requirement to force a specific class type
    - added aUseBatchMode optional parameter to TSQLRecordMany.ManyDelete() method
    - now JSON parser will handle #1..' ' chars as whitespace (not only ' ')
    - now huge service JSON response is truncated (to default 20 KB) in logs


  String usage in the Synopse SQLite3 database framework:
    - RawUTF8 is used for every internal data usage, since both SQLite3 and
      JSON do expect UTF-8 encoding
................................................................................
    // - source ID is taken from the fSourceID field (set by TSQLRecord.Create)
    // - note that if the Source record has just been added, fSourceID is not
    // set, so this method will fail: please call the other overloaded method
    function ManyAdd(aClient: TSQLRest; aDestID: Integer;
      NoDuplicates: boolean=false): boolean; overload;
    /// will delete the record associated with a particular Source/Dest pair
    // - will return TRUE if the pair was found and successfully deleted
    // - if aUseBatchMode is TRUE, it will use aClient.BatchDelete() instead
    // of the slower aClient.Delete() method - but you shall call it within
    // a BatchStart / BatchSend block
    function ManyDelete(aClient: TSQLRest; aSourceID, aDestID: Integer;
      aUseBatchMode: boolean=false): boolean; overload;
    /// will delete the record associated with the current source and a specified Dest
    // - source ID is taken from the fSourceID field (set by TSQLRecord.Create)
    // - note that if the Source record has just been added, fSourceID is not
    // set, so this method will fail: please call the other overloaded method
    // - if aUseBatchMode is TRUE, it will use aClient.BatchDelete() instead
    // of the slower aClient.Delete() method - but you shall call it within
    // a BatchStart / BatchSend block
    function ManyDelete(aClient: TSQLRest; aDestID: Integer;
      aUseBatchMode: boolean=false): boolean; overload;
    /// will retrieve the record associated with a particular Source/Dest pair
    // - will return TRUE if the pair was found
    // - in this case, all "through" columns are available in the TSQLRecordMany
    // field instance
    function ManySelect(aClient: TSQLRest; aSourceID, aDestID: Integer): boolean; overload;
    /// will retrieve the record associated with the current source and a specified Dest
    // - source ID is taken from the fSourceID field (set by TSQLRecord.Create)
................................................................................
begin
  if fSourceID=nil then
    result := false else // avoid GPF
    result := DestGet(aClient,fSourceID^,DestIDs);
   // fSourceID has been set by TSQLRecord.Create
end;

function TSQLRecordMany.ManyDelete(aClient: TSQLRest; aSourceID, aDestID: Integer;
  aUseBatchMode: boolean=false): boolean;
var aID: integer;
begin
  result := false;
  if (self=nil) or (aClient=nil) or (aSourceID=0) or (aDestID=0) then
    exit;
  aID := InternalIDFromSourceDest(aClient,aSourceID,aDestID);
  if aID<>0 then
    if aUseBatchMode and aClient.InheritsFrom(TSQLRestClientURI) then
      result := TSQLRestClientURI(aClient).BatchDelete(RecordClass,aID)>=0 else
      result := aClient.Delete(RecordClass,aID);
end;

function TSQLRecordMany.ManyDelete(aClient: TSQLRest; aDestID: Integer;
  aUseBatchMode: boolean=false): boolean;
begin
  if fSourceID=nil then
    result := false else // avoid GPF
    result := ManyDelete(aClient,fSourceID^,aDestID,aUseBatchMode);
end;

function TSQLRecordMany.ManySelect(aClient: TSQLRest; aSourceID, aDestID: Integer): boolean;
begin
  if (self=nil) or (aClient=nil) or (aSourceID=0) or (aDestID=0) then
    result := false else // invalid parameters
    result := aClient.Retrieve(FormatUTF8('Source=:(%): AND Dest=:(%):',

Changes to SQLite3/SQLite3DB.pas.

418
419
420
421
422
423
424
425

426
427
428
429
430
431
432
...
444
445
446
447
448
449
450

451
452

453
454
455

456
457
458
459
460
461
462
...
477
478
479
480
481
482
483
484



485
486
487
488
489
490
491
...
514
515
516
517
518
519
520




521
522


523
524














525
526
527
528
529
530
531
  fSelectOneDirectSQL := fSelectOneDirectSQL+' where ID=?';
  fSelectAllDirectSQL := FormatUTF8('select %,ID from %',[StoredClassProps.SQLInsertSet,fTableName]);
end;

function TSQLRestServerStaticExternal.AdaptSQLForEngineList(var SQL: RawUTF8): boolean;
var Prop: ShortString;
    P: PUTF8Char;
    BPos,RowIDFromPos,TablePos,RowIDWherePos: integer;

    tmp: RawUTF8;
procedure GetFieldProp;
var i,L: integer;
    B: PUTF8Char;
begin
  if P^=#0 then begin
    Prop[0] := #0;
................................................................................
end;
begin
  result := inherited AdaptSQLForEngineList(SQL);
  if result or (SQL='') then
    exit; // found generic 'SELECT * FROM table' query
  // change 'RowID' into 'ID' column name, and SQLTableName into fTableName
  // process 'SELECT Field1,Field2 FROM table WHERE RowID=... AND/OR/NOT Field2='

  RowIDFromPos := 0;
  RowIDWherePos := 0;

  P := pointer(SQL);
  GetFieldProp;
  if Prop<>'SELECT' then exit;

  repeat
    GetFieldProp;
    if Prop='' then exit;
    if Prop='ROWID' then
      if RowIDFromPos>0 then
        exit else
        RowIDFromPos := BPos;
................................................................................
  if Prop='ORDER' then begin // simple ORDER BY clause is accepted
    GetFieldProp;
    if Prop<>'BY' then exit;
    GetFieldProp;
    if not (GotoNextNotSpace(P)^ in [#0,';']) then
      exit; // allow only one column name
    if Prop='ROWID' then
      system.delete(SQL,BPos,3); // 'RowID' -> 'ID'



  end else
  if Prop='WHERE' then
  repeat
    GetFieldProp;
    if Prop='NOT' then
      GetFieldProp; // allow  field1=456 AND NOT field2='Toto'
    if Prop='' then exit;
................................................................................
      repeat inc(P) until P^ in [#0..' ',';',')']; // go to end of value
    P := GotoNextNotSpace(P);
    if PWord(P)^=ord(')')+ord(':')shl 8 then
      inc(P,2); // ignore :(...): parameter
    P := GotoNextNotSpace(P);
    if P^ in [#0,';'] then break; // properly ended the WHERE clause
    GetFieldProp;




    if (Prop<>'AND') and (Prop<>'OR') then exit;
  until false else


  if Prop<>'' then
    exit;














  if RowIDWherePos>0 then
    system.delete(SQL,RowIDWherePos,3); // 'RowID' -> 'ID'
  system.delete(SQL,TablePos,length(StoredClassProps.SQLTableName));
  insert(fTableName,SQL,TablePos); // change table name
  if RowIDFromPos>0 then
    system.delete(SQL,RowIDFromPos,3); // 'RowID' -> 'ID'
  result := true;






|
>







 







>


>



>







 







|
>
>
>







 







>
>
>
>


>
>


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







418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
...
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
...
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
...
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
  fSelectOneDirectSQL := fSelectOneDirectSQL+' where ID=?';
  fSelectAllDirectSQL := FormatUTF8('select %,ID from %',[StoredClassProps.SQLInsertSet,fTableName]);
end;

function TSQLRestServerStaticExternal.AdaptSQLForEngineList(var SQL: RawUTF8): boolean;
var Prop: ShortString;
    P: PUTF8Char;
    BPos,AfterSelectPos,RowIDFromPos,TablePos,RowIDWherePos,RowIDOrderByPos: integer;
    LimitPos, LimitRowCount,err: integer;
    tmp: RawUTF8;
procedure GetFieldProp;
var i,L: integer;
    B: PUTF8Char;
begin
  if P^=#0 then begin
    Prop[0] := #0;
................................................................................
end;
begin
  result := inherited AdaptSQLForEngineList(SQL);
  if result or (SQL='') then
    exit; // found generic 'SELECT * FROM table' query
  // change 'RowID' into 'ID' column name, and SQLTableName into fTableName
  // process 'SELECT Field1,Field2 FROM table WHERE RowID=... AND/OR/NOT Field2='
  LimitPos := 0;
  RowIDFromPos := 0;
  RowIDWherePos := 0;
  RowIDOrderByPos := 0;
  P := pointer(SQL);
  GetFieldProp;
  if Prop<>'SELECT' then exit;
  AfterSelectPos := BPos;
  repeat
    GetFieldProp;
    if Prop='' then exit;
    if Prop='ROWID' then
      if RowIDFromPos>0 then
        exit else
        RowIDFromPos := BPos;
................................................................................
  if Prop='ORDER' then begin // simple ORDER BY clause is accepted
    GetFieldProp;
    if Prop<>'BY' then exit;
    GetFieldProp;
    if not (GotoNextNotSpace(P)^ in [#0,';']) then
      exit; // allow only one column name
    if Prop='ROWID' then
      RowIDOrderByPos := BPos; // 'RowID' -> 'ID';
    GetFieldProp;
    if Prop='LIMIT' then
      LimitPos := BPos;
  end else
  if Prop='WHERE' then
  repeat
    GetFieldProp;
    if Prop='NOT' then
      GetFieldProp; // allow  field1=456 AND NOT field2='Toto'
    if Prop='' then exit;
................................................................................
      repeat inc(P) until P^ in [#0..' ',';',')']; // go to end of value
    P := GotoNextNotSpace(P);
    if PWord(P)^=ord(')')+ord(':')shl 8 then
      inc(P,2); // ignore :(...): parameter
    P := GotoNextNotSpace(P);
    if P^ in [#0,';'] then break; // properly ended the WHERE clause
    GetFieldProp;
    if Prop='LIMIT' then begin
      LimitPos := BPos;
      break;
    end else
    if (Prop<>'AND') and (Prop<>'OR') then exit;
  until false else
  if Prop='LIMIT' then
    LimitPos := BPos else
  if Prop<>'' then
    exit;
  // handle LIMIT # statement at the end of the SQL
  if LimitPos>0 then begin
    GetFieldProp;
    val(Prop,LimitRowCount,err);
    if err<>0 then
      exit;
    exit;
    // TODO: add LIMIT clause using SynDB.DB_SQLLIMITCLAUSE[]+LimitRowCount
    // (so we need to have DBMS: TSQLDBDefinition at TSQLDBConnectionProperties level)
    system.delete(SQL,LimitPos,P-pointer(SQL)+LimitPos); // erase 'LIMIT #'
  end;
  // convert all 'RowID' to 'ID'
  if RowIDOrderByPos>0 then
    system.delete(SQL,RowIDOrderByPos,3);
  if RowIDWherePos>0 then
    system.delete(SQL,RowIDWherePos,3); // 'RowID' -> 'ID'
  system.delete(SQL,TablePos,length(StoredClassProps.SQLTableName));
  insert(fTableName,SQL,TablePos); // change table name
  if RowIDFromPos>0 then
    system.delete(SQL,RowIDFromPos,3); // 'RowID' -> 'ID'
  result := true;

Changes to SynDB.pas.

1568
1569
1570
1571
1572
1573
1574














1575
1576
1577
1578
1579
1580
1581
    '', '', '');

  /// the known function prototypes used to retrieve a mORMot field type
  // from the native type as returned by a TSQLDBSQLGetField function
  DB_COLUMNNATIVETODB: array[TSQLDBDefinition] of TSQLDBColumnTypeNativeToDB = (
    nil, ColumnTypeNativeToDBOracle, nil, nil, nil, nil);
















{$ifdef EMULATES_TQUERY}

{ -------------- TQuery TField TParam emulation classes and types }

type
  /// generic Exception type raised by the TQuery class






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







1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
    '', '', '');

  /// the known function prototypes used to retrieve a mORMot field type
  // from the native type as returned by a TSQLDBSQLGetField function
  DB_COLUMNNATIVETODB: array[TSQLDBDefinition] of TSQLDBColumnTypeNativeToDB = (
    nil, ColumnTypeNativeToDBOracle, nil, nil, nil, nil);

  /// the known SQL syntax to limit the number of returned rows in a SELECT
  // - Positon indicates if should be included within the WHERE clause,
  // at the beginning of the SQL statement, or at the end of the SQL statement
  // - Insert will replace '%' with the maximum number of lines to be retrieved
  DB_SQLLIMITCLAUSE: array[TSQLDBDefinition] of record
    Position: (posNone, posWhere, posSelect, posAfter);
    Insert: RawUTF8;
  end = (
    (Position: posNone;   Insert:''),
    (Position: posWhere;  Insert:'rownum<=%'),
    (Position: posSelect; Insert:'top %'),
    (Position: posSelect; Insert:'top %'),
    (Position: posAfter;  Insert:'limit %'),
    (Position: posAfter;  Insert:'limit %'));

{$ifdef EMULATES_TQUERY}

{ -------------- TQuery TField TParam emulation classes and types }

type
  /// generic Exception type raised by the TQuery class