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