/// answer to http://stackoverflow.com/q/39685770/458259
program ORMFastMultiInsertWithIndex;

{
   insert 1,000,000 rows via the ORM in a SQlite3 in-memory database,
   and search by Key using an index (only 1024 diverse values of Key)
   - note that two similar SELECT are executed, to show two ORM methods

  with index defined before insertion:
    INSERT 1000000 rows in 6.71s
    SELECT 1000000 rows per Key index in 1.15s

  with index created after insertion:
    INSERT 1000000 rows in 2.91s
    CREATE INDEX 1000000 in 1.28s
    SELECT 1000000 rows per Key index in 1.15s

  without the index:
    INSERT 1000000 rows in 2.94s
    SELECT 1000000 rows per Key index in 129.27s

  So for huge data set, an index is worth it, and better done AFTER insertion!

}

{$APPTYPE CONSOLE}

uses
  {$I SynDprUses.inc} // use FastMM4 on older Delphi, or set FPC threads
  SysUtils,
  SynCommons,
  SynSQLite3,
  SynSQLite3Static,
  mORMot,
  mORMotSQLite3;

type
  TSQLEntry = class(TSQLRecord)
  private
    fKey: RawUTF8;
    fValue: RawUTF8;
  published
    property Key: RawUTF8 read fKey write fKey;
    property Value: RawUTF8 read fValue write fValue;
  end;

const
  // used to create some fake data, with some multiple occurences of Key
  COUNT = 1000000; // one million rows insertion !
  UNIQUE_KEY = 1024; // should be a power of two

procedure Process;

var
  db: TSQLRestServerDB;

  procedure DoInsert;
  var i: integer;
      rec: TSQLEntry;
      batch: TSQLRestBatch;
  begin
    rec := TSQLEntry.Create;
    batch := TSQLRestBatch.Create(db,TSQLEntry,10000,[boExtendedJSON]);
    try
      write('batch:');
      for i := 0 to COUNT-1 do begin
        // here we fill with some data
        rec.Key := FormatUTF8('KEY%',[i and pred(UNIQUE_KEY)]);
        rec.Value := FormatUTF8('VALUE%',[i]);
        batch.Add(rec,true);
        if i and $2ffff = $2ffff then begin
          write(' send');
          db.batchSend(batch);
          batch.Reset;
        end;
      end;
      db.BatchSend(batch);
    finally
      batch.Free;
      rec.Free;
    end;
  end;

  procedure DoSelect;
  var i, total: integer;
      rec: TSQLEntry;
      key: RawUTF8;
      values: TRawUTF8DynArray;
  begin
    total := 0;
    rec := TSQLEntry.Create;
    try
      for i := 0 to pred(UNIQUE_KEY) do begin
        key := FormatUTF8('KEY%',[i]);
        // 1. first way: get all values at once
        db.OneFieldValues(TSQLEntry,'Value',FormatUTF8('Key=?',[],[key]),values);
        // here values[] contains all "Value" column associated with this key
        // 2. second way: use ORM
        rec.FillPrepare(db,'Key=?',[],[key],'Value');
        while rec.FillOne do begin
          // here you have rec.Value filled
          assert(rec.Value=values[rec.FillCurrentRow-2]);
          inc(total);
        end;
      end;
      assert(total=COUNT);
    finally
      rec.Free;
    end;
  end;

var timer: TPrecisionTimer;
begin
  if true then
    db := TSQLRestServerDB.CreateWithOwnModel([TSQLEntry],SQLITE_MEMORY_DATABASE_NAME)
  else begin
    deletefile('test.db');
    db := TSQLRestServerDB.CreateWithOwnModel([TSQLEntry],'test.db');
  end;
  try
    db.DB.LockingMode := lmExclusive;
    db.DB.Synchronous := smOff;
    db.CreateMissingTables;
    write('INSERT ', COUNT, ' rows');
    timer.Start;
    DoInsert;
    writeln(' in ',timer.Stop);
    write('CREATE INDEX ', COUNT);
    timer.Start;
    db.CreateSQLIndex(TSQLEntry,'Key',false); // faster done after insertion
    writeln(' in ',timer.Stop);
    write('SELECT ', COUNT, ' rows per Key index');
    timer.Start;
    DoSelect;
    writeln(' in ',timer.Stop);
  finally
    db.Free;
  end;
end;

begin
  Process;
  writeln('Press any key');
  readln;
end.
