Merge lp:~akopytov/percona-server/bugs-1039536-1081003-5.1 into lp:percona-server/5.1

Proposed by Alexey Kopytov
Status: Merged
Approved by: Laurynas Biveinis
Approved revision: no longer in the source branch.
Merged at revision: 501
Proposed branch: lp:~akopytov/percona-server/bugs-1039536-1081003-5.1
Merge into: lp:percona-server/5.1
Diff against target: 339 lines (+222/-35)
3 files modified
Percona-Server/client/mysqldump.c (+108/-35)
Percona-Server/mysql-test/r/percona_mysqldump_innodb_optimize_keys.result (+74/-0)
Percona-Server/mysql-test/t/percona_mysqldump_innodb_optimize_keys.test (+40/-0)
To merge this branch: bzr merge lp:~akopytov/percona-server/bugs-1039536-1081003-5.1
Reviewer Review Type Date Requested Status
Laurynas Biveinis (community) Approve
Review via email: mp+135091@code.launchpad.net

Description of the change

Bug #1039536: mysqldump --innodb-optimize-keys can generate invalid
              table definitions
Bug #1081003: mysqldump --innodb-optimize-keys handles AUTO_INCREMENT
              columns inefficiently

The problem in bug #1039536 was that mysqldump --innodb-optimize-keys
did not handle composite indexes correctly when verifying if the
optimization is applicable with respect to AUTO_INCREMENT columns. When
an AUTO_INCREMENT column was encountered in the SHOW CREATE TABLE
output, the column was marked so that subsequent index specifications
involving that column would not be used in deferred index creation
mechanism, as MySQL does not allow creating tables with unindexed
AUTO_INCREMENT columns. However, the code checking if an index
specification involves a previously marked AUTO_INCREMENT column failed
to handle composite keys correctly, so those keys were optimized away
resulting in an invalid table definition.

A closely related problem in bug #1081003 was that even in cases where
indexes with AUTO_INCREMENT columns where correctly detected, mysqldump
prevented all such keys from optimization, even though it is sufficient
to skip just one (e.g. the first one).

Fixed by refactoring the AUTO_INCREMENT handling code in mysqldump
--innodb-optimize-keys to:

- process composite keys correctly

- prevent only the first key indexing an AUTO_INCREMENT columns from
  optimization

- use a simple pointer instead of the hash table to keep track of
  AUTO_INCREMENT column with a simple pointer, as the server only allows
  one such column per table anyway

To post a comment you must log in.
Revision history for this message
Alexey Kopytov (akopytov) wrote :
Revision history for this message
Laurynas Biveinis (laurynas-biveinis) wrote :

Looks good.

Were there any serious issues, I'd also suggest addressing two minor comments:
1) strncmp("foo", blah, sizeof("foo") - 1)) effectively does string prefix comparison, as sizeof - 1 removes the trailing NUL from comparison. Which is probably unintentional, but no real difference.
2) instead of a trinary operator at contains_autoinc_column call I'd add if (type == KEY_TYPE_NONE) return FALSE; to the function itself, seems more natural to me.

review: Approve
Revision history for this message
Alexey Kopytov (akopytov) wrote :

Laurynas,

The prefix comparison was intentional, so removing -1 from "sizeof(...) - 1" would break the check.

As to returning FALSE for KEY_TYPE_NONE in contains_autoinc_column() -- yes, it might be implemented like that, but I wanted to assert the function is never called with a wrong key type.
The general principle in the MySQL code is that callers are responsible for validating arguments to a function, not the callee.

Revision history for this message
Laurynas Biveinis (laurynas-biveinis) wrote :

