MySQL Lists are EOL. Please join:

List:Commits« Previous MessageNext Message »
From:Alexander Nozdrin Date:May 13 2010 6:10pm
Subject:bzr commit into mysql-trunk-runtime branch (alik:3018) Bug#27480
View as plain text  
#At file:///mnt/raid/alik/MySQL/bzr/00/bug27480/mysql-trunk-rt-bug27480/ based on revid:kostja@stripped

 3018 Alexander Nozdrin	2010-05-13
      Preliminary patch for Bug#27480.

    added:
      mysql-test/r/create_notembedded.result
      mysql-test/t/create_notembedded.test
    modified:
      sql/sql_acl.cc
      sql/sql_base.cc
      sql/sql_base.h
      sql/sql_parse.cc
=== added file 'mysql-test/r/create_notembedded.result'
--- a/mysql-test/r/create_notembedded.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/create_notembedded.result	2010-05-13 18:10:04 +0000
@@ -0,0 +1,102 @@
+DROP DATABASE IF EXISTS mysqltest2;
+CREATE DATABASE mysqltest2;
+GRANT CREATE, SELECT ON test.* TO mysqltest_u1@localhost;
+GRANT CREATE TEMPORARY TABLES ON mysqltest2.* TO mysqltest_u1@localhost;
+CREATE TEMPORARY TABLE tmp1(a INT);
+CREATE TEMPORARY TABLE tmp2(a INT);
+CREATE TABLE t1(a INT);
+ERROR 42000: CREATE command denied to user 'mysqltest_u1'@'localhost' for table 't1'
+CREATE TABLE test.t1(a INT);
+INSERT INTO tmp1 VALUES (11), (12), (13);
+INSERT INTO tmp2 VALUES (21), (22), (23);
+CREATE TEMPORARY TABLE tmp3(b INT);
+INSERT INTO tmp3 SELECT a FROM tmp1 UNION SELECT a FROM tmp2;
+SELECT * FROM tmp3;
+b
+11
+12
+13
+21
+22
+23
+SELECT * FROM tmp1;
+a
+11
+12
+13
+SELECT * FROM tmp1, tmp2;
+a	a
+11	21
+12	21
+13	21
+11	22
+12	22
+13	22
+11	23
+12	23
+13	23
+SELECT * FROM tmp1, t1, tmp2;
+ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1'
+SELECT * FROM tmp1, test.t1, tmp2;
+a	a	a
+UPDATE tmp1 SET a = a * 10;
+SELECT * FROM tmp1;
+a
+110
+120
+130
+UPDATE tmp2 SET a = a - 20 WHERE a >= 10;
+SELECT * FROM tmp2;
+a
+1
+2
+3
+UPDATE tmp1, tmp3
+SET a = a * 1, b = b * 1
+WHERE b < 100;
+SELECT * FROM tmp1;
+a
+110
+120
+130
+SELECT * FROM tmp3;
+b
+11
+12
+13
+21
+22
+23
+DELETE FROM tmp1;
+SELECT * FROM tmp1;
+a
+DELETE FROM tmp2 WHERE a > 2;
+SELECT * FROM tmp2;
+a
+1
+2
+DELETE FROM tmp2;
+INSERT INTO tmp1 VALUES (1), (2), (3);
+INSERT INTO tmp2 VALUES (2), (3), (4);
+SELECT * FROM tmp1;
+a
+1
+2
+3
+SELECT * FROM tmp2;
+a
+2
+3
+4
+DELETE a1, a2
+FROM tmp1 AS a1 INNER JOIN tmp2 AS a2
+WHERE a1.a = a2.a;
+SELECT * FROM tmp1;
+a
+1
+SELECT * FROM tmp2;
+a
+4
+DROP DATABASE mysqltest2;
+DROP TABLE t1;
+DROP USER mysqltest_u1@localhost;

