1 /*
2    Copyright (c) 2011, 2021, Oracle and/or its affiliates.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License, version 2.0,
6    as published by the Free Software Foundation.
7 
8    This program is also distributed with certain software (including
9    but not limited to OpenSSL) that is licensed under separate terms,
10    as designated in a particular file or component or in included license
11    documentation.  The authors of MySQL hereby grant you an additional
12    permission to link the program and your derivative works with the
13    separately licensed software that they have included with MySQL.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License, version 2.0, for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 #ifndef MYSQL_SERVER
26 #define MYSQL_SERVER
27 #endif
28 
29 #include "my_global.h"
30 #include "ndb_local_connection.h"
31 #include "sql_class.h"
32 #include "sql_prepare.h"
33 #include "log.h"
34 
Ndb_local_connection(THD * thd_arg)35 Ndb_local_connection::Ndb_local_connection(THD* thd_arg):
36   m_thd(thd_arg)
37 {
38   assert(thd_arg);
39 
40   /*
41     System(or daemon) threads report error to log file
42     all other threads use push_warning
43   */
44   m_push_warnings = (thd_arg->get_command() != COM_DAEMON);
45 }
46 
47 
48 static inline bool
should_ignore_error(const uint * ignore_error_list,uint error)49 should_ignore_error(const uint* ignore_error_list, uint error)
50 {
51   DBUG_ENTER("should_ignore_error");
52   DBUG_PRINT("enter", ("error: %u", error));
53   const uint* ignore_error = ignore_error_list;
54   while(*ignore_error)
55   {
56     DBUG_PRINT("info", ("ignore_error: %u", *ignore_error));
57     if (*ignore_error == error)
58       DBUG_RETURN(true);
59     ignore_error++;
60   }
61   DBUG_PRINT("info", ("Don't ignore error"));
62   DBUG_RETURN(false);
63 }
64 
65 
66 class Suppressor {
67 public:
~Suppressor()68   virtual ~Suppressor() {}
69   virtual bool should_ignore_error(Ed_connection& con) const = 0;
70 };
71 
72 
73 bool
execute_query(MYSQL_LEX_STRING sql_text,const uint * ignore_mysql_errors,const Suppressor * suppressor)74 Ndb_local_connection::execute_query(MYSQL_LEX_STRING sql_text,
75                                     const uint* ignore_mysql_errors,
76                                     const Suppressor* suppressor)
77 {
78   DBUG_ENTER("Ndb_local_connection::execute_query");
79   Ed_connection con(m_thd);
80   if (con.execute_direct(sql_text))
81   {
82     /* Error occured while executing the query */
83     const uint last_errno = con.get_last_errno();
84     assert(last_errno); // last_errno must have been set
85     const char* last_errmsg = con.get_last_error();
86 
87     DBUG_PRINT("error", ("Query '%s' failed, error: '%d: %s'",
88                          sql_text.str,
89                          last_errno, last_errmsg));
90 
91     // catch some SQL parse errors in debug
92     assert(last_errno != ER_PARSE_ERROR ||
93            last_errno != ER_EMPTY_QUERY);
94 
95     /* Check if this is a MySQL level errors that should be ignored */
96     if (ignore_mysql_errors &&
97         should_ignore_error(ignore_mysql_errors, last_errno))
98     {
99       /* MySQL level error suppressed -> return success */
100       m_thd->clear_error();
101       DBUG_RETURN(false);
102     }
103 
104     /*
105       Call the suppressor to check if it want to silence
106       this error
107     */
108      if (suppressor &&
109          suppressor->should_ignore_error(con))
110     {
111       /* Error suppressed -> return sucess */
112       m_thd->clear_error();
113       DBUG_RETURN(false);
114     }
115 
116     if (m_push_warnings)
117     {
118       // Append the error which caused the error to thd's warning list
119       push_warning(m_thd, Sql_condition::SL_WARNING,
120                    last_errno, last_errmsg);
121     }
122     else
123     {
124       // Print the error to log file
125       sql_print_error("NDB: Query '%s' failed, error: %d: %s",
126                       sql_text.str,
127                       last_errno, last_errmsg);
128     }
129 
130     DBUG_RETURN(true);
131   }
132 
133   // Query returned ok, thd should have no error
134   assert(!m_thd->is_error());
135 
136   DBUG_RETURN(false); // Success
137 }
138 
139 
140 /*
141   Execute the query with even higher isolation than what execute_query
142   provides to avoid that for example THD's status variables are changed
143 */
144 
145 bool
execute_query_iso(MYSQL_LEX_STRING sql_text,const uint * ignore_mysql_errors,const Suppressor * suppressor)146 Ndb_local_connection::execute_query_iso(MYSQL_LEX_STRING sql_text,
147                                         const uint* ignore_mysql_errors,
148                                         const Suppressor* suppressor)
149 {
150   /* Don't allow queries to affect THD's status variables */
151   struct system_status_var save_thd_status_var= m_thd->status_var;
152 
153   /* Check modified_non_trans_table is false(check if actually needed) */
154   assert(!m_thd->get_transaction()->has_modified_non_trans_table(
155     Transaction_ctx::STMT));
156 
157 #if 0
158   /*
159     Saves pseudo_thread_id and assign a "random" thread id from
160     the global "thread_id" variable without taking a lock
161     This looks like a copy and paste bug from some THD:: function
162     should probably be assigned thd->thread_id, if the pseudo_thread_id
163     need to be changed at all..
164   */
165   ulong save_thd_thread_id= m_thd->variables.pseudo_thread_id;
166   m_thd->variables.pseudo_thread_id = thread_id;
167 #endif
168 
169   /* Turn off binlogging */
170   ulonglong save_thd_options= m_thd->variables.option_bits;
171   assert(sizeof(save_thd_options) == sizeof(m_thd->variables.option_bits));
172   m_thd->variables.option_bits&= ~OPTION_BIN_LOG;
173 
174   bool result = execute_query(sql_text,
175                               ignore_mysql_errors,
176                               suppressor);
177 
178   /* Restore THD settings */
179   m_thd->variables.option_bits= save_thd_options;
180 #if 0
181   m_thd->variables.pseudo_thread_id = save_thd_thread_id;
182 #endif
183   m_thd->status_var= save_thd_status_var;
184 
185   return result;
186 }
187 
188 
189 bool
truncate_table(const char * db,size_t db_length,const char * table,size_t table_length,bool ignore_no_such_table)190 Ndb_local_connection::truncate_table(const char* db, size_t db_length,
191                                      const char* table, size_t table_length,
192                                      bool ignore_no_such_table)
193 {
194   DBUG_ENTER("Ndb_local_connection::truncate_table");
195   DBUG_PRINT("enter", ("db: '%s', table: '%s'", db, table));
196 
197   // Create the SQL string
198   String sql_text((uint32)(db_length + table_length + 100));
199   sql_text.append(STRING_WITH_LEN("TRUNCATE TABLE "));
200   sql_text.append(db, (uint32)db_length);
201   sql_text.append(STRING_WITH_LEN("."));
202   sql_text.append(table, (uint32)table_length);
203 
204   // Setup list of errors to ignore
205   uint ignore_mysql_errors[2] = {0, 0};
206   if (ignore_no_such_table)
207     ignore_mysql_errors[0] = ER_NO_SUCH_TABLE;
208 
209   DBUG_RETURN(execute_query_iso(sql_text.lex_string(),
210                                 ignore_mysql_errors,
211                                 NULL));
212 }
213 
214 
215 bool
flush_table(const char * db,size_t db_length,const char * table,size_t table_length)216 Ndb_local_connection::flush_table(const char* db, size_t db_length,
217                                   const char* table, size_t table_length)
218 {
219   DBUG_ENTER("Ndb_local_connection::flush_table");
220   DBUG_PRINT("enter", ("db: '%s', table: '%s'", db, table));
221 
222   // Create the SQL string
223   String sql_text((uint32)(db_length + table_length + 100));
224   sql_text.append(STRING_WITH_LEN("FLUSH TABLES "));
225   sql_text.append(db, (uint32)db_length);
226   sql_text.append(STRING_WITH_LEN("."));
227   sql_text.append(table, (uint32)table_length);
228 
229   DBUG_RETURN(execute_query_iso(sql_text.lex_string(),
230                                 NULL,
231                                 NULL));
232 }
233 
234 
235 bool
delete_rows(const char * db,size_t db_length,const char * table,size_t table_length,bool ignore_no_such_table,...)236 Ndb_local_connection::delete_rows(const char* db, size_t db_length,
237                                   const char* table, size_t table_length,
238                                   bool ignore_no_such_table,
239                                   ...)
240 {
241   DBUG_ENTER("Ndb_local_connection::truncate_table");
242   DBUG_PRINT("enter", ("db: '%s', table: '%s'", db, table));
243 
244   // Create the SQL string
245   String sql_text((uint32)(db_length + table_length + 100));
246   sql_text.append(STRING_WITH_LEN("DELETE FROM "));
247   sql_text.append(db, (uint32)db_length);
248   sql_text.append(STRING_WITH_LEN("."));
249   sql_text.append(table, (uint32)table_length);
250   sql_text.append(" WHERE ");
251 
252   va_list args;
253   va_start(args, ignore_no_such_table);
254 
255   // Append var args strings until ending NULL as WHERE clause
256   const char* arg;
257   bool empty_where = true;
258   while ((arg= va_arg(args, char *)))
259   {
260     sql_text.append(arg);
261     empty_where = false;
262   }
263 
264   va_end(args);
265 
266   if (empty_where)
267     sql_text.append("1=1");
268 
269   // Setup list of errors to ignore
270   uint ignore_mysql_errors[2] = {0, 0};
271   if (ignore_no_such_table)
272     ignore_mysql_errors[0] = ER_NO_SUCH_TABLE;
273 
274   DBUG_RETURN(execute_query_iso(sql_text.lex_string(),
275                                 ignore_mysql_errors,
276                                 NULL));
277 }
278 
279 
280 class Create_sys_table_suppressor : public Suppressor
281 {
282 public:
~Create_sys_table_suppressor()283   virtual ~Create_sys_table_suppressor() {}
should_ignore_error(Ed_connection & con) const284   virtual bool should_ignore_error(Ed_connection& con) const
285   {
286     const uint last_errno = con.get_last_errno();
287     const char* last_errmsg = con.get_last_error();
288     DBUG_ENTER("Create_sys_table_suppressor::should_ignore_error");
289     DBUG_PRINT("enter", ("last_errno: %d, last_errmsg: '%s'",
290                          last_errno, last_errmsg));
291 
292     if (last_errno == ER_CANT_CREATE_TABLE)
293     {
294       /*
295         The CREATE TABLE failed late and it was classifed as a
296         'Can't create table' error.
297       */
298 
299       /*
300         Error message always end with " %d)" in all languages. Find last
301         space and convert number from there
302       */
303       const char* last_space = strrchr(last_errmsg, ' ');
304       DBUG_PRINT("info", ("last_space: '%s'", last_space));
305       if (!last_space)
306       {
307         // Could not find last space, parse error
308         assert(false);
309         DBUG_RETURN(false); // Don't suppress
310       }
311 
312       int error;
313       if (sscanf(last_space, " %d)", &error) != 1)
314       {
315         // Not a number here, parse error
316         assert(false);
317         DBUG_RETURN(false); // Don't suppress
318       }
319       DBUG_PRINT("info", ("error: %d", error));
320 
321       switch (error)
322       {
323         case HA_ERR_TABLE_EXIST:
324         {
325           /*
326             The most common error is that NDB returns error 721
327             which means 'No such table' and the error is automatically
328             mapped to MySQL error code ER_TABLE_EXISTS_ERROR
329 
330             This is most likley caused by another MySQL Server trying
331             to create the same table inbetween the check if table
332             exists(on local disk and in storage engine) and the actual
333             create.
334           */
335           DBUG_RETURN(true); // Suppress
336           break;
337         }
338 
339         case 701: // System busy with other schema operation
340         case 711: // System busy with node restart, no schema operations
341         case 702: // Request to non-master(should never pop up to api)
342         {
343           /* Different errors from NDB, that just need to be retried later */
344           DBUG_RETURN(true); // Suppress
345           break;
346         }
347 
348         case 4009: // Cluster failure
349         case HA_ERR_NO_CONNECTION: // 4009 auto mapped to this error
350         {
351           /*
352             No connection to cluster, don't spam error log with
353             failed to create ndb_xx tables
354           */
355           DBUG_RETURN(true); // Suppress
356           break;
357         }
358       }
359     }
360     DBUG_PRINT("info", ("Don't ignore error"));
361     DBUG_RETURN(false); // Don't suppress
362   }
363 };
364 
365 
366 bool
create_sys_table(const char * db,size_t db_length,const char * table,size_t table_length,bool create_if_not_exists,const char * create_definitions,const char * create_options)367 Ndb_local_connection::create_sys_table(const char* db, size_t db_length,
368                                        const char* table, size_t table_length,
369                                        bool create_if_not_exists,
370                                        const char* create_definitions,
371                                        const char* create_options)
372 {
373   DBUG_ENTER("Ndb_local_connection::create_table");
374   DBUG_PRINT("enter", ("db: '%s', table: '%s'", db, table));
375 
376   // Create the SQL string
377   String sql_text(512);
378   sql_text.append(STRING_WITH_LEN("CREATE TABLE "));
379 
380   if (create_if_not_exists)
381     sql_text.append(STRING_WITH_LEN("IF NOT EXISTS "));
382   sql_text.append(db, (uint32)db_length);
383   sql_text.append(STRING_WITH_LEN("."));
384   sql_text.append(table, (uint32)table_length);
385 
386   sql_text.append(STRING_WITH_LEN(" ( "));
387   sql_text.append(create_definitions);
388   sql_text.append(STRING_WITH_LEN(" ) "));
389   sql_text.append(create_options);
390 
391   // List of errors to ignore
392   uint ignore_mysql_errors[2] = {ER_TABLE_EXISTS_ERROR, 0};
393 
394   /*
395     This is the only place where an error is suppressed
396     based one the original NDB error, wich is extracted
397     by parsing the error string, use a special suppressor
398   */
399   Create_sys_table_suppressor suppressor;
400 
401   DBUG_RETURN(execute_query_iso(sql_text.lex_string(),
402                                 ignore_mysql_errors,
403                                 &suppressor));
404 }
405 
406 
407 bool
raw_run_query(const char * query,size_t query_length,const int * suppress_errors)408 Ndb_local_connection::raw_run_query(const char* query, size_t query_length,
409                                     const int* suppress_errors)
410 {
411   DBUG_ENTER("Ndb_local_connection::raw_run_query");
412 
413   LEX_STRING sql_text = { (char*)query, query_length };
414 
415   DBUG_RETURN(execute_query_iso(sql_text,
416                                 (const uint*)suppress_errors,
417                                 NULL));
418 }
419 
420