Right, it must be the prefix comparison, sorry.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Percona-Server/client/mysqldump.c'
--- Percona-Server/client/mysqldump.c 2012-08-20 03:14:02 +0000
+++ Percona-Server/client/mysqldump.c 2012-11-20 09:37:22 +0000
@@ -85,6 +85,13 @@
85#define IGNORE_DATA 0x01 /* don't dump data for this table */85#define IGNORE_DATA 0x01 /* don't dump data for this table */
86#define IGNORE_INSERT_DELAYED 0x02 /* table doesn't support INSERT DELAYED */86#define IGNORE_INSERT_DELAYED 0x02 /* table doesn't support INSERT DELAYED */
8787
88typedef enum {
89 KEY_TYPE_NONE,
90 KEY_TYPE_PRIMARY,
91 KEY_TYPE_UNIQUE,
92 KEY_TYPE_NON_UNIQUE
93} key_type_t;
94
88/* general_log or slow_log tables under mysql database */95/* general_log or slow_log tables under mysql database */
89static inline my_bool general_log_or_slow_log_tables(const char *db, 96static inline my_bool general_log_or_slow_log_tables(const char *db,
90 const char *table)97 const char *table)
@@ -2460,20 +2467,62 @@
2460}2467}
24612468
2462/*2469/*
2463 Parse the specified key definition string and check if the key indexes2470 Parse the specified key definition string and check if the key contains an
2464 any of the columns from ignored_columns.2471 AUTO_INCREMENT column as the first key part. We only check for the first key
2472 part, because unlike MyISAM, InnoDB does not allow the AUTO_INCREMENT column
2473 as a secondary key column, i.e. the AUTO_INCREMENT column would not be
2474 considered indexed for such key specification.
2465*/2475*/
2466static my_bool contains_ignored_column(HASH *ignored_columns, char *keydef)2476static my_bool contains_autoinc_column(const char *autoinc_column,
2477 const char *keydef,
2478 key_type_t type)
2467{2479{
2468 char *leftp, *rightp;2480 char *from, *to;
24692481 uint idnum;
2470 if ((leftp = strchr(keydef, '(')) &&2482
2471 (rightp = strchr(leftp, ')')) &&2483 DBUG_ASSERT(type != KEY_TYPE_NONE);
2472 rightp > leftp + 3 && /* (`...`) */2484
2473 leftp[1] == '`' &&2485 if (autoinc_column == NULL || !(from= strchr(keydef, '`')))
2474 rightp[-1] == '`' &&2486 return FALSE;
2475 hash_search(ignored_columns, (uchar *) leftp + 2, rightp - leftp - 3))2487
2476 return TRUE;2488 to= from;
2489 idnum= 0;
2490
2491 while ((to= strchr(to + 1, '`')))
2492 {
2493 /*
2494 Double backticks represent a backtick in identifier, rather than a quote
2495 character.
2496 */
2497 if (to[1] == '`')
2498 {
2499 to++;
2500 continue;
2501 }
2502
2503 if (to <= from + 1)
2504 break; /* Broken key definition */
2505
2506 idnum++;
2507
2508 /*
2509 Skip the check if it's the first identifier and we are processing a
2510 secondary key.
2511 */
2512 if ((type == KEY_TYPE_PRIMARY || idnum != 1) &&
2513 !strncmp(autoinc_column, from + 1, to - from - 1))
2514 return TRUE;
2515
2516 /*
2517 Check only the first (for PRIMARY KEY) or the second (for secondary keys)
2518 quoted identifier.
2519 */
2520 if ((idnum == 1 + test(type != KEY_TYPE_PRIMARY)) ||
2521 !(from= strchr(to + 1, '`')))
2522 break;
2523
2524 to= from;
2525 }
24772526
2478 return FALSE;2527 return FALSE;
2479}2528}
@@ -2500,14 +2549,11 @@
2500static void skip_secondary_keys(char *create_str, my_bool has_pk)2549static void skip_secondary_keys(char *create_str, my_bool has_pk)
2501{2550{
2502 char *ptr, *strend;2551 char *ptr, *strend;
2503 char *last_comma = NULL;2552 char *last_comma= NULL;
2504 HASH ignored_columns;
2505 my_bool pk_processed= FALSE;2553 my_bool pk_processed= FALSE;
25062554 char *autoinc_column= NULL;
2507 if (hash_init(&ignored_columns, charset_info, 16, 0, 0,2555 my_bool has_autoinc= FALSE;
2508 (hash_get_key) get_table_key,2556 key_type_t type;
2509 (hash_free_key) free_table_ent, 0))
2510 exit(EX_EOM);
25112557
2512 strend= create_str + strlen(create_str);2558 strend= create_str + strlen(create_str);
25132559
@@ -2515,7 +2561,6 @@
2515 while (*ptr)2561 while (*ptr)
2516 {2562 {
2517 char *tmp, *orig_ptr, c;2563 char *tmp, *orig_ptr, c;
2518 my_bool UNINIT_VAR(is_unique);
25192564
2520 orig_ptr= ptr;2565 orig_ptr= ptr;
2521 /* Skip leading whitespace */2566 /* Skip leading whitespace */
@@ -2528,12 +2573,22 @@
2528 c= *tmp;2573 c= *tmp;
2529 *tmp= '\0'; /* so strstr() only processes the current line */2574 *tmp= '\0'; /* so strstr() only processes the current line */
25302575
2576 if (!strncmp(ptr, "UNIQUE KEY ", sizeof("UNIQUE KEY ") - 1))
2577 type= KEY_TYPE_UNIQUE;
2578 else if (!strncmp(ptr, "KEY ", sizeof("KEY ") - 1))
2579 type= KEY_TYPE_NON_UNIQUE;
2580 else if (!strncmp(ptr, "PRIMARY KEY ", sizeof("PRIMARY KEY ") - 1))
2581 type= KEY_TYPE_PRIMARY;
2582 else
2583 type= KEY_TYPE_NONE;
2584
2585 has_autoinc= (type != KEY_TYPE_NONE) ?
2586 contains_autoinc_column(autoinc_column, ptr, type) : FALSE;
2587
2531 /* Is it a secondary index definition? */2588 /* Is it a secondary index definition? */
2532 if (c == '\n' &&2589 if (c == '\n' &&
2533 (((is_unique= !strncmp(ptr, "UNIQUE KEY ", sizeof("UNIQUE KEY ")-1)) &&2590 ((type == KEY_TYPE_UNIQUE && (pk_processed || !has_pk)) ||
2534 (pk_processed || !has_pk)) ||2591 type == KEY_TYPE_NON_UNIQUE) && !has_autoinc)
2535 !strncmp(ptr, "KEY ", sizeof("KEY ") - 1)) &&
2536 !contains_ignored_column(&ignored_columns, ptr))
2537 {2592 {
2538 char *data, *end= tmp - 1;2593 char *data, *end= tmp - 1;
25392594
@@ -2566,23 +2621,41 @@
2566 *last_comma= ',';2621 *last_comma= ',';
2567 }2622 }
25682623
2569 if ((has_pk && is_unique && !pk_processed) ||2624 /*
2570 !strncmp(ptr, "PRIMARY KEY ", sizeof("PRIMARY KEY ") - 1))2625 If we are skipping a key which indexes an AUTO_INCREMENT column, it is
2626 safe to optimize all subsequent keys, i.e. we should not be checking for
2627 that column anymore.
2628 */
2629 if (type != KEY_TYPE_NONE && has_autoinc)
2630 {
2631 DBUG_ASSERT(autoinc_column != NULL);
2632
2633 my_free(autoinc_column, MYF(0));
2634 autoinc_column= NULL;
2635 }
2636
2637 if ((has_pk && type == KEY_TYPE_UNIQUE && !pk_processed) ||
2638 type == KEY_TYPE_PRIMARY)
2571 pk_processed= TRUE;2639 pk_processed= TRUE;
25722640
2573 if (strstr(ptr, "AUTO_INCREMENT") && *ptr == '`')2641 if (strstr(ptr, "AUTO_INCREMENT") && *ptr == '`')
2574 {2642 {
2575 /*2643 /*
2576 If a secondary key is defined on this column later,2644 The first secondary key defined on this column later cannot be
2577 it cannot be skipped, as CREATE TABLE would fail on import.2645 skipped, as CREATE TABLE would fail on import. Unless there is a
2646 PRIMARY KEY and it indexes that column.
2578 */2647 */
2579 for (end= ptr + 1; *end != '`' && *end != '\0'; end++);2648 for (end= ptr + 1;
2580 if (*end == '`' && end > ptr + 1 &&2649 /* Skip double backticks as they are a part of identifier */
2581 my_hash_insert(&ignored_columns,2650 *end != '\0' && (*end != '`' || end[1] == '`');
2582 (uchar *) my_strndup(ptr + 1,2651 end++)
2583 end - ptr - 1, MYF(0))))2652 /* empty */;
2653
2654 if (*end == '`' && end > ptr + 1)
2584 {2655 {
2585 exit(EX_EOM);2656 DBUG_ASSERT(autoinc_column == NULL);
2657
2658 autoinc_column= my_strndup(ptr + 1, end - ptr - 1, MYF(MY_FAE));
2586 }2659 }
2587 }2660 }
25882661
@@ -2594,7 +2667,7 @@
2594 }2667 }
2595 }2668 }
25962669
2597 hash_free(&ignored_columns);2670 my_free(autoinc_column, MYF(MY_ALLOW_ZERO_PTR));
2598}2671}
25992672
2600/*2673/*
26012674
=== modified file 'Percona-Server/mysql-test/r/percona_mysqldump_innodb_optimize_keys.result'
--- Percona-Server/mysql-test/r/percona_mysqldump_innodb_optimize_keys.result 2011-11-24 02:01:33 +0000
+++ Percona-Server/mysql-test/r/percona_mysqldump_innodb_optimize_keys.result 2012-11-20 09:37:22 +0000
@@ -365,3 +365,77 @@
365365
366######################################366######################################
367DROP TABLE t1, t2;367DROP TABLE t1, t2;
368CREATE TABLE t1 (
369id INT NOT NULL AUTO_INCREMENT,
370uid INT NOT NULL,
371`id``` INT NOT NULL,
372```id` INT NOT NULL,
373# The following ones may be skipped and used in ALTER TABLE later
374KEY k1 (```id`, id),
375KEY k2 (```id`, `id```),
376# The following one should be kept in CREATE TABLE
377KEY k3 (id, uid),
378# The following one may be skipped again
379KEY k4 (id, `id```)
380) ENGINE=InnoDB;
381CREATE TABLE t2 (
382id INT NOT NULL AUTO_INCREMENT,
383PRIMARY KEY (id),
384KEY k1 (id),
385KEY k2 (id)
386) ENGINE=InnoDB;
387######################################
388
389/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
390/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
391/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
392/*!40101 SET NAMES utf8 */;
393/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
394/*!40103 SET TIME_ZONE='+00:00' */;
395/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
396/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
397/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
398/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
399DROP TABLE IF EXISTS `t1`;
400/*!40101 SET @saved_cs_client = @@character_set_client */;
401/*!40101 SET character_set_client = utf8 */;
402CREATE TABLE `t1` (
403 `id` int(11) NOT NULL AUTO_INCREMENT,
404 `uid` int(11) NOT NULL,
405 `id``` int(11) NOT NULL,
406 ```id` int(11) NOT NULL,
407 KEY `k3` (`id`,`uid`)
408) ENGINE=InnoDB DEFAULT CHARSET=latin1;
409/*!40101 SET character_set_client = @saved_cs_client */;
410
411LOCK TABLES `t1` WRITE;
412/*!40000 ALTER TABLE `t1` DISABLE KEYS */;
413ALTER TABLE `t1` ADD KEY `k1` (```id`,`id`), ADD KEY `k2` (```id`,`id```), ADD KEY `k4` (`id`,`id```);
414/*!40000 ALTER TABLE `t1` ENABLE KEYS */;
415UNLOCK TABLES;
416DROP TABLE IF EXISTS `t2`;
417/*!40101 SET @saved_cs_client = @@character_set_client */;
418/*!40101 SET character_set_client = utf8 */;
419CREATE TABLE `t2` (
420 `id` int(11) NOT NULL AUTO_INCREMENT,
421 PRIMARY KEY (`id`)
422) ENGINE=InnoDB DEFAULT CHARSET=latin1;
423/*!40101 SET character_set_client = @saved_cs_client */;
424
425LOCK TABLES `t2` WRITE;
426/*!40000 ALTER TABLE `t2` DISABLE KEYS */;
427ALTER TABLE `t2` ADD KEY `k1` (`id`), ADD KEY `k2` (`id`);
428/*!40000 ALTER TABLE `t2` ENABLE KEYS */;
429UNLOCK TABLES;
430/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
431
432/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
433/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
434/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
435/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
436/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
437/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
438/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
439
440######################################
441DROP TABLE t1, t2;
368442
=== modified file 'Percona-Server/mysql-test/t/percona_mysqldump_innodb_optimize_keys.test'
--- Percona-Server/mysql-test/t/percona_mysqldump_innodb_optimize_keys.test 2011-11-24 02:01:33 +0000
+++ Percona-Server/mysql-test/t/percona_mysqldump_innodb_optimize_keys.test 2012-11-20 09:37:22 +0000
@@ -183,5 +183,45 @@
183183
184DROP TABLE t1, t2;184DROP TABLE t1, t2;
185185
186########################################################################
187# Bug #1039536: mysqldump --innodb-optimize-keys can generate invalid table
188# definitions
189########################################################################
190
191CREATE TABLE t1 (
192 id INT NOT NULL AUTO_INCREMENT,
193 uid INT NOT NULL,
194 `id``` INT NOT NULL,
195 ```id` INT NOT NULL,
196 # The following ones may be skipped and used in ALTER TABLE later
197 KEY k1 (```id`, id),
198 KEY k2 (```id`, `id```),
199 # The following one should be kept in CREATE TABLE
200 KEY k3 (id, uid),
201 # The following one may be skipped again
202 KEY k4 (id, `id```)
203) ENGINE=InnoDB;
204
205CREATE TABLE t2 (
206 id INT NOT NULL AUTO_INCREMENT,
207 PRIMARY KEY (id),
208 KEY k1 (id),
209 KEY k2 (id)
210) ENGINE=InnoDB;
211
212--exec $MYSQL_DUMP --skip-comments --innodb-optimize-keys test t1 t2 >$file
213
214--echo ######################################
215--cat_file $file
216--echo ######################################
217
218# Check that the resulting dump can be imported back
219
220--exec $MYSQL test < $file
221
222--remove_file $file
223
224DROP TABLE t1, t2;
225
186# Wait till we reached the initial number of concurrent sessions226# Wait till we reached the initial number of concurrent sessions
187--source include/wait_until_count_sessions.inc227--source include/wait_until_count_sessions.inc

Subscribers

People subscribed via source and target branches