1 /* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
22
23 /* open a MyISAM MERGE table */
24
25 #include "myrg_def.h"
26 #include <stddef.h>
27 #include <errno.h>
28
29 /*
30 open a MyISAM MERGE table
31 if handle_locking is 0 then exit with error if some table is locked
32 if handle_locking is 1 then wait if table is locked
33
34 NOTE: This function is not used in the MySQL server. It is for
35 MERGE use independent from MySQL. Currently there is some code
36 duplication between myrg_open() and myrg_parent_open() +
37 myrg_attach_children(). Please duplicate changes in these
38 functions or make common sub-functions.
39 */
40
myrg_open(const char * name,int mode,int handle_locking)41 MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking)
42 {
43 int save_errno,errpos=0;
44 uint files= 0, i, dir_length, length, key_parts= 0, min_keys= 0;
45 ulonglong file_offset=0;
46 char name_buff[FN_REFLEN*2],buff[FN_REFLEN],*end;
47 MYRG_INFO *m_info=0;
48 File fd;
49 IO_CACHE file;
50 MI_INFO *isam=0;
51 uint found_merge_insert_method= 0;
52 size_t name_buff_length;
53 my_bool bad_children= FALSE;
54 DBUG_ENTER("myrg_open");
55
56 memset(&file, 0, sizeof(file));
57 if ((fd= mysql_file_open(rg_key_file_MRG,
58 fn_format(name_buff, name, "", MYRG_NAME_EXT,
59 MY_UNPACK_FILENAME|MY_APPEND_EXT),
60 O_RDONLY | O_SHARE, MYF(0))) < 0)
61 goto err;
62 errpos=1;
63 if (init_io_cache(&file, fd, 4*IO_SIZE, READ_CACHE, 0, 0,
64 MYF(MY_WME | MY_NABP)))
65 goto err;
66 errpos=2;
67 dir_length=dirname_part(name_buff, name, &name_buff_length);
68 while ((length=my_b_gets(&file,buff,FN_REFLEN-1)))
69 {
70 if ((end=buff+length)[-1] == '\n')
71 end[-1]='\0';
72 if (buff[0] && buff[0] != '#')
73 files++;
74 }
75
76 my_b_seek(&file, 0);
77 while ((length=my_b_gets(&file,buff,FN_REFLEN-1)))
78 {
79 if ((end=buff+length)[-1] == '\n')
80 *--end='\0';
81 if (!buff[0])
82 continue; /* Skip empty lines */
83 if (buff[0] == '#')
84 {
85 if (!strncmp(buff+1,"INSERT_METHOD=",14))
86 { /* Lookup insert method */
87 int tmp= find_type(buff + 15, &merge_insert_method, FIND_TYPE_BASIC);
88 found_merge_insert_method = (uint) (tmp >= 0 ? tmp : 0);
89 }
90 continue; /* Skip comments */
91 }
92
93 if (!has_path(buff))
94 {
95 (void) strmake(name_buff+dir_length,buff,
96 sizeof(name_buff)-1-dir_length);
97 (void) cleanup_dirname(buff,name_buff);
98 }
99 else
100 fn_format(buff, buff, "", "", 0);
101 if (!(isam=mi_open(buff,mode,(handle_locking?HA_OPEN_WAIT_IF_LOCKED:0))))
102 {
103 if (handle_locking & HA_OPEN_FOR_REPAIR)
104 {
105 myrg_print_wrong_table(buff);
106 bad_children= TRUE;
107 continue;
108 }
109 goto bad_children;
110 }
111 if (!m_info) /* First file */
112 {
113 key_parts=isam->s->base.key_parts;
114 if (!(m_info= (MYRG_INFO*) my_malloc(rg_key_memory_MYRG_INFO,
115 sizeof(MYRG_INFO) +
116 files*sizeof(MYRG_TABLE) +
117 key_parts*sizeof(long),
118 MYF(MY_WME|MY_ZEROFILL))))
119 goto err;
120 assert(files);
121 m_info->open_tables=(MYRG_TABLE *) (m_info+1);
122 m_info->rec_per_key_part=(ulong *) (m_info->open_tables+files);
123 m_info->tables= files;
124 files= 0;
125 m_info->reclength=isam->s->base.reclength;
126 min_keys= isam->s->base.keys;
127 errpos=3;
128 }
129 m_info->open_tables[files].table= isam;
130 m_info->open_tables[files].file_offset=(my_off_t) file_offset;
131 file_offset+=isam->state->data_file_length;
132 files++;
133 if (m_info->reclength != isam->s->base.reclength)
134 {
135 if (handle_locking & HA_OPEN_FOR_REPAIR)
136 {
137 myrg_print_wrong_table(buff);
138 bad_children= TRUE;
139 continue;
140 }
141 goto bad_children;
142 }
143 m_info->options|= isam->s->options;
144 m_info->records+= isam->state->records;
145 m_info->del+= isam->state->del;
146 m_info->data_file_length+= isam->state->data_file_length;
147 if (min_keys > isam->s->base.keys)
148 min_keys= isam->s->base.keys;
149 for (i=0; i < key_parts; i++)
150 m_info->rec_per_key_part[i]+= (isam->s->state.rec_per_key_part[i] /
151 m_info->tables);
152 }
153
154 if (bad_children)
155 goto bad_children;
156 if (!m_info && !(m_info= (MYRG_INFO*) my_malloc(rg_key_memory_MYRG_INFO,
157 sizeof(MYRG_INFO),
158 MYF(MY_WME | MY_ZEROFILL))))
159 goto err;
160 /* Don't mark table readonly, for ALTER TABLE ... UNION=(...) to work */
161 m_info->options&= ~(HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA);
162 m_info->merge_insert_method= found_merge_insert_method;
163
164 if (sizeof(my_off_t) == 4 && file_offset > (ulonglong) (ulong) ~0L)
165 {
166 set_my_errno(HA_ERR_RECORD_FILE_FULL);
167 goto err;
168 }
169 m_info->keys= min_keys;
170 memset(&m_info->by_key, 0, sizeof(m_info->by_key));
171
172 /* this works ok if the table list is empty */
173 m_info->end_table=m_info->open_tables+files;
174 m_info->last_used_table=m_info->open_tables;
175 m_info->children_attached= TRUE;
176
177 (void) mysql_file_close(fd, MYF(0));
178 end_io_cache(&file);
179 mysql_mutex_init(rg_key_mutex_MYRG_INFO_mutex,
180 &m_info->mutex, MY_MUTEX_INIT_FAST);
181 m_info->open_list.data=(void*) m_info;
182 mysql_mutex_lock(&THR_LOCK_open);
183 myrg_open_list=list_add(myrg_open_list,&m_info->open_list);
184 mysql_mutex_unlock(&THR_LOCK_open);
185 DBUG_RETURN(m_info);
186
187 bad_children:
188 set_my_errno(HA_ERR_WRONG_MRG_TABLE_DEF);
189 err:
190 save_errno=my_errno();
191 switch (errpos) {
192 case 3:
193 while (files)
194 (void) mi_close(m_info->open_tables[--files].table);
195 my_free(m_info);
196 /* Fall through */
197 case 2:
198 end_io_cache(&file);
199 /* Fall through */
200 case 1:
201 (void) mysql_file_close(fd, MYF(0));
202 }
203 set_my_errno(save_errno);
204 DBUG_RETURN (NULL);
205 }
206
207
208 /**
209 @brief Open parent table of a MyISAM MERGE table.
210
211 @detail Open MERGE meta file to get the table name paths for the child
212 tables. Count the children. Allocate and initialize MYRG_INFO
213 structure. Call a callback function for each child table.
214
215 @param[in] parent_name merge table name path as "database/table"
216 @param[in] callback function to call for each child table
217 @param[in] callback_param data pointer to give to the callback
218
219 @return MYRG_INFO pointer
220 @retval != NULL OK
221 @retval NULL Error
222
223 @note: Currently there is some code duplication between myrg_open()
224 and myrg_parent_open() + myrg_attach_children(). Please duplicate
225 changes in these functions or make common sub-functions.
226 */
227
myrg_parent_open(const char * parent_name,int (* callback)(void *,const char *),void * callback_param)228 MYRG_INFO *myrg_parent_open(const char *parent_name,
229 int (*callback)(void*, const char*),
230 void *callback_param)
231 {
232 MYRG_INFO *m_info= NULL;
233 int rc;
234 int errpos;
235 int save_errno;
236 int insert_method;
237 uint length;
238 uint child_count;
239 File fd;
240 IO_CACHE file_cache;
241 char parent_name_buff[FN_REFLEN * 2];
242 char child_name_buff[FN_REFLEN];
243 DBUG_ENTER("myrg_parent_open");
244
245 rc= 1;
246 errpos= 0;
247 memset(&file_cache, 0, sizeof(file_cache));
248
249 /* Open MERGE meta file. */
250 if ((fd= mysql_file_open(rg_key_file_MRG,
251 fn_format(parent_name_buff, parent_name,
252 "", MYRG_NAME_EXT,
253 MY_UNPACK_FILENAME|MY_APPEND_EXT),
254 O_RDONLY | O_SHARE, MYF(0))) < 0)
255 goto err; /* purecov: inspected */
256 errpos= 1;
257
258 if (init_io_cache(&file_cache, fd, 4 * IO_SIZE, READ_CACHE, 0, 0,
259 MYF(MY_WME | MY_NABP)))
260 goto err; /* purecov: inspected */
261 errpos= 2;
262
263 /* Count children. Determine insert method. */
264 child_count= 0;
265 insert_method= 0;
266 while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
267 {
268 /* Remove line terminator. */
269 if (child_name_buff[length - 1] == '\n')
270 child_name_buff[--length]= '\0';
271
272 /* Skip empty lines. */
273 if (!child_name_buff[0])
274 continue; /* purecov: inspected */
275
276 /* Skip comments, but evaluate insert method. */
277 if (child_name_buff[0] == '#')
278 {
279 if (!strncmp(child_name_buff + 1, "INSERT_METHOD=", 14))
280 {
281 /* Compare buffer with global methods list: merge_insert_method. */
282 insert_method= find_type(child_name_buff + 15,
283 &merge_insert_method, FIND_TYPE_BASIC);
284 }
285 continue;
286 }
287
288 /* Count the child. */
289 child_count++;
290 }
291
292 /* Allocate MERGE parent table structure. */
293 if (!(m_info= (MYRG_INFO*) my_malloc(rg_key_memory_MYRG_INFO,
294 sizeof(MYRG_INFO) +
295 child_count * sizeof(MYRG_TABLE),
296 MYF(MY_WME | MY_ZEROFILL))))
297 goto err; /* purecov: inspected */
298 errpos= 3;
299 m_info->open_tables= (MYRG_TABLE*) (m_info + 1);
300 m_info->tables= child_count;
301 m_info->merge_insert_method= insert_method > 0 ? insert_method : 0;
302 /* This works even if the table list is empty. */
303 m_info->end_table= m_info->open_tables + child_count;
304 if (!child_count)
305 {
306 /* Do not attach/detach an empty child list. */
307 m_info->children_attached= TRUE;
308 }
309
310 /* Call callback for each child. */
311 my_b_seek(&file_cache, 0);
312 while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
313 {
314 /* Remove line terminator. */
315 if (child_name_buff[length - 1] == '\n')
316 child_name_buff[--length]= '\0';
317
318 /* Skip empty lines and comments. */
319 if (!child_name_buff[0] || (child_name_buff[0] == '#'))
320 continue;
321
322 DBUG_PRINT("info", ("child: '%s'", child_name_buff));
323
324 /* Callback registers child with handler table. */
325 if ((rc= (*callback)(callback_param, child_name_buff)))
326 goto err; /* purecov: inspected */
327 }
328
329 end_io_cache(&file_cache);
330 (void) mysql_file_close(fd, MYF(0));
331 mysql_mutex_init(rg_key_mutex_MYRG_INFO_mutex,
332 &m_info->mutex, MY_MUTEX_INIT_FAST);
333
334 m_info->open_list.data= (void*) m_info;
335 mysql_mutex_lock(&THR_LOCK_open);
336 myrg_open_list= list_add(myrg_open_list, &m_info->open_list);
337 mysql_mutex_unlock(&THR_LOCK_open);
338
339 DBUG_RETURN(m_info);
340
341 /* purecov: begin inspected */
342 err:
343 save_errno= my_errno();
344 switch (errpos) {
345 case 3:
346 my_free(m_info);
347 /* Fall through */
348 case 2:
349 end_io_cache(&file_cache);
350 /* Fall through */
351 case 1:
352 (void) mysql_file_close(fd, MYF(0));
353 }
354 set_my_errno(save_errno);
355 DBUG_RETURN (NULL);
356 /* purecov: end */
357 }
358
359
360 /**
361 @brief Attach children to a MyISAM MERGE parent table.
362
363 @detail Call a callback function for each child table.
364 The callback returns the MyISAM table handle of the child table.
365 Check table definition match.
366
367 @param[in] m_info MERGE parent table structure
368 @param[in] handle_locking if contains HA_OPEN_FOR_REPAIR, warn about
369 incompatible child tables, but continue
370 @param[in] callback function to call for each child table
371 @param[in] callback_param data pointer to give to the callback
372 @param[in] need_compat_check pointer to ha_myisammrg::need_compat_check
373 (we need this one to decide if previously
374 allocated buffers can be reused).
375
376 @return status
377 @retval 0 OK
378 @retval != 0 Error
379
380 @note: Currently there is some code duplication between myrg_open()
381 and myrg_parent_open() + myrg_attach_children(). Please duplicate
382 changes in these functions or make common sub-functions.
383 */
384
myrg_attach_children(MYRG_INFO * m_info,int handle_locking,MI_INFO * (* callback)(void *),void * callback_param,my_bool * need_compat_check)385 int myrg_attach_children(MYRG_INFO *m_info, int handle_locking,
386 MI_INFO *(*callback)(void*),
387 void *callback_param, my_bool *need_compat_check)
388 {
389 ulonglong file_offset;
390 MI_INFO *myisam;
391 int errpos;
392 int save_errno;
393 uint idx;
394 uint child_nr;
395 uint key_parts= 0;
396 uint min_keys;
397 my_bool bad_children= FALSE;
398 my_bool first_child= TRUE;
399 DBUG_ENTER("myrg_attach_children");
400 DBUG_PRINT("myrg", ("handle_locking: %d", handle_locking));
401
402 /*
403 This function can be called while another thread is trying to abort
404 locks of this MERGE table. If the processor reorders instructions or
405 write to memory, 'children_attached' could be set before
406 'open_tables' has all the pointers to the children. Use of a mutex
407 here and in ha_myisammrg::store_lock() forces consistent data.
408 */
409 mysql_mutex_lock(&m_info->mutex);
410 errpos= 0;
411 file_offset= 0;
412 min_keys= 0;
413 for (child_nr= 0; child_nr < m_info->tables; child_nr++)
414 {
415 if (! (myisam= (*callback)(callback_param)))
416 {
417 if (handle_locking & HA_OPEN_FOR_REPAIR)
418 {
419 /* An appropriate error should've been already pushed by callback. */
420 bad_children= TRUE;
421 continue;
422 }
423 goto bad_children;
424 }
425
426 DBUG_PRINT("myrg", ("child_nr: %u table: '%s'",
427 child_nr, myisam->filename));
428
429 /* Special handling when the first child is attached. */
430 if (first_child)
431 {
432 first_child= FALSE;
433 m_info->reclength= myisam->s->base.reclength;
434 min_keys= myisam->s->base.keys;
435 key_parts= myisam->s->base.key_parts;
436 if (*need_compat_check && m_info->rec_per_key_part)
437 {
438 my_free(m_info->rec_per_key_part);
439 m_info->rec_per_key_part= NULL;
440 }
441 if (!m_info->rec_per_key_part)
442 {
443 if(!(m_info->rec_per_key_part= (ulong*)
444 my_malloc(rg_key_memory_MYRG_INFO,
445 key_parts * sizeof(long), MYF(MY_WME))))
446 goto err; /* purecov: inspected */
447 errpos= 1;
448 }
449 memset(m_info->rec_per_key_part, 0, key_parts * sizeof(long));
450 }
451
452 /* Add MyISAM table info. */
453 m_info->open_tables[child_nr].table= myisam;
454 m_info->open_tables[child_nr].file_offset= (my_off_t) file_offset;
455 file_offset+= myisam->state->data_file_length;
456
457 /* Check table definition match. */
458 if (m_info->reclength != myisam->s->base.reclength)
459 {
460 DBUG_PRINT("error", ("definition mismatch table: '%s' repair: %d",
461 myisam->filename,
462 (handle_locking & HA_OPEN_FOR_REPAIR)));
463 if (handle_locking & HA_OPEN_FOR_REPAIR)
464 {
465 myrg_print_wrong_table(myisam->filename);
466 bad_children= TRUE;
467 continue;
468 }
469 goto bad_children;
470 }
471
472 m_info->options|= myisam->s->options;
473 m_info->records+= myisam->state->records;
474 m_info->del+= myisam->state->del;
475 m_info->data_file_length+= myisam->state->data_file_length;
476 if (min_keys > myisam->s->base.keys)
477 min_keys= myisam->s->base.keys; /* purecov: inspected */
478 for (idx= 0; idx < key_parts; idx++)
479 m_info->rec_per_key_part[idx]+= (myisam->s->state.rec_per_key_part[idx] /
480 m_info->tables);
481 }
482
483 if (bad_children)
484 goto bad_children;
485
486 if (sizeof(my_off_t) == 4 && file_offset > (ulonglong) (ulong) ~0L)
487 {
488 set_my_errno(HA_ERR_RECORD_FILE_FULL);
489 goto err;
490 }
491 /* Don't mark table readonly, for ALTER TABLE ... UNION=(...) to work */
492 m_info->options&= ~(HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA);
493 m_info->keys= min_keys;
494 m_info->last_used_table= m_info->open_tables;
495 m_info->children_attached= TRUE;
496 mysql_mutex_unlock(&m_info->mutex);
497 DBUG_RETURN(0);
498
499 bad_children:
500 set_my_errno(HA_ERR_WRONG_MRG_TABLE_DEF);
501 err:
502 save_errno= my_errno();
503 switch (errpos) {
504 case 1:
505 my_free(m_info->rec_per_key_part);
506 m_info->rec_per_key_part= NULL;
507 }
508 mysql_mutex_unlock(&m_info->mutex);
509 set_my_errno(save_errno);
510 DBUG_RETURN(1);
511 }
512
513
514 /**
515 @brief Detach children from a MyISAM MERGE parent table.
516
517 @param[in] m_info MERGE parent table structure
518
519 @note Detach must not touch the children in any way.
520 They may have been closed at ths point already.
521 All references to the children should be removed.
522
523 @return status
524 @retval 0 OK
525 */
526
myrg_detach_children(MYRG_INFO * m_info)527 int myrg_detach_children(MYRG_INFO *m_info)
528 {
529 DBUG_ENTER("myrg_detach_children");
530 /* For symmetry with myrg_attach_children() we use the mutex here. */
531 mysql_mutex_lock(&m_info->mutex);
532 if (m_info->tables)
533 {
534 /* Do not attach/detach an empty child list. */
535 m_info->children_attached= FALSE;
536 memset(m_info->open_tables, 0, m_info->tables * sizeof(MYRG_TABLE));
537 }
538 m_info->records= 0;
539 m_info->del= 0;
540 m_info->data_file_length= 0;
541 m_info->options= 0;
542 mysql_mutex_unlock(&m_info->mutex);
543 DBUG_RETURN(0);
544 }
545
546