------------------------------------------------------------------------------
--                             G N A T C O L L                              --
--                                                                          --
--                     Copyright (C) 2011-2015, AdaCore                     --
--                                                                          --
-- This library is free software;  you can redistribute it and/or modify it --
-- under terms of the  GNU General Public License  as published by the Free --
-- Software  Foundation;  either version 3,  or (at your  option) any later --
-- version. This library is distributed in the hope that it will be useful, --
-- but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN- --
-- TABILITY or FITNESS FOR A PARTICULAR PURPOSE.                            --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
------------------------------------------------------------------------------

with Ada.Calendar;          use Ada.Calendar;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO;           use Ada.Text_IO;
with GNATCOLL.SQL;          use GNATCOLL.SQL;
with GNATCOLL.SQL.Exec;     use GNATCOLL.SQL.Exec;
with GNATCOLL.SQL.Inspect;  use GNATCOLL.SQL.Inspect;
with GNATCOLL.SQL.Sqlite;   use GNATCOLL.SQL.Sqlite;
with GNATCOLL.Traces;       use GNATCOLL.Traces;
with GNATCOLL.VFS;          use GNATCOLL.VFS;

--  Load the auto-generated file that describes the database
with Dbschema;              use Dbschema;

--  Example of performance for 100_000 queries, on a x86_64 linux machine.
--  The exact timings are irrelevant, the comparaison is useful.
--  To reproduce, set Repeat to 100_000.
--
--  Simple queries, direct cursor 4.058620000
--  Simple queries, forward cursor 3.698005000
--  Prepared on client, direct cursor 2.505329000
--  Caching on client, direct cursor 0.136569000
--  Prepared on server, direct cursor 0.558868000

procedure Prepared is
   Me : constant Trace_Handle := Create ("MAIN");

   Repeat : constant Natural := 1;
   --  The number of times we repeat each query.
   --  Set this to 1 to debug the traces, to more for performance testing

   Start_Time : Time;
   Descr    : Database_Description;
   DB1, DB2 : Database_Connection;
   Q        : SQL_Query;

   procedure Start is
   begin
      Start_Time := Clock;
   end Start;

   procedure Stop (Msg : String) is
      End_Time : Time := Clock;
   begin
      if Repeat > 1 then
         Put_Line (Msg & Duration'Image (End_Time - Start_Time));
      end if;
   end Stop;

   procedure Do_Test is
      P1, P2   : Prepared_Statement;
      D : Direct_Cursor;
      F : Forward_Cursor;
      S : Forward_Cursor;
   begin
      --  Read the schema from dbschema.txt
      --  And create a new database from it.
      --  Finally copy some data into the database.

      Trace (Me, "-------------- Preparing database -------------");
      declare
         Schema : DB_Schema;
         DBIO   : DB_Schema_IO;
      begin
         Schema := New_Schema_IO (Create ("dbschema.txt")).Read_Schema;

         DBIO.DB := DB1;
         DBIO.Write_Schema (Schema);

         Load_Data (DB1, Create ("initialdata.txt"), Schema);
         DB1.Commit_Or_Rollback;
      end;

      --  Now some queries.
      --  First, a standard query

      Trace (Me, "-------------- Simple queries -------------");

      Q := SQL_Select
         (Fields => Data.Id,
          From   => Data,
          Where  => Data.Value = 2);

      --  Fetch all results at once

      Start;
      for R in 1 .. Repeat loop
         D.Fetch (DB1, Q);
      end loop;
      Stop ("Simple queries, direct cursor");

      --  Fetch rows one by one
      --  Use a declare block, so that the cursor is automatically closed

      Start;
      for R in 1 .. Repeat loop
         F.Fetch (DB1, Q);
      end loop;
      Stop ("Simple queries, forward cursor");

      --  Similar test, but with a prepared query.
      --  First, we prepare the query on the client side only. If we were
      --  to repeat the query a lot, this would save a lot of time spent in
      --  GNATCOLL.SQL to prepare the query tree and then dump it to a string

      Trace (Me, "-------------- preparation on client -------------");

      P1 := Prepare (Q, On_Server => False);
      D.Fetch (DB1, P1);

      Start;
      for R in 1 .. Repeat loop
         D.Fetch (DB1, P1);  --  Check that we can run twice
      end loop;
      Stop ("Prepared on client, direct cursor");

      P1 := No_Prepared;
      D.Fetch (DB1, P1);  --  Error, P1 has been unreferenced and freed

      --  A prepared statement, whose results are cached on the client. This
      --  avoids further queries to the server, but should of course be limited
      --  to tables that never change.

      Trace (Me, "-------------- caching on client -------------");

      P1 := Prepare (Q, On_Server => False, Use_Cache => True);
      D.Fetch (DB1, P1);

      Start;
      for R in 1 .. Repeat loop
         D.Fetch (DB1, P1);  --  Check that we can run twice
      end loop;
      Stop ("Caching on client, direct cursor");

      P1 := No_Prepared;

      --  Then prepare the query on the server. This will be even faster.

      Trace (Me, "-------------- preparation on server -------------");

      P2 := Prepare (Q, On_Server => True, Name => "query2");
      D.Fetch (DB1, P2);

      Start;
      for R in 1 .. Repeat loop
         D.Fetch (DB1, P2); --  Check that we can run twice
      end loop;
      Stop ("Prepared on server, direct cursor");

      Trace (Me, "----------- same prepared stmt, on second connection --");
      D.Fetch (DB2, P2);  --  will prepare again on that connection

      --  A second query, using parameters that are substituted when the query
      --  is executed

      Trace (Me, "-------------- using parameters -------------");

      Q := SQL_Select
         (Fields => Data.Id,
          From   => Data,
          Where  => Data.Value = Integer_Param (1) and
                    Data.Amount < Money_Param (2));
      P2 := Prepare (Q, On_Server => True);

      D.Fetch (DB1, P2, Params => (1 => +2, 2 => +T_Money (10004.56)));
      D.Fetch (DB1, P2, Params => (1 => +3, 2 => +T_Money (54.76)));

      Trace (Me, "-------------- test money value -------------");

      Q := SQL_Select
         (Fields => Data.Id & Data.Amount,
          From   => Data,
          Where  => Data.Amount < T_Money (1536.99));

      S.Fetch (DB1, Q);
      while S.Has_Row loop
         Trace (Me, "result " & S.Money_Value (1)'Img);
         S.Next;
      end loop;
   end Do_Test;

begin
   --  If you want to see what SQL commands are executed, create a file
   --  called ".gnatdebug" in the current directory, that contains a
   --  single "+" (see documentation for GNATCOLL.Traces)

   GNATCOLL.Traces.Parse_Config_File;

   GNATCOLL.SQL.Sqlite.Sqlite_Always_Use_Transactions := False;

   --  Connect to the database.
   --  We use directly Build_Sqlite_Connection instead of going through
   --  Get_Task_Connection, because we want to have several connections in
   --  the same thread. This is relatively unusual scenario, though.

   Descr := GNATCOLL.SQL.Sqlite.Setup
      (Database => "test.db", Cache_Support => True);
   DB1 := Descr.Build_Connection;
   DB2 := Descr.Build_Connection;

   Do_Test;

   --  Free memory

   Free (Descr);
   Free (DB1);
   Free (DB2);
   GNATCOLL.Traces.Finalize;
end Prepared;
