------------------------------------------------------------------------------
--                             G N A T C O L L                              --
--                                                                          --
--                     Copyright (C) 2010-2021, 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.                            --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- 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/>.                                          --
--                                                                          --
------------------------------------------------------------------------------

--  An Object Relationship Management API.
--  This API creates a high-level Ada interface to a SQL database.
--  This package only contains base types that are further derived in an
--  automatically generated package that depends on your database schema. You
--  should not have to read the generated package, all comments are described
--  here.
--  Some subprograms are commented out in this package, and are here for
--  documentation purposes only. They illustrate what the automatically
--  generated package contains (see gnatcoll_db2ada documentation).

with GNATCOLL.SQL.Sessions; use GNATCOLL.SQL.Sessions;
with GNATCOLL.SQL.Exec;     use GNATCOLL.SQL.Exec;

package GNATCOLL.SQL.Orm is

   Dynamic_Fetching : Boolean := True;
   --  If this variable is true, the Orm.* packages will automatically
   --  perform SQL queries to fetch missing information. Otherwise, you will
   --  get an exception Field_Not_Available, and you'll need to adjust the
   --  Select_Related for the manager. Setting this to False is mostly useful
   --  for debugging purposes, and helps optimize the queries.

   Field_Not_Available : exception;
   --  Raised when an sql query would need to be performed to retrieve a field,
   --  but Dynamic_Fetching is False.

   Self_Referencing : exception;
   --  Raised when an element is referencing itself, but it hasn't been
   --  inserted in the database yet. For instance:
   --      E := New_...;
   --      Set_FK (E, E);
   --      Session.Flush;
   --  The row cannot be inserted in the database, because we need to know
   --  E's id before we can insert E.
   --  The solution here is to first insert E without the self-reference, flush
   --  the session, and then set the self-reference (and possibly flush again).

   No_Update : aliased constant String := "#$%@%";
   --  A string that is unlikely to be specified by a user in a field. This is
   --  then used as the default value for various fields below, so that we
   --  know whether that field is being modified or not.

   type Manager is abstract tagged private;
   --  A high-level interface to the database. Through a Manager, you
   --  construct a query that is then run on the database and returns
   --  the list of results.
   --  Specialized children of Manager are provided for each of the tables in
   --  the database, to return the appropriate list of elements.
   --
   --  The manager does not cache the result of the query. As a result, the
   --  same manager can be reused multiple time to retrieve the same kind of
   --  elements. We do not have to decide in advance whether we want a forward
   --  or direct list, nor whether we want a standard or detached component.
   --  This reuse gives a more efficient API, since we can prepare in advance
   --  (possibly at elaboration time) a series of managers and reuse them if
   --  needed.
   --  ??? When we have prepared parametrized statements in GNATCOLL.SQL, we
   --  could take advantage of them here as well, and replace the actual values
   --  only at the last moment.
   --
   --  The managers that are automatically define a function Get that returns
   --  a specific row from a table (by primary key). This function is just a
   --  shortcut which creates a manager and retrieves a list of rows (limited
   --  to a single row for efficiency). This subprograms assumes there is a
   --  matching element. If you are not sure, retrieve the list directly and
   --  check whether it is empty.

   procedure Order_By (Self : in out Manager'Class; By : SQL_Field_List);
   procedure Order_By (Self : in out Manager'Class; By : SQL_Field'Class);
   pragma Inline (Order_By);
   --  Specifies a specific sort order.
   --  The generated managers will contain a version of the functions above.
   --  They return a copy of the Manager, so you can then manipulate both
   --  Self and the result independently.

   procedure Limit
     (Self : in out Manager'Class; Count : Natural; From : Natural := 0);
   pragma Inline (Limit);
   --  Return a copy of Self that will only retrieve a specific number of
   --  rows

   procedure Distinct (Self : in out Manager'Class);
   pragma Inline (Distinct);
   --  Return a copy of Self that ensures that no two rows in the result are
   --  the same.

   procedure Select_Related
      (Self  : in out Manager'Class;
       Depth : Natural;
       Follow_Left_Join : Boolean := False);
   pragma Inline (Select_Related);
   --  The query performed by Self will also return fields from other tables
   --  linked through a foreign key.
   --  For instance, if A.Field is a foreign key to B.PK, the resulting
   --  manager will retrieve the values for A, but also the related values
   --  from B, which can result in major speed ups when processing the
   --  results.
   --  Depth indicates how many levels of foreign keys are followed. If it is
   --  0, no related table will be fetched.
   --  This subprogram will include all related tables. For finer control, see
   --  the Select_Related operation that the manager defines and that allow
   --  you to specify which table to link. If you have called the version with
   --  Depth, though, the other version will have no effect.

   function Select_Related (Self : Manager'Class) return Natural;
   pragma Inline (Select_Related);
   --  Whether we should also query related tables. 0 indicates that we should
   --  not, and other result indicates to which depth

   procedure Filter (Self : in out Manager'Class; Condition : SQL_Criteria);
   pragma Inline (Filter);
   --  Filter the data returned by Self (returns a copy of Self).
   --  Condition can be used when you need to reference another table than the
   --  one manipulated by Self. If you only want to impact that table, it is
   --  better to use the dedicated version of Filter, which provides more type
   --  safety and will be easier to write in general.

   procedure Query (Self   : Manager'Class;
                    Query  : out SQL_Query;
                    Fields : SQL_Field_List;
                    From   : SQL_Table_List;
                    Criteria : SQL_Criteria := No_Criteria);
   --  Return the query to execute for this manager.
   --  Fields are the list of fields that should be retrieved by the
   --  query.
   --  From is the list of JOIN and LEFT JOIN that should be performed for
   --  the query. The query is auto-completed, so technically only the
   --  LEFT JOIN are needed.
   --  ??? Result should be cached, but making Self "in out" is not convenient

   ------------------------
   -- Lists and elements --
   ------------------------
   --  Each child package defines a manager specific to one table, and two
   --  kinds of lists: Forward_List and Direct_List. In both cases, the number
   --  of SQL queries is exactly the same. However, depending on the SQL
   --  backend, the former might be faster since we do not have to get all
   --  results in memory at once (in particular much faster with sqlite). In
   --  exchange, the list can be traversed only once.
   --
   --  Each package also provides two types of elements: standard components or
   --  detached components. In both cases, the number of queries is exactly the
   --  same. The former is more efficient since we do not create a record with
   --  the value of all fields (in particular avoiding copies of strings and
   --  parsing times, when these fields might not be needed), but we cannot
   --  move the list forward when using standard components. Detached
   --  components can be kept forever but require more memory management.

   --  Each component has a set of primitive operations to query the value of
   --  the fields. They can automatically lookup related records (performing
   --  SQL queries automatically if necessary). These lookups can be eliminated
   --  by using the appropriate value for Select_Related (for the manager).
   --  In the case of the detached components, the result value is also cached,
   --  thus multiple calls to the primitive operation will result in at most
   --  once SQL query. For standard components, a SQL query might be performed
   --  every time (in the spirit of keeping these components as light as
   --  possible). To avoid the extra queries, you should store the result in
   --  a temporary variable and reuse that as appropriate.
   --
   --  The list always holds a handle to the connection (which therefore is not
   --  released to the pool until the list is destroyed). As a result you
   --  should not store such a list in a datastructure (which in addition would
   --  be inefficient).

   Cursor_Has_Moved : exception;
   --  Raised when retrieving a value from an element, but the database cursor
   --  has changed in between. This does not occur with detached elements.

   type Forward_List;   --  extends GNATCOLL.SQL.Exec.Forward_Cursor
   type Direct_List;    --  extends GNATCOLL.SQL.Exec.Direct_Cursor
   --  The two types of lists that can be created by a manager. They influence
   --  efficiency of the code (forward_list: only moving forward, but only one
   --  row fetched at a time depending on DBMS; direct_list: moving in any
   --  order, but all data needs to be stored in memory)

   --------------------
   -- Implementation --
   --------------------
   --  The following types and subprograms are only useful for the package
   --  automatically generated by gnatcoll_db2ada to provide the ORM API for
   --  your schema. We need to make these types public so that the generated
   --  package (which will be in another package hierarchy) has access to them.

   type Shared_List_Data is record
      Session   : Session_Type;
      Follow_LJ : Boolean;  --  Whether to follow LEFT JOIN automatically
   end record;
   type Shared_List_Data_Access is access constant Shared_List_Data;

   procedure Copy (Self : Manager'Class; Into : in out Manager'Class);
   pragma Inline (Copy);
   --  Copy all fields from Self into Into

   type Counts is array (Natural range <>, Boolean range <>) of Field_Index;

   type Alias_Array is array (Natural range <>) of Integer;
   --  This array is used to compute the name of tables in queries. This is
   --  complex because a given table might occur several times, especially
   --  when select_related(...) is used. The table names are precomputed here
   --  for efficiency.
   --  Semantically, we have a nested list structure (the way it would be
   --  implemented in list), where each level indicates the aliases for tables.
   --  The first instance of all tables should have its full name, so that it
   --  is easier for users to write Filter or Order_By clauses.
   --
   --  For instance (in square in the alias index to use):
   --      links[-1]   ---meta---> associations[-1] ---end1---> assoc_end[-1]
   --                                               \--end2---> assoc_end[1]
   --                  \--end1---> models[-1] ---from---> model_names[-1]
   --                                         \--meta---> models[2]
   --                  \--end2---> models[0] ---from---> model_names[3]
   --                                         \--meta---> models[4]
   --
   --  This is stored as a flat array on the Ada side, because each level
   --  might have a varying number of entries, some of which are pointers to
   --  sublists, others are integers for the table aliases. In this table, the
   --  first item for each level is a alias number, others are jumps to other
   --  parts of the array.
   --
   --  Aliases : array (Integer range <>) of Integer :=
   --      (-1,4,16,?,   # links, meta index, end1 index,  end2 index
   --       -1,8,12,     # at index 4: meta is alias -1, end1 index, end2 index
   --       ...)
   --  The table is computed for the worst case, ie the deepest select_related
   --  we might use and with Follow_Left_Join. To keep queries shorter, alias
   --  numbers are assigned breadth first.

   No_Shared_Lists_Data : constant Shared_List_Data :=
     (Session    => No_Session,
      Follow_LJ  => False);
   --  Common information shared by the list types

   type Forward_List is new Forward_Cursor with record
      Data  : aliased Shared_List_Data;
      Depth : Natural;  --  Depth for Select_Related
   end record;
   No_Forward_List : constant Forward_List :=
      (No_Element with Data => No_Shared_Lists_Data, Depth => 0);
   --  Depth cannot be part of Data, since the same List is shared by all
   --  levels of elements (a Config (depth 0) points to a platform (depth 1),
   --  and yet they share the same list that contains the data).

   type Direct_List is new Direct_Cursor with record
      Data  : aliased Shared_List_Data;
      Depth : Natural;  --  Depth for Select_Related
   end record;
   No_Direct_List : constant Direct_List :=
      (No_Direct_Element with Data => No_Shared_Lists_Data, Depth => 0);

   function Follow_LJ (Self : Manager'Class) return Boolean;
   pragma Inline (Follow_LJ);
   --  Whether the manager follows LEFT JOIN

private
   type Manager is abstract tagged record
      Where          : SQL_Criteria := No_Criteria;
      Order_By       : SQL_Field_List;
      Limit_Count    : Integer := -1;
      Offset         : Integer := -1;
      Distinct       : Boolean := False;
      Select_Related : Natural := 0;
      Follow_LJ      : Boolean := False;
   end record;
end GNATCOLL.SQL.Orm;
