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 | |
36 | MYRG_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 | |
182 | bad_children: |
183 | my_errno= HA_ERR_WRONG_MRG_TABLE_DEF; |
184 | err: |
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 | |
223 | MYRG_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 | |
379 | int 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 | |
494 | bad_children: |
495 | my_errno= HA_ERR_WRONG_MRG_TABLE_DEF; |
496 | err: |
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 | |
522 | int 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 | |