2018-03-15 12:49:06 +06:00
|
|
|
------------------------------------------------------------------------------
|
2020-06-09 00:55:25 +02:00
|
|
|
-- G N A T C O L L --
|
2018-03-15 12:49:06 +06:00
|
|
|
-- --
|
2021-12-20 15:11:08 +06:00
|
|
|
-- Copyright (C) 2010-2021, AdaCore --
|
2018-03-15 12:49:06 +06:00
|
|
|
-- --
|
|
|
|
|
-- 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.
|
2020-06-09 00:55:25 +02:00
|
|
|
-- ??? When we have prepared parametrized statements in GNATCOLL.SQL, we
|
2018-03-15 12:49:06 +06:00
|
|
|
-- 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
|
2020-06-09 00:55:25 +02:00
|
|
|
-- the query. The query is auto-completed, so technically only the
|
2018-03-15 12:49:06 +06:00
|
|
|
-- 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);
|
2020-06-09 00:55:25 +02:00
|
|
|
-- Whether the manager follows LEFT JOIN
|
2018-03-15 12:49:06 +06:00
|
|
|
|
|
|
|
|
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;
|