1--  This file is covered by the Internet Software Consortium (ISC) License
2--  Reference: ../../License.txt
3
4with Ada.Exceptions;
5
6package body AdaBase.Driver.Base.MySQL is
7
8   package EX renames Ada.Exceptions;
9
10   ---------------
11   --  execute  --
12   ---------------
13   overriding
14   function execute (driver : MySQL_Driver; sql : String)
15                     return Affected_Rows
16   is
17      trsql   : String := CT.trim_sql (sql);
18      nquery  : Natural := CT.count_queries (trsql);
19      aborted : constant Affected_Rows := 0;
20      err1    : constant CT.Text :=
21                 CT.SUS ("ACK! Execution attempted on inactive connection");
22      err2    : constant String :=
23                         "Driver is configured to allow only one query at " &
24                         "time, but this SQL contains multiple queries: ";
25   begin
26      if not driver.connection_active then
27         --  Fatal attempt to query an unccnnected database
28         driver.log_problem (category => execution,
29                             message  => err1,
30                             break    => True);
31         return aborted;
32      end if;
33
34      if nquery > 1 and then not driver.trait_multiquery_enabled then
35         --  Fatal attempt to execute multiple queries when it's not permitted
36         driver.log_problem (category   => execution,
37                             message    => CT.SUS (err2 & trsql),
38                             break      => True);
39         return aborted;
40      end if;
41
42      declare
43         result : Affected_Rows;
44      begin
45         --  MySQL execute is configured to support multiquery at this point
46         --  so it is not necessary to loop through subqueries.  We send the
47         --  trimmed compound query as it was received.
48         driver.connection.execute (trsql);
49         driver.log_nominal (execution, CT.SUS (trsql));
50         result := driver.connection.rows_affected_by_execution;
51         return result;
52      exception
53         when ACM.QUERY_FAIL =>
54            driver.log_problem (category   => execution,
55                                message    => CT.SUS (trsql),
56                                pull_codes => True);
57            return aborted;
58      end;
59   end execute;
60
61
62   ------------------------------------------------------------------------
63   --  ROUTINES OF ALL DRIVERS NOT COVERED BY INTERFACES (TECH REASON)   --
64   ------------------------------------------------------------------------
65
66
67   -------------
68   --  query  --
69   -------------
70   function query (driver : MySQL_Driver; sql : String)
71                   return ASM.MySQL_statement is
72   begin
73      return driver.private_query (sql);
74   end query;
75
76
77   ---------------
78   --  prepare  --
79   ---------------
80   function prepare (driver : MySQL_Driver; sql : String)
81                     return ASM.MySQL_statement is
82   begin
83      return driver.private_prepare (sql);
84   end prepare;
85
86
87   --------------------
88   --  query_select  --
89   --------------------
90   function query_select (driver      : MySQL_Driver;
91                          distinct    : Boolean := False;
92                          tables      : String;
93                          columns     : String;
94                          conditions  : String := blankstring;
95                          groupby     : String := blankstring;
96                          having      : String := blankstring;
97                          order       : String := blankstring;
98                          null_sort   : Null_Priority := native;
99                          limit       : Trax_ID := 0;
100                          offset      : Trax_ID := 0)
101                          return ASM.MySQL_statement is
102   begin
103      return driver.private_query
104        (driver.sql_assemble (distinct   => distinct,
105                              tables     => tables,
106                              columns    => columns,
107                              conditions => conditions,
108                              groupby    => groupby,
109                              having     => having,
110                              order      => order,
111                              null_sort  => null_sort,
112                              limit      => limit,
113                              offset     => offset));
114   end query_select;
115
116
117   ----------------------
118   --  prepare_select  --
119   ----------------------
120   function prepare_select (driver      : MySQL_Driver;
121                            distinct    : Boolean := False;
122                            tables      : String;
123                            columns     : String;
124                            conditions  : String := blankstring;
125                            groupby     : String := blankstring;
126                            having      : String := blankstring;
127                            order       : String := blankstring;
128                            null_sort   : Null_Priority := native;
129                            limit       : Trax_ID := 0;
130                            offset      : Trax_ID := 0)
131                            return ASM.MySQL_statement is
132   begin
133      return driver.private_prepare
134        (driver.sql_assemble (distinct   => distinct,
135                              tables     => tables,
136                              columns    => columns,
137                              conditions => conditions,
138                              groupby    => groupby,
139                              having     => having,
140                              order      => order,
141                              null_sort  => null_sort,
142                              limit      => limit,
143                              offset     => offset));
144   end prepare_select;
145
146
147   ------------------------------------------------------------------------
148   --  PUBLIC ROUTINES NOT COVERED BY INTERFACES                         --
149   ------------------------------------------------------------------------
150
151   ---------------------------------
152   --  trait_compressed_protocol  --
153   ---------------------------------
154   function trait_protocol_compressed (driver : MySQL_Driver) return Boolean
155   is
156   begin
157      return driver.connection.compressed;
158   end trait_protocol_compressed;
159
160
161   --------------------------------
162   --  trait_query_buffers_used  --
163   --------------------------------
164   function trait_query_buffers_used  (driver : MySQL_Driver) return Boolean
165   is
166   begin
167      return driver.connection.useBuffer;
168   end trait_query_buffers_used;
169
170
171   --------------------------------------
172   --  set_trait_compressed_protocol  --
173   -------------------------------------
174   procedure set_trait_protocol_compressed (driver : MySQL_Driver;
175                                            trait  : Boolean)
176   is
177   begin
178      driver.connection.setCompressed (compressed => trait);
179   end set_trait_protocol_compressed;
180
181
182   ------------------------------
183   --  set_query_buffers_used  --
184   ------------------------------
185   procedure set_trait_query_buffers_used (driver : MySQL_Driver;
186                                           trait  : Boolean)
187   is
188   begin
189      driver.connection.setUseBuffer (buffered => trait);
190   end set_trait_query_buffers_used;
191
192   ------------------------------------------------------------------------
193   --  PRIVATE ROUTINES NOT COVERED BY INTERFACES                        --
194   ------------------------------------------------------------------------
195
196   ------------------
197   --  initialize  --
198   ------------------
199   overriding
200   procedure initialize (Object : in out MySQL_Driver)
201   is
202   begin
203      Object.connection       := Object.local_connection'Unchecked_Access;
204      Object.dialect          := driver_mysql;
205   end initialize;
206
207
208   -----------------------
209   --  private_connect  --
210   -----------------------
211   overriding
212   procedure private_connect (driver   : out MySQL_Driver;
213                              database : String;
214                              username : String;
215                              password : String;
216                              hostname : String     := blankstring;
217                              socket   : String     := blankstring;
218                              port     : Posix_Port := portless)
219   is
220      err1 : constant CT.Text :=
221        CT.SUS ("ACK! Reconnection attempted on active connection");
222      nom  : constant CT.Text :=
223        CT.SUS ("Connection to " & database & " database succeeded.");
224   begin
225      if driver.connection_active then
226         driver.log_problem (category => execution,
227                             message  => err1);
228         return;
229      end if;
230      driver.connection.connect (database => database,
231                                 username => username,
232                                 password => password,
233                                 socket   => socket,
234                                 hostname => hostname,
235                                 port     => port);
236
237      driver.connection_active := driver.connection.all.connected;
238
239      driver.log_nominal (category => connecting, message => nom);
240   exception
241      when Error : others =>
242         driver.log_problem
243           (category => connecting,
244            break    => True,
245            message  => CT.SUS (ACM.EX.Exception_Message (X => Error)));
246   end private_connect;
247
248
249   ---------------------
250   --  private_query  --
251   ---------------------
252   function private_query (driver : MySQL_Driver; sql : String)
253                           return ASM.MySQL_statement
254   is
255      duplicate : aliased String := sql;
256      err1 : constant CT.Text :=
257        CT.SUS ("ACK! Query attempted on inactive connection");
258   begin
259      if driver.connection_active then
260         declare
261            err2 : constant CT.Text := CT.SUS ("Query failed!");
262            statement : ASM.MySQL_statement
263                (type_of_statement => AID.ASB.direct_statement,
264                 log_handler       => logger'Access,
265                 mysql_conn        => ACM.MySQL_Connection_Access
266                                      (driver.connection),
267                 initial_sql       => duplicate'Unchecked_Access,
268                 con_error_mode    => driver.trait_error_mode,
269                 con_case_mode     => driver.trait_column_case,
270                 con_max_blob      => driver.trait_max_blob_size,
271                 con_buffered      => driver.trait_query_buffers_used);
272         begin
273            if statement.successful then
274               driver.log_nominal (category => execution,
275                                   message => CT.SUS ("query succeeded," &
276                                       statement.rows_returned'Img &
277                                       " rows returned"));
278            else
279               driver.log_nominal (category => execution, message => err2);
280            end if;
281            return statement;
282         exception
283            when RES : others =>
284               --  Fatal attempt to create a direct statement
285               driver.log_problem
286                 (category   => execution,
287                  message    => CT.SUS (EX.Exception_Message (RES)),
288                  pull_codes => True,
289                  break      => True);
290         end;
291      else
292         --  Fatal attempt to query an unconnected database
293         driver.log_problem (category => execution,
294                             message  => err1,
295                             break    => True);
296      end if;
297      --  We never get here, the driver.log_problem throws exception first
298      raise ACM.STMT_NOT_VALID
299        with "failed to return MySQL statement";
300   end private_query;
301
302
303   -----------------------
304   --  private_prepare  --
305   -----------------------
306   function private_prepare (driver : MySQL_Driver; sql : String)
307                             return ASM.MySQL_statement
308   is
309      duplicate : aliased String := sql;
310      err1 : constant CT.Text :=
311        CT.SUS ("ACK! Query attempted on inactive connection");
312   begin
313      if driver.connection_active then
314         declare
315            statement : ASM.MySQL_statement
316                (type_of_statement => AID.ASB.prepared_statement,
317                 log_handler       => logger'Access,
318                 mysql_conn        => ACM.MySQL_Connection_Access
319                                      (driver.connection),
320                 initial_sql       => duplicate'Unchecked_Access,
321                 con_error_mode    => driver.trait_error_mode,
322                 con_case_mode     => driver.trait_column_case,
323                 con_max_blob      => driver.trait_max_blob_size,
324                 con_buffered      => driver.trait_query_buffers_used);
325         begin
326            return statement;
327         exception
328            when RES : others =>
329               --  Fatal attempt to prepare a statement
330               driver.log_problem
331                 (category   => statement_preparation,
332                  message    => CT.SUS (EX.Exception_Message (RES)),
333                  pull_codes => True,
334                  break      => True);
335         end;
336      else
337         --  Fatal attempt to query an unconnected database
338         driver.log_problem (category => statement_preparation,
339                             message  => err1,
340                             break    => True);
341      end if;
342      --  We never get here, the driver.log_problem throws exception first
343      raise ACM.STMT_NOT_VALID
344        with "failed to return MySQL statement";
345   end private_prepare;
346
347
348   --------------------
349   --  sql_assemble  --
350   --------------------
351   function sql_assemble (driver     : MySQL_Driver;
352                          distinct   : Boolean := False;
353                          tables     : String;
354                          columns    : String;
355                          conditions : String := blankstring;
356                          groupby    : String := blankstring;
357                          having     : String := blankstring;
358                          order      : String := blankstring;
359                          null_sort  : Null_Priority := native;
360                          limit      : Trax_ID := 0;
361                          offset     : Trax_ID := 0) return String
362   is
363      vanilla   : String := assembly_common_select
364        (distinct, tables, columns, conditions, groupby, having, order);
365   begin
366      if null_sort /= native then
367         driver.log_nominal
368           (category => execution,
369            message => CT.SUS ("Note that NULLS FIRST/LAST is not " &
370                "supported by MySQL so the null_sort setting is ignored"));
371      end if;
372      if limit > 0 then
373         if offset > 0 then
374            return vanilla & " LIMIT" & limit'Img & " OFFSET" & offset'Img;
375         else
376            return vanilla & " LIMIT" & limit'Img;
377         end if;
378      end if;
379      return vanilla;
380   end sql_assemble;
381
382
383   -----------------------------
384   --  call_stored_procedure  --
385   -----------------------------
386   function call_stored_procedure (driver           : MySQL_Driver;
387                                   stored_procedure : String;
388                                   proc_arguments   : String)
389                                   return ASM.MySQL_statement
390   is
391      SQL : String := "CALL " & stored_procedure & " (" & proc_arguments & ")";
392   begin
393      return driver.query (SQL);
394   end call_stored_procedure;
395
396
397end AdaBase.Driver.Base.MySQL;
398