=== added file 'mysql-test/t/create_notembedded.test'
--- a/mysql-test/t/create_notembedded.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/create_notembedded.test	2010-05-13 18:10:04 +0000
@@ -0,0 +1,92 @@
+# Grant tests not performed with embedded server
+-- source include/not_embedded.inc
+
+#
+# Bug#27480: Extend CREATE TEMPORARY TABLES privilege to allow temp table
+# operations.
+#
+
+--disable_warnings
+DROP DATABASE IF EXISTS mysqltest2;
+--enable_warnings
+
+CREATE DATABASE mysqltest2;
+
+GRANT CREATE, SELECT ON test.* TO mysqltest_u1@localhost;
+GRANT CREATE TEMPORARY TABLES ON mysqltest2.* TO mysqltest_u1@localhost;
+
+--connect (con1, localhost, mysqltest_u1, , mysqltest2)
+
+CREATE TEMPORARY TABLE tmp1(a INT);
+CREATE TEMPORARY TABLE tmp2(a INT);
+
+--error ER_TABLEACCESS_DENIED_ERROR
+CREATE TABLE t1(a INT);
+
+CREATE TABLE test.t1(a INT);
+
+# Check INSERT INTO.
+
+INSERT INTO tmp1 VALUES (11), (12), (13);
+INSERT INTO tmp2 VALUES (21), (22), (23);
+
+CREATE TEMPORARY TABLE tmp3(b INT);
+INSERT INTO tmp3 SELECT a FROM tmp1 UNION SELECT a FROM tmp2;
+SELECT * FROM tmp3;
+
+# Check SELECT.
+
+SELECT * FROM tmp1;
+
+SELECT * FROM tmp1, tmp2;
+
+--error ER_TABLEACCESS_DENIED_ERROR
+SELECT * FROM tmp1, t1, tmp2;
+
+SELECT * FROM tmp1, test.t1, tmp2;
+
+# Check UPDATE.
+
+UPDATE tmp1 SET a = a * 10;
+SELECT * FROM tmp1;
+
+UPDATE tmp2 SET a = a - 20 WHERE a >= 10;
+SELECT * FROM tmp2;
+
+UPDATE tmp1, tmp3
+SET a = a * 1, b = b * 1
+WHERE b < 100;
+
+SELECT * FROM tmp1;
+SELECT * FROM tmp3;
+
+# Check DELETE.
+
+DELETE FROM tmp1;
+SELECT * FROM tmp1;
+
+DELETE FROM tmp2 WHERE a > 2;
+SELECT * FROM tmp2;
+
+DELETE FROM tmp2;
+
+INSERT INTO tmp1 VALUES (1), (2), (3);
+INSERT INTO tmp2 VALUES (2), (3), (4);
+
+SELECT * FROM tmp1;
+SELECT * FROM tmp2;
+
+DELETE a1, a2
+FROM tmp1 AS a1 INNER JOIN tmp2 AS a2
+WHERE a1.a = a2.a;
+
+SELECT * FROM tmp1;
+SELECT * FROM tmp2;
+
+--connection default
+--disconnect con1
+
+DROP DATABASE mysqltest2;
+DROP TABLE t1;
+
+DROP USER mysqltest_u1@localhost;

=== modified file 'sql/sql_acl.cc'
--- a/sql/sql_acl.cc	2010-04-28 10:04:11 +0000
+++ b/sql/sql_acl.cc	2010-05-13 18:10:04 +0000
@@ -3988,6 +3988,18 @@ end:
   DBUG_RETURN(return_val);
 }
 
