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 #include "ndb_local_schema.h"
26 
27 #ifndef MYSQL_SERVER
28 #define MYSQL_SERVER
29 #endif
30 
31 #include "sql_class.h"
32 #include "sql_table.h"
33 #include "mdl.h"
34 #include "log.h"
35 #include "table_trigger_dispatcher.h"
36 #include "sql_trigger.h"
37 #include "auth_common.h"              // check_readonly()
38 
39 static const char *ndb_ext=".ndb";
40 
41 
mdl_try_lock(void) const42 bool Ndb_local_schema::Base::mdl_try_lock(void) const
43 {
44   MDL_request_list mdl_requests;
45   MDL_request global_request;
46   MDL_request schema_request;
47   MDL_request mdl_request;
48 
49   MDL_REQUEST_INIT(&global_request,
50                    MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
51                    MDL_STATEMENT);
52   MDL_REQUEST_INIT(&schema_request,
53                    MDL_key::SCHEMA, m_db, "", MDL_INTENTION_EXCLUSIVE,
54                    MDL_TRANSACTION);
55   MDL_REQUEST_INIT(&mdl_request,
56                    MDL_key::TABLE, m_db, m_name, MDL_EXCLUSIVE,
57                    MDL_TRANSACTION);
58 
59   mdl_requests.push_front(&mdl_request);
60   mdl_requests.push_front(&schema_request);
61   mdl_requests.push_front(&global_request);
62 
63   if (m_thd->mdl_context.acquire_locks(&mdl_requests,
64                                        0 /* don't wait for lock */))
65   {
66     // Check that an error has been pushed to thd and then
67     // clear it since this is just a _try lock_
68     assert(m_thd->is_error());
69     m_thd->clear_error();
70 
71     log_warning("Failed to acquire metadata lock");
72 
73     return false;
74   }
75 
76   /*
77     Now when we have protection against concurrent change of read_only
78     option we can safely re-check its value.
79   */
80   if (check_readonly(m_thd, true))
81     return false;
82 
83   DBUG_PRINT("info", ("acquired metadata lock"));
84   return true;
85 }
86 
87 
mdl_unlock(void)88 void Ndb_local_schema::Base::mdl_unlock(void)
89 {
90   m_thd->mdl_context.release_transactional_locks();
91 }
92 
93 
log_warning(const char * fmt,...) const94 void Ndb_local_schema::Base::log_warning(const char* fmt, ...) const
95 {
96   char buf[1024];
97   va_list args;
98   va_start(args, fmt);
99   my_vsnprintf(buf, sizeof(buf), fmt, args);
100   va_end(args);
101 
102   if (m_push_warnings)
103   {
104     // Append the error which caused the error to thd's warning list
105     push_warning_printf(m_thd, Sql_condition::SL_NOTE,
106                         ER_GET_ERRMSG, "Ndb schema[%s.%s]: %s",
107                         m_db, m_name, buf);
108   }
109   else
110   {
111     // Print the warning to log file
112     sql_print_warning("Ndb schema[%s.%s]: %s",
113                       m_db, m_name, buf);
114   }
115 }
116 
117 
Base(THD * thd,const char * db,const char * name)118 Ndb_local_schema::Base::Base(THD* thd, const char* db, const char* name) :
119   m_thd(thd),
120   m_db(db), m_name(name)
121 {
122   /*
123     System(or daemon) threads report error to log file
124     all other threads use push_warning
125   */
126   m_push_warnings = (thd->get_command() != COM_DAEMON);
127 
128   m_have_mdl_lock= mdl_try_lock();
129 }
130 
131 
~Base()132 Ndb_local_schema::Base::~Base()
133 {
134   // Release MDL locks
135   if (m_have_mdl_lock)
136   {
137    DBUG_PRINT("info", ("releasing mdl lock"));
138     mdl_unlock();
139   }
140 }
141 
142 
143 bool
file_exists(const char * ext) const144 Ndb_local_schema::Table::file_exists(const char* ext) const
145 {
146   char buf[FN_REFLEN + 1];
147   build_table_filename(buf, sizeof(buf)-1,
148                        m_db, m_name, ext, 0);
149 
150   if (my_access(buf, F_OK))
151   {
152     DBUG_PRINT("info", ("File '%s' does not exist", buf));
153     return false;
154   }
155 
156   DBUG_PRINT("info", ("File '%s' exist", buf));
157   return true;
158 }
159 
160 
161 bool
remove_file(const char * ext) const162 Ndb_local_schema::Table::remove_file(const char* ext) const
163 {
164   char buf[FN_REFLEN + 1];
165   build_table_filename(buf, sizeof(buf)-1,
166                        m_db, m_name, ext, 0);
167 
168   int error = my_delete(buf, 0);
169   if (!error || errno == ENOENT)
170     return true;
171 
172   log_warning("Failed to remove file '%s', errno: %d", buf, errno);
173   return false;
174 }
175 
176 
177 bool
rename_file(const char * new_db,const char * new_name,const char * ext) const178 Ndb_local_schema::Table::rename_file(const char* new_db, const char* new_name,
179                              const char* ext) const
180 {
181   char from[FN_REFLEN + 1];
182   build_table_filename(from, sizeof(from)-1,
183                        m_db, m_name, ext, 0);
184 
185   char to[FN_REFLEN + 1];
186   build_table_filename(to, sizeof(to) - 1, new_db, new_name, ext, 0);
187 
188   int error = my_rename(from, to, 0);
189   if (!error)
190     return true;
191 
192   log_warning("Failed to rename file '%s' to '%s', errno: %d",
193             from, to, errno);
194   return false;
195 }
196 
197 
198 // Read the engine type from .frm and return true if it says NDB
199 bool
frm_engine_is_ndb(void) const200 Ndb_local_schema::Table::frm_engine_is_ndb(void) const
201 {
202   char buf[FN_REFLEN + 1];
203   build_table_filename(buf, sizeof(buf)-1,
204                        m_db, m_name, reg_ext, 0);
205 
206   legacy_db_type engine_type;
207   (void)dd_frm_type(m_thd, buf, &engine_type);
208   DBUG_PRINT("info", ("engine_type: %d", engine_type));
209 
210   return (engine_type == DB_TYPE_NDBCLUSTER);
211 }
212 
213 
Table(THD * thd,const char * db,const char * name)214 Ndb_local_schema::Table::Table(THD* thd,
215                                const char* db, const char* name) :
216   Ndb_local_schema::Base(thd, db, name),
217   m_ndb_file_exist(false),
218   m_has_triggers(false)
219 {
220   DBUG_ENTER("Ndb_local_table");
221   DBUG_PRINT("enter", ("name: '%s.%s'", db, name));
222 
223   // Check if .frm file exist
224   m_frm_file_exist = file_exists(reg_ext);
225   if (!m_frm_file_exist)
226   {
227     // Check for stray .ndb file
228     assert(!file_exists(ndb_ext));
229     DBUG_VOID_RETURN;
230   }
231 
232   // Check if .ndb file exist
233   m_ndb_file_exist = file_exists(ndb_ext);
234 
235   // Check if there are trigger files
236   m_has_triggers = file_exists(TRG_EXT);
237 
238   DBUG_VOID_RETURN;
239 }
240 
241 
242 bool
is_local_table(void) const243 Ndb_local_schema::Table::is_local_table(void) const
244 {
245   if (m_frm_file_exist && !m_ndb_file_exist)
246   {
247     // The .frm exist but no .ndb file , this is a "local" table
248 
249     // Double check that the engine type in .frm doesn't say NDB
250     assert(!frm_engine_is_ndb());
251 
252     return true;
253   }
254   return false;
255 }
256 
257 
258 void
remove_table(void) const259 Ndb_local_schema::Table::remove_table(void) const
260 {
261   (void)remove_file(reg_ext);
262   (void)remove_file(ndb_ext);
263 
264   if (m_has_triggers)
265   {
266     // Copy to buffers since 'drop_all_triggers' want char*
267     char db_name_buf[FN_REFLEN + 1], table_name_buf[FN_REFLEN + 1];
268     my_stpcpy(db_name_buf, m_db);
269     my_stpcpy(table_name_buf, m_name);
270 
271     if (drop_all_triggers(m_thd, db_name_buf, table_name_buf))
272     {
273       log_warning("Failed to drop all triggers");
274     }
275   }
276 }
277 
278 
279 void
rename_table(const char * new_db,const char * new_name) const280 Ndb_local_schema::Table::rename_table(const char* new_db,
281                                       const char* new_name) const
282 {
283   (void)rename_file(new_db, new_name, reg_ext);
284   (void)rename_file(new_db, new_name, ndb_ext);
285 
286   if (m_has_triggers)
287   {
288     if (!have_mdl_lock())
289     {
290       // change_trigger_table_name() requires an EXLUSIVE mdl lock
291       // so if the mdl lock was not aquired, skip this part
292       log_warning("Can't rename triggers, no mdl lock");
293     }
294     else
295     {
296       if (change_trigger_table_name(m_thd,
297                                     m_db, m_name, m_name,
298                                     new_db, new_name))
299       {
300         log_warning("Failed to rename all triggers");
301       }
302     }
303   }
304 }
305