diff --git a/lib/modules/Mysql.pmod/SqlTable.pike b/lib/modules/Mysql.pmod/SqlTable.pike
index 963b366190ced2d85a86bf1329e8e54d45060485..197cfc2d40a3f5fd642906d0a9ac0e8119dfd693 100644
--- a/lib/modules/Mysql.pmod/SqlTable.pike
+++ b/lib/modules/Mysql.pmod/SqlTable.pike
@@ -215,12 +215,11 @@ protected void create (function(void:Sql.Sql) get_db,
     col_types = ([]);
     timestamp_cols = ([]);
     datetime_cols = ([]);
-    array(mapping(string:mixed)) col_list =
-      conn->list_fields (string_to_utf8 (table));
+    array(mapping(string:mixed)) col_list = conn->list_fields(table);
     mapping(string:mixed) prop_col_info;
 
     foreach (col_list, mapping(string:mixed) col_info) {
-      string name = utf8_to_string (col_info->name);
+      string name = col_info->name;
       if (String.width (name) > 8) query_charset = 0;
       if (col_types[name])
 	error ("Strange duplicate column %O in %O\n", name, col_list);
diff --git a/lib/modules/Sql.pmod/mysql.pike b/lib/modules/Sql.pmod/mysql.pike
index 5fe0c6258c202b8203b1366119bc0bb97c1d6ecd..3dfeae92b999eac957005ffd4d3d586eb03c5e1d 100644
--- a/lib/modules/Sql.pmod/mysql.pike
+++ b/lib/modules/Sql.pmod/mysql.pike
@@ -653,68 +653,112 @@ int decode_datetime (string timestr)
 #define HAVE_MYSQL_FIELD_CHARSETNR_IFELSE(TRUE, FALSE) FALSE
 #endif
 
+//! @returns
+//!   Returns an array:
+//!   @array
+//!     @item string 0
+//!       The adjusted query.
+//!     @item string|zero 1
+//!       The charset to restore after performing the query (if any).
+//!       This is only set when @[charset] has been set.
+//!   @endarray
+protected array(string(8bit)) fix_query_charset(string query,
+						string|void charset)
+{
+  if (charset) {
+    string current_charset = send_charset || get_charset();
+    if (charset != current_charset) {
+      CH_DEBUG ("Switching charset from %O to %O (due to charset arg).\n",
+		current_charset, charset);
+      ::big_query ("SET character_set_client=" + charset);
+      /* Can't be changed automatically - has side effects. /mast */
+      /* ::big_query("SET character_set_connection=" + charset); */
+      return ({ query, current_charset });
+    }
+    return ({ query, 0 });
+  }
+  if (!send_charset) return ({ query, 0 });
+
+  string new_send_charset = send_charset;
+  if (utf8_mode & LATIN1_UNICODE_ENCODE_MODE) {
+    if (String.width (query) == 8)
+      new_send_charset = "latin1";
+    else {
+      CH_DEBUG ("Converting (mysql-)latin1 query to utf8.\n");
+      query = utf8_encode_query (query, latin1_to_utf8, 2);
+      new_send_charset = "utf8";
+    }
+  }
+  else {  /* utf8_mode & UTF8_UNICODE_ENCODE_MODE */
+    /* NB: The send_charset may only be upgraded from
+     * "latin1" to "utf8", not the other way around.
+     * This is to avoid extraneous charset changes
+     * where the charset is changed from query to query.
+     */
+    if ((send_charset == "utf8") || !_can_send_as_latin1(query)) {
+      CH_DEBUG ("Converting query to utf8.\n");
+      query = utf8_encode_query (query, string_to_utf8, 2);
+      new_send_charset = "utf8";
+    }
+  }
+
+  if (new_send_charset != send_charset) {
+    CH_DEBUG ("Switching charset from %O to %O.\n",
+	      send_charset, new_send_charset);
+    if (mixed err = catch {
+	::big_query ("SET character_set_client=" + new_send_charset);
+	/* Can't be changed automatically - has side effects. /mast */
+	/* ::big_query("SET character_set_connection=" +
+	   new_send_charset); */
+      }) {
+      if (new_send_charset == "utf8")
+	predef::error ("The query is a wide string "
+		       "and the MySQL server doesn't support UTF-8: %s\n",
+		       describe_error (err));
+      else
+	throw (err);
+    }
+    send_charset = new_send_charset;
+  }
+
+  return ({ query, 0 });
+}
+
+protected array|object|mapping|string|int fix_result_charset(array|object|mapping|string|int res)
+{
+  if (!res) return UNDEFINED;
+
+  if (!(utf8_mode & UNICODE_DECODE_MODE)) return res;
+
+  if (objectp(res)) {
+    CH_DEBUG ("Using unicode wrapper for result.\n");
+    return
+      HAVE_MYSQL_FIELD_CHARSETNR_IFELSE (
+	.sql_util.MySQLUnicodeWrapper(res),
+	.sql_util.MySQLBrokenUnicodeWrapper(res));
+  }
+
+  if (arrayp(res)) {
+    return map(res, fix_result_charset);
+  }
+
+  if (mappingp(res)) {
+    return mkmapping(fix_result_charset(indices(res)),
+		     fix_result_charset(values(res)));
+  }
+
+  if (stringp(res)) {
+    return utf8_to_string(res);
+  }
+
+  return res;
+}
+
 #define QUERY_BODY(do_query)						\
   if (bindings)								\
     query = .sql_util.emulate_bindings(query,bindings,this);		\
 									\
-  string restore_charset;						\
-  if (charset) {							\
-    restore_charset = send_charset || get_charset();			\
-    if (charset != restore_charset) {					\
-      CH_DEBUG ("Switching charset from %O to %O (due to charset arg).\n", \
-		restore_charset, charset);				\
-      ::big_query ("SET character_set_client=" + charset);		\
-      /* Can't be changed automatically - has side effects. /mast */	\
-      /* ::big_query("SET character_set_connection=" + charset); */	\
-    } else								\
-      restore_charset = 0;						\
-  }									\
-									\
-  else if (send_charset) {						\
-    string new_send_charset = send_charset;				\
-									\
-    if (utf8_mode & LATIN1_UNICODE_ENCODE_MODE) {			\
-      if (String.width (query) == 8)					\
-	new_send_charset = "latin1";					\
-      else {								\
-	CH_DEBUG ("Converting (mysql-)latin1 query to utf8.\n");	\
-	query = utf8_encode_query (query, latin1_to_utf8, 2);		\
-	new_send_charset = "utf8";					\
-      }									\
-    }									\
-									\
-    else {  /* utf8_mode & UTF8_UNICODE_ENCODE_MODE */			\
-      /* NB: The send_charset may only be upgraded from			\
-       * "latin1" to "utf8", not the other way around.			\
-       * This is to avoid extraneous charset changes			\
-       * where the charset is changed from query to query.		\
-       */								\
-      if ((send_charset == "utf8") || !_can_send_as_latin1(query)) {	\
-	CH_DEBUG ("Converting query to utf8.\n");			\
-	query = utf8_encode_query (query, string_to_utf8, 2);		\
-	new_send_charset = "utf8";					\
-      }									\
-    }									\
-									\
-    if (new_send_charset != send_charset) {				\
-      CH_DEBUG ("Switching charset from %O to %O.\n",			\
-		send_charset, new_send_charset);			\
-      if (mixed err = catch {						\
-	  ::big_query ("SET character_set_client=" + new_send_charset);	\
-	  /* Can't be changed automatically - has side effects. /mast */ \
-	  /* ::big_query("SET character_set_connection=" +		\
-	     new_send_charset); */					\
-	  }) {								\
-	if (new_send_charset == "utf8")					\
-	  predef::error ("The query is a wide string "			\
-			 "and the MySQL server doesn't support UTF-8: %s\n", \
-			 describe_error (err));				\
-	else								\
-	  throw (err);							\
-      }									\
-      send_charset = new_send_charset;					\
-    }									\
-  }									\
+  [query, string restore_charset] = fix_query_charset(query, charset);	\
 									\
   CH_DEBUG ("Sending query with charset %O: %s.\n",			\
 	    charset || send_charset,					\
@@ -722,7 +766,7 @@ int decode_datetime (string timestr)
 	     sprintf ("%O...", query[..200]) :				\
 	     sprintf ("%O", query)));					\
 									\
-  int|object res = ::do_query(query);					\
+  int|object res = fix_result_charset(::do_query(query));		\
 									\
   if (restore_charset) {						\
     if (send_charset && (<"latin1", "utf8">)[charset])			\
@@ -735,15 +779,6 @@ int decode_datetime (string timestr)
     }									\
   }									\
 									\