+static bool check_grant_for_temp_table(THD *thd, TABLE_LIST *tl)
+{
+  TABLE *table= tl->table;
+
+  if (!table && tl->correspondent_table)
+    table= tl->correspondent_table->table;
+
+  return !(table &&
+      table->s->tmp_table != NO_TMP_TABLE &&
+      !check_access(thd, CREATE_TMP_ACL, table->s->db.str, NULL, NULL, 0, TRUE));
+}
+
 
 /**
   @brief Check table level grants
@@ -4117,9 +4129,24 @@ bool check_grant(THD *thd, ulong want_ac
       }
       continue;
     }
-    if (!(grant_table= table_hash_search(sctx->host, sctx->ip,
-                                         table->get_db_name(), sctx->priv_user,
-                                         table->get_table_name(), FALSE)))
+
+    grant_table= table_hash_search(sctx->host, sctx->ip,
+                                   table->get_db_name(), sctx->priv_user,
+                                   table->get_table_name(), FALSE);
+
+    if (grant_table)
+    {
+      table->grant.grant_table=grant_table;	// Remember for column test
+      table->grant.version=grant_version;
+      table->grant.privilege|= grant_table->privs;
+      table->grant.want_privilege= ((want_access & COL_ACLS)
+                                    & ~table->grant.privilege);
+    }
+
+    if (!check_grant_for_temp_table(thd, table))
+      continue;
+
+    if (!grant_table)
     {
       want_access &= ~table->grant.privilege;
       goto err;					// No grants
@@ -4132,12 +4159,6 @@ bool check_grant(THD *thd, ulong want_ac
     if (any_combination_will_do)
       continue;
 
-    table->grant.grant_table=grant_table;	// Remember for column test
-    table->grant.version=grant_version;
-    table->grant.privilege|= grant_table->privs;
-    table->grant.want_privilege= ((want_access & COL_ACLS)
-				  & ~table->grant.privilege);
-
     if (!(~table->grant.privilege & want_access))
       continue;
 
@@ -4294,6 +4315,9 @@ bool check_column_grant_in_table_ref(THD
     table_name= table->s->table_name.str;
   }
 
+  if (!check_grant_for_temp_table(thd, table_ref))
+    return FALSE;
+
   if (grant->want_privilege)
     return check_grant_column(thd, grant, db_name, table_name, name,
                               length, sctx);
@@ -4339,7 +4363,9 @@ bool check_grant_all_columns(THD *thd, u
 
   for (; !fields->end_of_fields(); fields->next())
   {
+    Field *field= fields->field();
     const char *field_name= fields->name();
+    bool is_tmp_table= field->table->s->tmp_table != NO_TMP_TABLE;
 
     if (table_name != fields->get_table_name())
     {
@@ -4361,11 +4387,20 @@ bool check_grant_all_columns(THD *thd, u
         }
 
         grant_table= grant->grant_table;
-        DBUG_ASSERT (grant_table);
+
+        DBUG_ASSERT(grant_table || !grant_table && is_tmp_table);
       }
     }
 
-    if (want_access)
+    if (is_tmp_table)
+    {
+      if (check_access(thd, CREATE_TMP_ACL, fields->get_db_name(),
+                       NULL, NULL, 0, TRUE))
+      {
+        goto err;
+      }
+    }
+    else if (want_access)
     {
       GRANT_COLUMN *grant_column= 
         column_hash_search(grant_table, field_name,

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2010-05-05 22:02:08 +0000
+++ b/sql/sql_base.cc	2010-05-13 18:10:04 +0000
@@ -1979,16 +1979,26 @@ TABLE *find_temporary_table(THD *thd, TA
 {
   char	key[MAX_DBKEY_LENGTH];
   uint	key_length;
-  TABLE *table;
-  DBUG_ENTER("find_temporary_table");
+
+  DBUG_ENTER("find_temporary_table(TABLE_LIST)");
   DBUG_PRINT("enter", ("table: '%s'.'%s'",
                        table_list->db, table_list->table_name));
 
   key_length= create_table_def_key(thd, key, table_list, 1);
-  for (table=thd->temporary_tables ; table ; table= table->next)
+  DBUG_RETURN(find_temporary_table(thd, key, key_length));
+}
+
+
+TABLE *find_temporary_table(THD *thd,
+                            const char *table_key,
+                            uint table_key_length)
+{
+  DBUG_ENTER("find_temporary_table");
+
+  for (TABLE *table= thd->temporary_tables; table; table= table->next)
   {
-    if (table->s->table_cache_key.length == key_length &&
-	!memcmp(table->s->table_cache_key.str, key, key_length))
+    if (table->s->table_cache_key.length == table_key_length &&
+        !memcmp(table->s->table_cache_key.str, table_key, table_key_length))
     {
       DBUG_PRINT("info",
                  ("Found table. server_id: %u  pseudo_thread_id: %lu",
@@ -1997,7 +2007,7 @@ TABLE *find_temporary_table(THD *thd, TA
       DBUG_RETURN(table);
     }
   }
-  DBUG_RETURN(0);                               // Not a temporary table
+  DBUG_RETURN(NULL);  // Not a temporary table
 }
 
 
@@ -2152,6 +2162,147 @@ bool rename_temporary_table(THD* thd, TA
 }
 
 
+static void update_table_flags(THD *thd, TABLE_LIST *table_list, TABLE *table)
+{
+  DBUG_ASSERT(table->s->ref_count > 0 || table->s->tmp_table != NO_TMP_TABLE);
+
+  if (thd->lex->need_correct_ident())
+    table->alias_name_used= my_strcasecmp(table_alias_charset,
+                                          table->s->table_name.str,
+                                          table_list->alias);
+  /* Fix alias if table name changes */
+  if (strcmp(table->alias, table_list->alias))
+  {
+    uint length=(uint) strlen(table_list->alias)+1;
+    table->alias= (char*) my_realloc((char*) table->alias, length,
+                                     MYF(MY_WME));
+    memcpy((char*) table->alias, table_list->alias, length);
+  }
+  table->tablenr=thd->current_tablenr++;
+  table->used_fields=0;
+  table->const_table=0;
+  table->null_row= table->maybe_null= 0;
+  table->force_index= table->force_index_order= table->force_index_group= 0;
+  table->status=STATUS_NO_RECORD;
+  table->insert_values= 0;
+  table->fulltext_searched= 0;
+  table->file->ft_handler= 0;
+  table->reginfo.impossible_range= 0;
+  /* Catch wrong handling of the auto_increment_field_not_null. */
+  DBUG_ASSERT(!table->auto_increment_field_not_null);
+  table->auto_increment_field_not_null= FALSE;
+  if (table->timestamp_field)
+    table->timestamp_field_type= table->timestamp_field->get_auto_set_type();
+  table->pos_in_table_list= table_list;
+  table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
+  table->clear_column_bitmaps();
+  table_list->table= table;
+  DBUG_ASSERT(table->key_read == 0);
+  /* Tables may be reused in a sub statement. */
+  if (table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN))
+    table->file->extra(HA_EXTRA_DETACH_CHILDREN);
+}
+
+bool open_tmp_tables(THD *thd, TABLE_LIST *table_list)
+{
+  for (TABLE_LIST *tl= table_list; tl; tl= tl->next_global)
+  {
+    /*
+      If TABLE_LIST::schema_table is set, TABLE_LIST::table_name may be
+      corrupted.
+
+      TABLE_LIST::db may be an empty string, and TABLE_LIST::table_name
+      is corrupted in this case.
+    */
+
+    if (tl->schema_table || !tl->db[0])
+      continue;
+
+    char key[MAX_DBKEY_LENGTH];
+    uint key_length;
+
+    key_length= create_table_def_key(thd, key, tl, 1);
+
+    if (open_tmp_table(thd, key, key_length, tl, &tl->table, 0))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+
+/*
+  Unless requested otherwise, try to resolve this table in the list
+  of temporary tables of this thread. In MySQL temporary tables
+  are always thread-local and "shadow" possible base tables with the
+  same name. This block implements the behaviour.
+*/
+bool open_tmp_table(THD *thd,
+                    const char *table_key,
+                    uint table_key_length,
+                    TABLE_LIST *table_list,
+                    TABLE **tmp_table,
+                    uint flags)
+{
+  DBUG_ENTER("open_tmp_table");
+  DBUG_PRINT("enter", ("table: '%s'.'%s'",
+                       table_list->db, table_list->table_name));
+
+  *tmp_table= NULL;
+
+  if (table_list->open_type == OT_BASE_ONLY ||
+      (flags & MYSQL_OPEN_SKIP_TEMPORARY))
+  {
+    DBUG_PRINT("info", ("skip_temporary is set"));
+    DBUG_RETURN(FALSE);
+  }
+
+  TABLE *table= find_temporary_table(thd, table_key, table_key_length);
+
+  if (!table)
+    DBUG_RETURN(FALSE);
+
+  if (table->query_id)
+  {
+    /*
+      We're trying to use the same temporary table twice in a query.
+      Right now we don't support this because a temporary table is always
+      represented by only one TABLE object in THD, and it can not be
+      cloned. Emit an error for an unsupported behaviour.
+    */
+
+    DBUG_PRINT("error",
+               ("query_id: %lu  server_id: %u  pseudo_thread_id: %lu",
+                (ulong) table->query_id, (uint) thd->server_id,
+                (ulong) thd->variables.pseudo_thread_id));
+    my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
+    DBUG_RETURN(TRUE);
+  }
+
+  table->query_id= thd->query_id;
+  thd->thread_specific_used= TRUE;
+
+  update_table_flags(thd, table_list, table);
+
+  DBUG_PRINT("info", ("Using temporary table"));
+  *tmp_table= table;
+
+  if (table_list->open_type == OT_TEMPORARY_ONLY ||
+      (flags & MYSQL_OPEN_TEMPORARY_ONLY))
+  {
+    if (table_list->open_strategy == TABLE_LIST::OPEN_NORMAL)
+    {
+      my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name);
+      DBUG_RETURN(TRUE);
+    }
+    else
+      DBUG_RETURN(FALSE);
+  }
+
+  DBUG_RETURN(FALSE);
+}
+
+
 /**
    Force all other threads to stop using the table by upgrading
    metadata lock on it and remove unused TABLE instances from cache.
@@ -2532,57 +2683,6 @@ bool open_table(THD *thd, TABLE_LIST *ta
       DBUG_RETURN(TRUE);
     }
   }
-  /*
-    Unless requested otherwise, try to resolve this table in the list
-    of temporary tables of this thread. In MySQL temporary tables
-    are always thread-local and "shadow" possible base tables with the
-    same name. This block implements the behaviour.
-    TODO: move this block into a separate function.
-  */
-  if (table_list->open_type != OT_BASE_ONLY &&
-      ! (flags & MYSQL_OPEN_SKIP_TEMPORARY))
-  {
-    for (table= thd->temporary_tables; table ; table=table->next)
-    {
-      if (table->s->table_cache_key.length == key_length +
-          TMP_TABLE_KEY_EXTRA &&
-	  !memcmp(table->s->table_cache_key.str, key,
-		  key_length + TMP_TABLE_KEY_EXTRA))
-      {
-        /*
-          We're trying to use the same temporary table twice in a query.
-          Right now we don't support this because a temporary table
-          is always represented by only one TABLE object in THD, and
-          it can not be cloned. Emit an error for an unsupported behaviour.
-        */
-	if (table->query_id)
-	{
-          DBUG_PRINT("error",
-                     ("query_id: %lu  server_id: %u  pseudo_thread_id: %lu",
-                      (ulong) table->query_id, (uint) thd->server_id,
-                      (ulong) thd->variables.pseudo_thread_id));
-	  my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
-	  DBUG_RETURN(TRUE);
-	}
-	table->query_id= thd->query_id;
-	thd->thread_specific_used= TRUE;
-        DBUG_PRINT("info",("Using temporary table"));
-        goto reset;
-      }
-    }
-  }
-
-  if (table_list->open_type == OT_TEMPORARY_ONLY ||
-      (flags & MYSQL_OPEN_TEMPORARY_ONLY))
-  {
-    if (table_list->open_strategy == TABLE_LIST::OPEN_NORMAL)
-    {
-      my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name);
-      DBUG_RETURN(TRUE);
-    }
-    else
-      DBUG_RETURN(FALSE);
-  }
 
   /*
     The table is not temporary - if we're in pre-locked or LOCK TABLES
@@ -2959,42 +3059,7 @@ bool open_table(THD *thd, TABLE_LIST *ta
   table->reginfo.lock_type=TL_READ;		/* Assume read */
 
  reset:
