File: gnatcoll-sql-orm.ads

package info (click to toggle)
libgnatcoll-db 18-4
  • links: PTS
  • area: main
  • in suites: buster
  • size: 2,268 kB
  • sloc: ada: 23,786; python: 2,166; makefile: 486; sh: 34; ansic: 18
file content (288 lines) | stat: -rw-r--r-- 15,178 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
------------------------------------------------------------------------------
--                             M O D E L I N G                              --
--                                                                          --
--                     Copyright (C) 2010-2018, 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/>.                                          --
--                                                                          --
------------------------------------------------------------------------------

--  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;          use GNATCOLL.SQL;
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 parameterized 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 autocompleted, 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 m anager 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;