-  if (!objectp(res)) return res;					\
-									\
-  if (utf8_mode & UNICODE_DECODE_MODE) {				\
-    CH_DEBUG ("Using unicode wrapper for result.\n");			\
-    return								\
-      HAVE_MYSQL_FIELD_CHARSETNR_IFELSE (				\
-	.sql_util.MySQLUnicodeWrapper(res),				\
-	.sql_util.MySQLBrokenUnicodeWrapper (res));			\
-  }									\
   return res;
 
 Mysql.mysql_result big_query (string query,
@@ -835,6 +870,37 @@ Mysql.mysql_result streaming_typed_query (string query,
   QUERY_BODY (streaming_typed_query);
 }
 
+array(string) list_dbs(string|void wild)
+{
+  return fix_result_charset(::list_dbs(wild && fix_query_charset(wild)[0]));
+}
+
+array(string) list_tables(string|void wild)
+{
+  return fix_result_charset(::list_tables(wild && fix_query_charset(wild)[0]));
+}
+
+array(mapping(string:mixed)) list_fields(string table, string|void wild)
+{
+  if (!wild) {
+    // Very common case.
+    return fix_result_charset(::list_fields(fix_query_charset(table)[0]));
+  }
+
+  string table_and_wild = table + "\0\0PIKE\0\0" + wild;
+  table_and_wild = fix_query_charset(table_and_wild)[0];
+  array(string) a = table_and_wild / "\0\0PIKE\0\0";
+  if (sizeof(a) == 2) {
+    // Common case.
+    return fix_result_charset(::list_fields(@a));
+  }
+
+  // Very uncommon case, but...
+  //
+  // Assume that the table name can not contain NUL characters.
+  return fix_result_charset(::list_fields(a[0], a[1..] * "\0\0PIKE\0\0"));
+}
+
 int(0..1) is_keyword( string name )
 //! Return 1 if the argument @[name] is a mysql keyword that needs to
 //! be quoted in a query. The list is currently up-to-date with MySQL