-  DBUG_ASSERT(table->s->ref_count > 0 || table->s->tmp_table != NO_TMP_TABLE);
-
-  if (thd->lex->need_correct_ident())
-    table->alias_name_used= my_strcasecmp(table_alias_charset,
-                                          table->s->table_name.str, alias);
-  /* Fix alias if table name changes */
-  if (strcmp(table->alias, alias))
-  {
-    uint length=(uint) strlen(alias)+1;
-    table->alias= (char*) my_realloc((char*) table->alias, length,
-                                     MYF(MY_WME));
-    memcpy((char*) table->alias, alias, length);
-  }
-  table->tablenr=thd->current_tablenr++;
-  table->used_fields=0;
-  table->const_table=0;
-  table->null_row= table->maybe_null= 0;
-  table->force_index= table->force_index_order= table->force_index_group= 0;
-  table->status=STATUS_NO_RECORD;
-  table->insert_values= 0;
-  table->fulltext_searched= 0;
-  table->file->ft_handler= 0;
-  table->reginfo.impossible_range= 0;
-  /* Catch wrong handling of the auto_increment_field_not_null. */
-  DBUG_ASSERT(!table->auto_increment_field_not_null);
-  table->auto_increment_field_not_null= FALSE;
-  if (table->timestamp_field)
-    table->timestamp_field_type= table->timestamp_field->get_auto_set_type();
-  table->pos_in_table_list= table_list;
-  table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
-  table->clear_column_bitmaps();
-  table_list->table= table;
-  DBUG_ASSERT(table->key_read == 0);
-  /* Tables may be reused in a sub statement. */
-  if (table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN))
-    table->file->extra(HA_EXTRA_DETACH_CHILDREN);
+  update_table_flags(thd, table_list, table);
   DBUG_RETURN(FALSE);
 
 err_unlock:
