├── .gitignore ├── COPYING.txt ├── Makefile ├── README.txt ├── SOURCE.txt ├── include └── mysql.hrl ├── rebar ├── src ├── mysql.app.src ├── mysql.erl ├── mysql_auth.erl ├── mysql_conn.erl └── mysql_recv.erl └── test └── mysql_test.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | erl_crash.dump 3 | .eunit 4 | ebin 5 | doc 6 | 7 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizzyd/erlang-mysql-driver/051f22e1cbe130061b43273a14217c659e6b860d/COPYING.txt -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | ./rebar compile 3 | 4 | clean: 5 | ./rebar clean 6 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Description: Revamped MySQL driver for Erlang 2 | Modified by: Yariv Sadan (yarivvv@gmail.com) 3 | 4 | This MySQL driver for Erlang is based on the Yxa driver obtained from Process One (at https://support.process-one.net/doc/display/CONTRIBS/Yxa). It includes several new features such as prepared statements, transactions, binary queries, type-converted query results, more efficient logging and a new connection pooling mechanism. 5 | 6 | For more information, visit http://code.google.com/p/erlang-mysql-driver. 7 | -------------------------------------------------------------------------------- /SOURCE.txt: -------------------------------------------------------------------------------- 1 | Taken from http://code.google.com/p/erlang-mysql-driver/ SVN; revision 33 2 | -------------------------------------------------------------------------------- /include/mysql.hrl: -------------------------------------------------------------------------------- 1 | %% MySQL result record: 2 | -record(mysql_result, 3 | {fieldinfo=[], 4 | rows=[], 5 | affectedrows=0, 6 | insertid=0, 7 | error="", 8 | errcode=0, 9 | errsqlstate=""}). 10 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizzyd/erlang-mysql-driver/051f22e1cbe130061b43273a14217c659e6b860d/rebar -------------------------------------------------------------------------------- /src/mysql.app.src: -------------------------------------------------------------------------------- 1 | {application, mysql, 2 | [{description, "MySQL Library"}, 3 | {vsn, "34"}, 4 | {modules, [mysql, 5 | mysql_auth, 6 | mysql_conn, 7 | mysql_recv]}, 8 | {registered, []}, 9 | {applications, [kernel, stdlib]}]}. 10 | 11 | -------------------------------------------------------------------------------- /src/mysql.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizzyd/erlang-mysql-driver/051f22e1cbe130061b43273a14217c659e6b860d/src/mysql.erl -------------------------------------------------------------------------------- /src/mysql_auth.erl: -------------------------------------------------------------------------------- 1 | %%% coding: latin-1 2 | %%%------------------------------------------------------------------- 3 | %%% File : mysql_auth.erl 4 | %%% Author : Fredrik Thulin 5 | %%% Descrip.: MySQL client authentication functions. 6 | %%% Created : 4 Aug 2005 by Fredrik Thulin 7 | %%% 8 | %%% Note : All MySQL code was written by Magnus Ahltorp, originally 9 | %%% in the file mysql.erl - I just moved it here. 10 | %%% 11 | %%% Copyright (c) 2001-2004 Kungliga Tekniska H�gskolan 12 | %%% See the file COPYING 13 | %%% 14 | %%%------------------------------------------------------------------- 15 | -module(mysql_auth). 16 | 17 | %%-------------------------------------------------------------------- 18 | %% External exports (should only be used by the 'mysql_conn' module) 19 | %%-------------------------------------------------------------------- 20 | -export([ 21 | do_old_auth/8, 22 | do_new_auth/9 23 | ]). 24 | 25 | %%-------------------------------------------------------------------- 26 | %% Macros 27 | %%-------------------------------------------------------------------- 28 | -define(LONG_PASSWORD, 1). 29 | -define(FOUND_ROWS, 2). 30 | -define(LONG_FLAG, 4). 31 | -define(PROTOCOL_41, 512). 32 | -define(TRANSACTIONS, 8192). 33 | -define(SECURE_CONNECTION, 32768). 34 | -define(CONNECT_WITH_DB, 8). 35 | 36 | -define(MAX_PACKET_SIZE, 1000000). 37 | 38 | %%==================================================================== 39 | %% External functions 40 | %%==================================================================== 41 | 42 | %%-------------------------------------------------------------------- 43 | %% Function: do_old_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, 44 | %% LogFun) 45 | %% Sock = term(), gen_tcp socket 46 | %% RecvPid = pid(), receiver process pid 47 | %% SeqNum = integer(), first sequence number we should use 48 | %% User = string(), MySQL username 49 | %% Password = string(), MySQL password 50 | %% Salt1 = string(), salt 1 from server greeting 51 | %% LogFun = undefined | function() of arity 3 52 | %% FoundRows= boolean(), sets FLAG_FOUND_ROWS capability 53 | %% Descrip.: Perform old-style MySQL authentication. 54 | %% Returns : result of mysql_conn:do_recv/3 55 | %%-------------------------------------------------------------------- 56 | do_old_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, LogFun, FoundRows) -> 57 | Auth = password_old(Password, Salt1), 58 | Packet2 = make_auth(User, Auth, FoundRows), 59 | do_send(Sock, Packet2, SeqNum, LogFun), 60 | mysql_conn:do_recv(LogFun, RecvPid, SeqNum). 61 | 62 | %%-------------------------------------------------------------------- 63 | %% Function: do_new_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, 64 | %% Salt2, LogFun) 65 | %% Sock = term(), gen_tcp socket 66 | %% RecvPid = pid(), receiver process pid 67 | %% SeqNum = integer(), first sequence number we should use 68 | %% User = string(), MySQL username 69 | %% Password = string(), MySQL password 70 | %% Salt1 = string(), salt 1 from server greeting 71 | %% Salt2 = string(), salt 2 from server greeting 72 | %% LogFun = undefined | function() of arity 3 73 | %% FoundRows= boolean(), sets FLAG_FOUND_ROWS capability 74 | %% Descrip.: Perform MySQL authentication. 75 | %% Returns : result of mysql_conn:do_recv/3 76 | %%-------------------------------------------------------------------- 77 | do_new_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, Salt2, LogFun, 78 | FoundRows) -> 79 | Auth = password_new(Password, Salt1 ++ Salt2), 80 | Packet2 = make_new_auth(User, Auth, none, FoundRows), 81 | do_send(Sock, Packet2, SeqNum, LogFun), 82 | case mysql_conn:do_recv(LogFun, RecvPid, SeqNum) of 83 | {ok, Packet3, SeqNum2} -> 84 | case Packet3 of 85 | <<254:8>> -> 86 | AuthOld = password_old(Password, Salt1), 87 | do_send(Sock, <>, SeqNum2 + 1, LogFun), 88 | mysql_conn:do_recv(LogFun, RecvPid, SeqNum2 + 1); 89 | _ -> 90 | {ok, Packet3, SeqNum2} 91 | end; 92 | {error, Reason} -> 93 | {error, Reason} 94 | end. 95 | 96 | %%==================================================================== 97 | %% Internal functions 98 | %%==================================================================== 99 | 100 | password_old(Password, Salt) -> 101 | {P1, P2} = hash(Password), 102 | {S1, S2} = hash(Salt), 103 | Seed1 = P1 bxor S1, 104 | Seed2 = P2 bxor S2, 105 | List = rnd(9, Seed1, Seed2), 106 | {L, [Extra]} = lists:split(8, List), 107 | list_to_binary(lists:map(fun (E) -> 108 | E bxor (Extra - 64) 109 | end, L)). 110 | 111 | %% part of do_old_auth/4, which is part of mysql_init/4 112 | make_auth(User, Password, FoundRows) -> 113 | Caps0 = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS, 114 | Caps = case FoundRows of 115 | true -> 116 | Caps0 bor ?FOUND_ROWS; 117 | _ -> 118 | Caps0 119 | end, 120 | Maxsize = 0, 121 | UserB = list_to_binary(User), 122 | PasswordB = Password, 123 | <>. 125 | 126 | %% part of do_new_auth/4, which is part of mysql_init/4 127 | make_new_auth(User, Password, Database, FoundRows) -> 128 | DBCaps0 = case Database of 129 | none -> 130 | 0; 131 | _ -> 132 | ?CONNECT_WITH_DB 133 | end, 134 | DBCaps = case FoundRows of 135 | true -> 136 | DBCaps0 bor ?FOUND_ROWS; 137 | _ -> 138 | DBCaps0 139 | end, 140 | Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS bor 141 | ?PROTOCOL_41 bor ?SECURE_CONNECTION bor DBCaps, 142 | Maxsize = ?MAX_PACKET_SIZE, 143 | UserB = list_to_binary(User), 144 | PasswordL = size(Password), 145 | DatabaseB = case Database of 146 | none -> 147 | <<>>; 148 | _ -> 149 | list_to_binary(Database) 150 | end, 151 | <>. 153 | 154 | hash(S) -> 155 | hash(S, 1345345333, 305419889, 7). 156 | 157 | hash([C | S], N1, N2, Add) -> 158 | N1_1 = N1 bxor (((N1 band 63) + Add) * C + N1 * 256), 159 | N2_1 = N2 + ((N2 * 256) bxor N1_1), 160 | Add_1 = Add + C, 161 | hash(S, N1_1, N2_1, Add_1); 162 | hash([], N1, N2, _Add) -> 163 | Mask = (1 bsl 31) - 1, 164 | {N1 band Mask , N2 band Mask}. 165 | 166 | rnd(N, Seed1, Seed2) -> 167 | Mod = (1 bsl 30) - 1, 168 | rnd(N, [], Seed1 rem Mod, Seed2 rem Mod). 169 | 170 | rnd(0, List, _, _) -> 171 | lists:reverse(List); 172 | rnd(N, List, Seed1, Seed2) -> 173 | Mod = (1 bsl 30) - 1, 174 | NSeed1 = (Seed1 * 3 + Seed2) rem Mod, 175 | NSeed2 = (NSeed1 + Seed2 + 33) rem Mod, 176 | Float = (float(NSeed1) / float(Mod))*31, 177 | Val = trunc(Float)+64, 178 | rnd(N - 1, [Val | List], NSeed1, NSeed2). 179 | 180 | 181 | 182 | dualmap(_F, [], []) -> 183 | []; 184 | dualmap(F, [E1 | R1], [E2 | R2]) -> 185 | [F(E1, E2) | dualmap(F, R1, R2)]. 186 | 187 | bxor_binary(B1, B2) -> 188 | list_to_binary(dualmap(fun (E1, E2) -> 189 | E1 bxor E2 190 | end, binary_to_list(B1), binary_to_list(B2))). 191 | 192 | password_new([], _Salt) -> 193 | <<>>; 194 | password_new(Password, Salt) -> 195 | Stage1 = crypto:hash(sha, Password), 196 | Stage2 = crypto:hash(sha, Stage1), 197 | Res = crypto:hash_final( 198 | crypto:hash_update( 199 | crypto:hash_update(crypto:hash_init(sha), Salt), 200 | Stage2) 201 | ), 202 | bxor_binary(Res, Stage1). 203 | 204 | 205 | do_send(Sock, Packet, Num, LogFun) -> 206 | LogFun(?MODULE, ?LINE, debug, 207 | fun() -> {"mysql_auth send packet ~p: ~p", [Num, Packet]} end), 208 | Data = <<(size(Packet)):24/little, Num:8, Packet/binary>>, 209 | gen_tcp:send(Sock, Data). 210 | -------------------------------------------------------------------------------- /src/mysql_conn.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizzyd/erlang-mysql-driver/051f22e1cbe130061b43273a14217c659e6b860d/src/mysql_conn.erl -------------------------------------------------------------------------------- /src/mysql_recv.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizzyd/erlang-mysql-driver/051f22e1cbe130061b43273a14217c659e6b860d/src/mysql_recv.erl -------------------------------------------------------------------------------- /test/mysql_test.erl: -------------------------------------------------------------------------------- 1 | %% file: mysql_test.erl 2 | %% author: Yariv Sadan (yarivvv@gmail.com) 3 | %% for license see COPYING 4 | 5 | -module(mysql_test). 6 | -compile(export_all). 7 | 8 | test() -> 9 | compile:file("/usr/local/lib/erlang/lib/mysql/mysql.erl"), 10 | compile:file("/usr/local/lib/erlang/lib/mysql/mysql_conn.erl"), 11 | 12 | %% Start the MySQL dispatcher and create the first connection 13 | %% to the database. 'p1' is the connection pool identifier. 14 | mysql:start_link(p1, "localhost", "root", "password", "test"), 15 | 16 | %% Add 2 more connections to the connection pool 17 | mysql:connect(p1, "localhost", undefined, "root", "password", "test", 18 | true), 19 | mysql:connect(p1, "localhost", undefined, "root", "password", "test", 20 | true), 21 | 22 | mysql:fetch(p1, <<"DELETE FROM developer">>), 23 | 24 | mysql:fetch(p1, <<"INSERT INTO developer(name, country) VALUES " 25 | "('Claes (Klacke) Wikstrom', 'Sweden')," 26 | "('Ulf Wiger', 'USA')">>), 27 | 28 | %% Execute a query (using a binary) 29 | Result1 = mysql:fetch(p1, <<"SELECT * FROM developer">>), 30 | io:format("Result1: ~p~n", [Result1]), 31 | 32 | %% Register a prepared statement 33 | mysql:prepare(update_developer_country, 34 | <<"UPDATE developer SET country=? where name like ?">>), 35 | 36 | %% Execute the prepared statement 37 | mysql:execute(p1, update_developer_country, [<<"Sweden">>, <<"%Wiger">>]), 38 | 39 | Result2 = mysql:fetch(p1, <<"SELECT * FROM developer">>), 40 | io:format("Result2: ~p~n", [Result2]), 41 | 42 | mysql:transaction( 43 | p1, 44 | fun() -> mysql:fetch(<<"INSERT INTO developer(name, country) VALUES " 45 | "('Joe Armstrong', 'USA')">>), 46 | mysql:fetch(<<"DELETE FROM developer WHERE name like " 47 | "'Claes%'">>) 48 | end), 49 | 50 | Result3 = mysql:fetch(p1, <<"SELECT * FROM developer">>), 51 | io:format("Result3: ~p~n", [Result3]), 52 | 53 | mysql:prepare(delete_all, <<"DELETE FROM developer">>), 54 | 55 | {aborted, {{error, foo}, _}} = 56 | mysql:transaction( 57 | p1, 58 | fun() -> mysql:execute(delete_all), 59 | throw({error, foo}) 60 | end), 61 | 62 | Result4 = mysql:fetch(p1, <<"SELECT * FROM developer">>), 63 | io:format("Result4: ~p~n", [Result4]), 64 | 65 | mysql:fetch(p1, <<"DELETE FROM numbers">>), 66 | mysql:prepare(insert_number, 67 | <<"INSERT INTO numbers(name, _double) VALUES (?, ?)">>), 68 | D = 1.0e-16, 69 | mysql:execute(p1, insert_number, [<<"t1">>, D]), 70 | Result5 = mysql:fetch(p1, <<"SELECT * FROM numbers WHERE name='t1'">>), 71 | {data, {mysql_result, _, [[<<"t1">>, D]], _, _, _, _, _}} = Result5, 72 | mysql:fetch(p1, <<"DELETE FROM numbers">>), 73 | 74 | ok. 75 | 76 | 77 | --------------------------------------------------------------------------------