Browse Source

Use utf8mb4 for servers that support it

Before MySQL 5.5.3, only characters represented as 3 bytes in UTF-8
could be used. In 5.5.3, utf8mb4 was added which allows all Unicode
characters, so we use this as the connection character set from this
and later MySQL versions. (#123)
Michael Lenaghan 6 years ago
parent
commit
14472e2aca
3 changed files with 21 additions and 9 deletions
  1. 2 1
      include/protocol.hrl
  2. 4 2
      src/mysql_conn.erl
  3. 15 6
      src/mysql_protocol.erl

+ 2 - 1
include/protocol.hrl

@@ -22,7 +22,8 @@
 -define(ERROR, 16#ff).
 
 %% Character sets
--define(UTF8, 16#21). %% utf8_general_ci
+-define(UTF8MB3, 16#21). %% utf8_general_ci
+-define(UTF8MB4, 16#2d). %% utf8mb4_general_ci
 
 %% --- Capability flags ---
 

+ 4 - 2
src/mysql_conn.erl

@@ -284,10 +284,12 @@ handle_call({unprepare, Stmt}, _From, State) when is_atom(Stmt);
 handle_call({change_user, Username, Password, Database}, From,
             State = #state{transaction_levels = []}) ->
     #state{socket = Socket, sockmod = SockMod,
-           auth_plugin_data = AuthPluginData} = State,
+           auth_plugin_data = AuthPluginData,
+           server_version = ServerVersion} = State,
     setopts(SockMod, Socket, [{active, false}]),
     Result = mysql_protocol:change_user(SockMod, Socket, Username, Password,
-                                        AuthPluginData, Database),
+                                        AuthPluginData, Database, 
+                                        ServerVersion),
     setopts(SockMod, Socket, [{active, once}]),
     State1 = update_state(Result, State),
     State1#state.warning_count > 0 andalso State1#state.log_warnings

+ 15 - 6
src/mysql_protocol.erl

@@ -27,7 +27,7 @@
 %% @private
 -module(mysql_protocol).
 
--export([handshake/7, change_user/6, quit/2, ping/2,
+-export([handshake/7, change_user/7, quit/2, ping/2,
          query/4, query/5, fetch_query_response/3,
          fetch_query_response/4, prepare/3, unprepare/3,
          execute/5, execute/6, fetch_execute_response/3,
@@ -233,8 +233,9 @@ fetch_execute_response(SockModule, Socket, FilterMap, Timeout) ->
 
 %% @doc Changes the user of the connection.
 -spec change_user(module(), term(), iodata(), iodata(), binary(),
-                  undefined | iodata()) -> #ok{} | #error{}.
-change_user(SockModule, Socket, Username, Password, Salt, Database) ->
+                  undefined | iodata(), [integer()]) -> #ok{} | #error{}.
+change_user(SockModule, Socket, Username, Password, Salt, Database, 
+            ServerVersion) ->
     DbBin = case Database of
         undefined -> <<>>;
         _ -> iolist_to_binary(Database)
@@ -242,7 +243,7 @@ change_user(SockModule, Socket, Username, Password, Salt, Database) ->
     Hash = hash_password(Password, Salt),
     Req = <<?COM_CHANGE_USER, (iolist_to_binary(Username))/binary, 0,
             (lenenc_str_encode(Hash))/binary,
-            DbBin/binary, 0, ?UTF8:16/little>>,
+            DbBin/binary, 0, (character_set(ServerVersion)):16/little>>,
     {ok, _SeqNum1} = send_packet(SockModule, Socket, Req, 0),
     {ok, Packet, _SeqNum2} = recv_packet(SockModule, Socket, infinity, any),
     case Packet of
@@ -357,7 +358,7 @@ build_handshake_response(Handshake, Database, SetFoundRows) ->
     verify_server_capabilities(Handshake, CapabilityFlags),
     ClientCapabilities = add_client_capabilities(CapabilityFlags),
     ClientSSLCapabilities = ClientCapabilities bor ?CLIENT_SSL,
-    CharacterSet = ?UTF8,
+    CharacterSet = character_set(Handshake#handshake.server_version),
     <<ClientSSLCapabilities:32/little,
       ?MAX_BYTES_PER_PACKET:32/little,
       CharacterSet:8,
@@ -385,7 +386,7 @@ build_handshake_response(Handshake, Username, Password, Database,
             error({auth_method, UnknownAuthMethod})
     end,
     HashLength = size(Hash),
-    CharacterSet = ?UTF8,
+    CharacterSet = character_set(Handshake#handshake.server_version),
     UsernameUtf8 = unicode:characters_to_binary(Username),
     DbBin = case Database of
         undefined -> <<>>;
@@ -431,6 +432,14 @@ add_client_capabilities(Caps) ->
     ?CLIENT_MULTI_RESULTS bor
     ?CLIENT_PS_MULTI_RESULTS.
 
+-spec character_set([integer()]) -> integer().
+character_set(ServerVersion) when ServerVersion >= [5, 5, 3] ->
+    %% https://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-3.html
+    ?UTF8MB4;
+
+character_set(_ServerVersion) ->
+    ?UTF8MB3.
+
 %% @doc Handles the second packet from the server, when we have replied to the
 %% initial handshake. Returns an error if the server returns an error. Raises
 %% an error if unimplemented features are required.