@@ -4180,6 +4245,15 @@ open_and_process_table(THD *thd, LEX *le
   DBUG_ENTER("open_and_process_table");
 
   /*
+    If this is a temporary table. it is already open.
+  */
+  if (tables->table &&
+      tables->table->s->tmp_table != NO_TMP_TABLE)
+  {
+    goto end;
+  }
+
+  /*
     Ignore placeholders for derived tables. After derived tables
     processing, link to created temporary table will be put here.
     If this is derived table for view then we still want to process

=== modified file 'sql/sql_base.h'
--- a/sql/sql_base.h	2010-05-13 09:36:49 +0000
+++ b/sql/sql_base.h	2010-05-13 18:10:04 +0000
@@ -134,6 +134,9 @@ TABLE_LIST *find_table_in_list(TABLE_LIS
                                const char *table_name);
 TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name);
 TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list);
+TABLE *find_temporary_table(THD *thd,
+                            const char *table_key,
+                            uint table_key_length);
 void close_thread_tables(THD *thd);
 bool fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields,
                                           List<Item> &values,
@@ -231,6 +234,13 @@ void close_temporary_table(THD *thd, TAB
 void close_temporary(TABLE *table, bool free_share, bool delete_table);
 bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db,
 			    const char *table_name);
+bool open_tmp_table(THD *thd,
+                    const char *table_key,
+                    uint table_key_length,
+                    TABLE_LIST *table_list,
+                    TABLE **tmp_table,
+                    uint flags);
+bool open_tmp_tables(THD *thd, TABLE_LIST *table_list);
 void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table);
 void remove_db_from_cache(const char *db);
 void flush_tables();

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2010-05-05 22:02:08 +0000
+++ b/sql/sql_parse.cc	2010-05-13 18:10:04 +0000
@@ -2184,6 +2184,9 @@ mysql_execute_command(THD *thd)
     DEBUG_SYNC(thd,"before_execute_sql_command");
 #endif
 
+  if (open_tmp_tables(thd, all_tables))
+    goto error;
+
   switch (lex->sql_command) {
 
   case SQLCOM_SHOW_EVENTS:


Attachment: [text/bzr-bundle] bzr/alik@sun.com-20100513181004-g3ilvdunxcmwegg2.bundle
Thread
bzr commit into mysql-trunk-runtime branch (alik:3018) Bug#27480Alexander Nozdrin13 May