api: initial import of mysql driver in webmachine

This commit is contained in:
Bart Van Der Meerssche 2010-03-13 13:00:51 +00:00
parent f34f06cb22
commit 3de06ee609
29 changed files with 2193 additions and 19 deletions

View File

@ -2,7 +2,7 @@ ERL ?= erl
EBIN_DIRS := $(wildcard deps/*/ebin)
APP := flukso
all: erl ebin/$(APP).app erlrrd
all: erl ebin/$(APP).app erlrrd mysql
erl:
@$(ERL) -pa $(EBIN_DIRS) -noinput +B \
@ -21,3 +21,5 @@ ebin/$(APP).app: src/$(APP).app
erlrrd:
@(cd deps/erlrrd;$(MAKE))
mysql:
@(cd deps/mysql;$(MAKE))

View File

@ -1,12 +0,0 @@
{application, erlrrd,
[{description, "erlang rrdtool port"},
{vsn, "1.0"},
{modules, [
erlrrd,
erlrrd_app,
erlrrd_sup
]},
{registered, [erlrrd, erlrrd_sup]},
{mod, {erlrrd_app, []}},
{env, []},
{applications, [kernel, stdlib]}]}.

View File

@ -0,0 +1,11 @@
all:
(cd src;$(MAKE) all)
edoc:
(cd src;$(MAKE) edoc)
test:
(cd src;$(MAKE) test)
clean:
(cd src;$(MAKE) clean)

View File

@ -0,0 +1,3 @@
{application,mysql}.
{packages,[]}.
{modules,[mysql,mysql_auth,mysql_conn,mysql_recv]}.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,17 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>The mysql application</title>
</head>
<frameset cols="20%,80%">
<frame src="modules-frame.html" name="modulesFrame" title="">
<frame src="overview-summary.html" name="overviewFrame" title="">
<noframes>
<h2>This page uses frames</h2>
<p>Your browser does not accept frames.
<br>You should go to the <a href="overview-summary.html">non-frame version</a> instead.
</p>
</noframes>
</frameset>
</html>

View File

@ -0,0 +1,15 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>The mysql application</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<h2 class="indextitle">Modules</h2>
<table width="100%" border="0" summary="list of modules">
<tr><td><a href="mysql.html" target="overviewFrame" class="module">mysql</a></td></tr>
<tr><td><a href="mysql_auth.html" target="overviewFrame" class="module">mysql_auth</a></td></tr>
<tr><td><a href="mysql_conn.html" target="overviewFrame" class="module">mysql_conn</a></td></tr>
<tr><td><a href="mysql_recv.html" target="overviewFrame" class="module">mysql_recv</a></td></tr></table>
</body>
</html>

View File

@ -0,0 +1,145 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Module mysql</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>
<h1>Module mysql</h1>
<ul class="index"><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
<p><b>Behaviours:</b> <a href="gen_server.html"><tt>gen_server</tt></a>.</p>
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#asciz_binary-2">asciz_binary/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#code_change-3">code_change/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#connect-7">connect/7</a></td><td></td></tr>
<tr><td valign="top"><a href="#fetch-2">fetch/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#fetch-3">fetch/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#get_result_affected_rows-1">get_result_affected_rows/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#get_result_field_info-1">get_result_field_info/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#get_result_reason-1">get_result_reason/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#get_result_rows-1">get_result_rows/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#handle_call-3">handle_call/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#handle_cast-2">handle_cast/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#handle_info-2">handle_info/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#init-1">init/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#log-3">log/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#log-4">log/4</a></td><td></td></tr>
<tr><td valign="top"><a href="#quote-1">quote/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#start_link-5">start_link/5</a></td><td></td></tr>
<tr><td valign="top"><a href="#start_link-6">start_link/6</a></td><td></td></tr>
<tr><td valign="top"><a href="#start_link-7">start_link/7</a></td><td></td></tr>
<tr><td valign="top"><a href="#terminate-2">terminate/2</a></td><td></td></tr>
</table>
<h2><a name="functions">Function Details</a></h2>
<h3 class="function"><a name="asciz_binary-2">asciz_binary/2</a></h3>
<div class="spec">
<p><tt>asciz_binary() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="code_change-3">code_change/3</a></h3>
<div class="spec">
<p><tt>code_change() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="connect-7">connect/7</a></h3>
<div class="spec">
<p><tt>connect() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="fetch-2">fetch/2</a></h3>
<div class="spec">
<p><tt>fetch() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="fetch-3">fetch/3</a></h3>
<div class="spec">
<p><tt>fetch() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="get_result_affected_rows-1">get_result_affected_rows/1</a></h3>
<div class="spec">
<p><tt>get_result_affected_rows() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="get_result_field_info-1">get_result_field_info/1</a></h3>
<div class="spec">
<p><tt>get_result_field_info() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="get_result_reason-1">get_result_reason/1</a></h3>
<div class="spec">
<p><tt>get_result_reason() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="get_result_rows-1">get_result_rows/1</a></h3>
<div class="spec">
<p><tt>get_result_rows() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="handle_call-3">handle_call/3</a></h3>
<div class="spec">
<p><tt>handle_call() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="handle_cast-2">handle_cast/2</a></h3>
<div class="spec">
<p><tt>handle_cast() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="handle_info-2">handle_info/2</a></h3>
<div class="spec">
<p><tt>handle_info() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="init-1">init/1</a></h3>
<div class="spec">
<p><tt>init() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="log-3">log/3</a></h3>
<div class="spec">
<p><tt>log() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="log-4">log/4</a></h3>
<div class="spec">
<p><tt>log() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="quote-1">quote/1</a></h3>
<div class="spec">
<p><tt>quote() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="start_link-5">start_link/5</a></h3>
<div class="spec">
<p><tt>start_link() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="start_link-6">start_link/6</a></h3>
<div class="spec">
<p><tt>start_link() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="start_link-7">start_link/7</a></h3>
<div class="spec">
<p><tt>start_link() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="terminate-2">terminate/2</a></h3>
<div class="spec">
<p><tt>terminate() -&gt; term()</tt></p>
</div>
<hr>
<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc, Nov 12 2009, 13:11:27.</i></p>
</body>
</html>

View File

@ -0,0 +1,36 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Module mysql_auth</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>
<h1>Module mysql_auth</h1>
<ul class="index"><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#do_new_auth-8">do_new_auth/8</a></td><td></td></tr>
<tr><td valign="top"><a href="#do_old_auth-7">do_old_auth/7</a></td><td></td></tr>
</table>
<h2><a name="functions">Function Details</a></h2>
<h3 class="function"><a name="do_new_auth-8">do_new_auth/8</a></h3>
<div class="spec">
<p><tt>do_new_auth() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="do_old_auth-7">do_old_auth/7</a></h3>
<div class="spec">
<p><tt>do_old_auth() -&gt; term()</tt></p>
</div>
<hr>
<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc, Nov 12 2009, 13:11:27.</i></p>
</body>
</html>

View File

@ -0,0 +1,60 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Module mysql_conn</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>
<h1>Module mysql_conn</h1>
<ul class="index"><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#do_recv-3">do_recv/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#fetch-3">fetch/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#fetch-4">fetch/4</a></td><td></td></tr>
<tr><td valign="top"><a href="#squery-4">squery/4</a></td><td></td></tr>
<tr><td valign="top"><a href="#start-6">start/6</a></td><td></td></tr>
<tr><td valign="top"><a href="#start_link-6">start_link/6</a></td><td></td></tr>
</table>
<h2><a name="functions">Function Details</a></h2>
<h3 class="function"><a name="do_recv-3">do_recv/3</a></h3>
<div class="spec">
<p><tt>do_recv() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="fetch-3">fetch/3</a></h3>
<div class="spec">
<p><tt>fetch() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="fetch-4">fetch/4</a></h3>
<div class="spec">
<p><tt>fetch() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="squery-4">squery/4</a></h3>
<div class="spec">
<p><tt>squery() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="start-6">start/6</a></h3>
<div class="spec">
<p><tt>start() -&gt; term()</tt></p>
</div>
<h3 class="function"><a name="start_link-6">start_link/6</a></h3>
<div class="spec">
<p><tt>start_link() -&gt; term()</tt></p>
</div>
<hr>
<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc, Nov 12 2009, 13:11:27.</i></p>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Module mysql_recv</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>
<h1>Module mysql_recv</h1>
<ul class="index"><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#start_link-4">start_link/4</a></td><td></td></tr>
</table>
<h2><a name="functions">Function Details</a></h2>
<h3 class="function"><a name="start_link-4">start_link/4</a></h3>
<div class="spec">
<p><tt>start_link() -&gt; term()</tt></p>
</div>
<hr>
<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc, Nov 12 2009, 13:11:27.</i></p>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>The mysql application</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<h1>The mysql application</h1>
<hr>
<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc, Nov 12 2009, 13:11:27.</i></p>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>The mysql application</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<h2 class="indextitle">Packages</h2>
<table width="100%" border="0" summary="list of packages"></table>
</body>
</html>

View File

@ -0,0 +1,55 @@
/* standard EDoc style sheet */
body {
font-family: Verdana, Arial, Helvetica, sans-serif;
margin-left: .25in;
margin-right: .2in;
margin-top: 0.2in;
margin-bottom: 0.2in;
color: #000000;
background-color: #ffffff;
}
h1,h2 {
margin-left: -0.2in;
}
div.navbar {
background-color: #add8e6;
padding: 0.2em;
}
h2.indextitle {
padding: 0.4em;
background-color: #add8e6;
}
h3.function,h3.typedecl {
background-color: #add8e6;
padding-left: 1em;
}
div.spec {
margin-left: 2em;
background-color: #eeeeee;
}
a.module,a.package {
text-decoration:none
}
a.module:hover,a.package:hover {
background-color: #eeeeee;
}
ul.definitions {
list-style-type: none;
}
ul.index {
list-style-type: none;
background-color: #eeeeee;
}
/*
* Minor style tweaks
*/
ul {
list-style-type: square;
}
table {
border-collapse: collapse;
}
td {
padding: 3
}

View File

@ -0,0 +1,20 @@
include ../support/include.mk
APPLICATION=mysql
DOC_OPTS={dir,\"../doc\"}
all: $(EBIN_FILES)
debug:
$(MAKE) DEBUG=-DDEBUG
clean:
rm -rf $(EBIN_FILES)
edoc:
$(ERL) -noshell -pa ../ebin \
-eval "edoc:application($(APPLICATION), \".\", [$(DOC_OPTS)])" \
-s init stop
test: all
$(ERL) -noshell -pa ../ebin -s $(APPLICATION) test -s init stop

View File

@ -0,0 +1,12 @@
{application, mysql,
[{description, "erlang mysql driver"},
{vsn, "1.0"},
{modules, [
mysql,
mysql_app,
mysql_sup
]},
{registered, [mysql, mysql_sup]},
{mod, {mysql_app, []}},
{env, []},
{applications, [kernel, stdlib]}]}.

View File

@ -0,0 +1,656 @@
%%%-------------------------------------------------------------------
%%% File : mysql.erl
%%% Author : Magnus Ahltorp <ahltorp@nada.kth.se>
%%% Descrip.: MySQL client.
%%%
%%% Created : 4 Aug 2005 by Magnus Ahltorp <ahltorp@nada.kth.se>
%%%
%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan
%%% See the file COPYING
%%%
%%% Usage:
%%%
%%%
%%% Call one of the start-functions before any call to fetch/2
%%%
%%% start_link(Id, Host, User, Password, Database)
%%% start_link(Id, Host, Port, User, Password, Database)
%%% start_link(Id, Host, User, Password, Database, LogFun)
%%% start_link(Id, Host, Port, User, Password, Database, LogFun)
%%%
%%% Id is a connection group identifier. If you want to have more
%%% than one connection to a server (or a set of MySQL replicas),
%%% add more with
%%%
%%% connect(Id, Host, Port, User, Password, Database, Reconnect)
%%%
%%% use 'undefined' as Port to get default MySQL port number (3306).
%%% MySQL querys will be sent in a per-Id round-robin fashion.
%%% Set Reconnect to 'true' if you want the dispatcher to try and
%%% open a new connection, should this one die.
%%%
%%% When you have a mysql_dispatcher running, this is how you make a
%%% query :
%%%
%%% fetch(Id, "select * from hello") -> Result
%%% Result = {data, MySQLRes} | {updated, MySQLRes} |
%%% {error, MySQLRes}
%%%
%%% Actual data can be extracted from MySQLRes by calling the following API
%%% functions:
%%% - on data received:
%%% FieldInfo = mysql:get_result_field_info(MysqlRes)
%%% AllRows = mysql:get_result_rows(MysqlRes)
%%% with FieldInfo = list() of {Table, Field, Length, Name}
%%% and AllRows = list() of list() representing records
%%% - on update:
%%% Affected = mysql:get_result_affected_rows(MysqlRes)
%%% with Affected = integer()
%%% - on error:
%%% Reason = mysql:get_result_reason(MysqlRes)
%%% with Reason = string()
%%%
%%% If you just want a single MySQL connection, or want to manage your
%%% connections yourself, you can use the mysql_conn module as a
%%% stand-alone single MySQL connection. See the comment at the top of
%%% mysql_conn.erl.
%%%
%%%-------------------------------------------------------------------
-module(mysql).
-behaviour(gen_server).
%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([start_link/5,
start_link/6,
start_link/7,
fetch/2,
fetch/3,
get_result_field_info/1,
get_result_rows/1,
get_result_affected_rows/1,
get_result_reason/1,
quote/1,
asciz_binary/2,
connect/7
]).
%%--------------------------------------------------------------------
%% Internal exports - just for mysql_* modules
%%--------------------------------------------------------------------
-export([log/3,
log/4
]).
%%--------------------------------------------------------------------
%% Internal exports - gen_server callbacks
%%--------------------------------------------------------------------
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
%%--------------------------------------------------------------------
%% Records
%%--------------------------------------------------------------------
-include("mysql.hrl").
-record(state, {
conn_list, %% list() of mysql_connection record()
log_fun %% undefined | function for logging,
}).
-record(mysql_connection, {
id, %% term(), user of 'mysql' modules id of this socket group
conn_pid, %% pid(), mysql_conn process
reconnect, %% true | false, should mysql_dispatcher try to reconnect if this connection dies?
host, %% undefined | string()
port, %% undefined | integer()
user, %% undefined | string()
password, %% undefined | string()
database %% undefined | string()
}).
%%--------------------------------------------------------------------
%% Macros
%%--------------------------------------------------------------------
-define(SERVER, mysql_dispatcher).
-define(CONNECT_TIMEOUT, 5000).
-define(LOCAL_FILES, 128).
-define(PORT, 3306).
%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link(Id, Host, User, Password, Database)
%% start_link(Id, Host, Port, User, Password, Database)
%% start_link(Id, Host, User, Password, Database, LogFun)
%% start_link(Id, Host, Port, User, Password, Database,
%% LogFun)
%% Id = term(), first connection-group Id
%% Host = string()
%% Port = integer()
%% User = string()
%% Password = string()
%% Database = string()
%% LogFun = undefined | function() of arity 3
%% Descrip.: Starts the MySQL client gen_server process.
%% Returns : {ok, Pid} | ignore | {error, Error}
%%--------------------------------------------------------------------
start_link(Id, Host, User, Password, Database) when is_list(Host), is_list(User), is_list(Password),
is_list(Database) ->
start_link(Id, Host, ?PORT, User, Password, Database, undefined).
start_link(Id, Host, Port, User, Password, Database) when is_list(Host), is_integer(Port), is_list(User),
is_list(Password), is_list(Database) ->
start_link(Id, Host, Port, User, Password, Database, undefined);
start_link(Id, Host, User, Password, Database, LogFun) when is_list(Host), is_list(User), is_list(Password),
is_list(Database) ->
start_link(Id, Host, ?PORT, User, Password, Database, LogFun).
start_link(Id, Host, Port, User, Password, Database, LogFun) when is_list(Host), is_integer(Port), is_list(User),
is_list(Password), is_list(Database) ->
crypto:start(),
gen_server:start_link({local, ?SERVER}, ?MODULE, [Id, Host, Port, User, Password, Database, LogFun], []).
%%--------------------------------------------------------------------
%% Function: fetch(Id, Query)
%% fetch(Id, Query, Timeout)
%% Id = term(), connection-group Id
%% Query = string(), MySQL query in verbatim
%% Timeout = integer() | infinity, gen_server timeout value
%% Descrip.: Send a query and wait for the result.
%% Returns : {data, MySQLRes} |
%% {updated, MySQLRes} |
%% {error, MySQLRes}
%% MySQLRes = term()
%%--------------------------------------------------------------------
fetch(Id, Query) when is_list(Query) ->
gen_server:call(?SERVER, {fetch, Id, Query}).
fetch(Id, Query, Timeout) when is_list(Query) ->
gen_server:call(?SERVER, {fetch, Id, Query}, Timeout).
%%--------------------------------------------------------------------
%% Function: get_result_field_info(MySQLRes)
%% MySQLRes = term(), result of fetch function on "data"
%% Descrip.: Extract the FieldInfo from MySQL Result on data received
%% Returns : FieldInfo
%% FieldInfo = list() of {Table, Field, Length, Name}
%%--------------------------------------------------------------------
get_result_field_info(#mysql_result{fieldinfo = FieldInfo}) ->
FieldInfo.
%%--------------------------------------------------------------------
%% Function: get_result_rows(MySQLRes)
%% MySQLRes = term(), result of fetch function on "data"
%% Descrip.: Extract the Rows from MySQL Result on data received
%% Returns : Rows
%% Rows = list() of list() representing records
%%--------------------------------------------------------------------
get_result_rows(#mysql_result{rows=AllRows}) ->
AllRows.
%%--------------------------------------------------------------------
%% Function: get_result_affected_rows(MySQLRes)
%% MySQLRes = term(), result of fetch function on "updated"
%% Descrip.: Extract the Rows from MySQL Result on update
%% Returns : AffectedRows
%% AffectedRows = integer()
%%--------------------------------------------------------------------
get_result_affected_rows(#mysql_result{affectedrows=AffectedRows}) ->
AffectedRows.
%%--------------------------------------------------------------------
%% Function: get_result_reason(MySQLRes)
%% MySQLRes = term(), result of fetch function on "error"
%% Descrip.: Extract the error Reason from MySQL Result on error
%% Returns : Reason
%% Reason = string()
%%--------------------------------------------------------------------
get_result_reason(#mysql_result{error=Reason}) ->
Reason.
%%--------------------------------------------------------------------
%% Function: quote(String)
%% String = string()
%% Descrip.: Quote a string so that it can be included safely in a
%% MySQL query.
%% Returns : Quoted = string()
%%--------------------------------------------------------------------
quote(String) when is_list(String) ->
[34 | lists:reverse([34 | quote(String, [])])]. %% 34 is $"
quote([], Acc) ->
Acc;
quote([0 | Rest], Acc) ->
quote(Rest, [$0, $\\ | Acc]);
quote([10 | Rest], Acc) ->
quote(Rest, [$n, $\\ | Acc]);
quote([13 | Rest], Acc) ->
quote(Rest, [$r, $\\ | Acc]);
quote([$\\ | Rest], Acc) ->
quote(Rest, [$\\ , $\\ | Acc]);
quote([39 | Rest], Acc) -> %% 39 is $'
quote(Rest, [39, $\\ | Acc]); %% 39 is $'
quote([34 | Rest], Acc) -> %% 34 is $"
quote(Rest, [34, $\\ | Acc]); %% 34 is $"
quote([26 | Rest], Acc) ->
quote(Rest, [$Z, $\\ | Acc]);
quote([C | Rest], Acc) ->
quote(Rest, [C | Acc]).
%%--------------------------------------------------------------------
%% Function: asciz_binary(Data, Acc)
%% Data = binary()
%% Acc = list(), input accumulator
%% Descrip.: Find the first zero-byte in Data and add everything
%% before it to Acc, as a string.
%% Returns : {NewList, Rest}
%% NewList = list(), Acc plus what we extracted from Data
%% Rest = binary(), whatever was left of Data, not
%% including the zero-byte
%%--------------------------------------------------------------------
asciz_binary(<<>>, Acc) ->
{lists:reverse(Acc), <<>>};
asciz_binary(<<0:8, Rest/binary>>, Acc) ->
{lists:reverse(Acc), Rest};
asciz_binary(<<C:8, Rest/binary>>, Acc) ->
asciz_binary(Rest, [C | Acc]).
%%--------------------------------------------------------------------
%% Function: connect(Id, Host, Port, User, Password, Database,
%% Reconnect)
%% Id = term(), connection-group Id
%% Host = string()
%% Port = undefined | integer()
%% User = string()
%% Password = string()
%% Database = string()
%% Reconnect = true | false
%% Descrip.: Starts a MySQL connection and, if successfull, registers
%% it with the mysql_dispatcher.
%% Returns : {ok, ConnPid} | {error, Reason}
%%--------------------------------------------------------------------
connect(Id, Host, undefined, User, Password, Database, Reconnect) ->
connect(Id, Host, ?PORT, User, Password, Database, Reconnect);
connect(Id, Host, Port, User, Password, Database, Reconnect) ->
{ok, LogFun} = gen_server:call(?SERVER, get_logfun),
case mysql_conn:start(Host, Port, User, Password, Database, LogFun) of
{ok, ConnPid} ->
MysqlConn =
case Reconnect of
true ->
#mysql_connection{id = Id,
conn_pid = ConnPid,
reconnect = true,
host = Host,
port = Port,
user = User,
password = Password,
database = Database
};
false ->
#mysql_connection{id = Id,
conn_pid = ConnPid,
reconnect = false
}
end,
case gen_server:call(?SERVER, {add_mysql_connection, MysqlConn}) of
ok ->
{ok, ConnPid};
Res ->
Res
end;
{error, Reason} ->
{error, Reason}
end.
%%--------------------------------------------------------------------
%% Function: log(LogFun, Level, Format)
%% log(LogFun, Level, Format, Arguments)
%% LogFun = undefined | function() with arity 3
%% Level = debug | normal | error
%% Format = string()
%% Arguments = list() of term()
%% Descrip.: Either call the function LogFun with the Level, Format
%% and Arguments as parameters or log it to the console if
%% LogFun is undefined.
%% Returns : void()
%%
%% Note : Exported only for use by the mysql_* modules.
%%
%%--------------------------------------------------------------------
log(LogFun, Level, Format) ->
log(LogFun, Level, Format, []).
log(LogFun, Level, Format, Arguments) when is_function(LogFun) ->
LogFun(Level, Format, Arguments);
log(undefined, _Level, Format, Arguments) ->
%% default is to log to console
io:format(Format, Arguments),
io:format("~n", []).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Args = [Id, Host, Port, User, Password, Database, LogFun]
%% Id = term(), connection-group Id
%% Host = string()
%% Port = integer()
%% User = string()
%% Password = string()
%% Database = string()
%% LogFun = undefined | function() with arity 3
%% Descrip.: Initiates the gen_server (MySQL dispatcher).
%%--------------------------------------------------------------------
init([Id, Host, Port, User, Password, Database, LogFun]) ->
case mysql_conn:start(Host, Port, User, Password, Database, LogFun) of
{ok, ConnPid} ->
MysqlConn = #mysql_connection{id = Id,
conn_pid = ConnPid,
reconnect = true,
host = Host,
port = Port,
user = User,
password = Password,
database = Database
},
case add_mysql_conn(MysqlConn, []) of
{ok, ConnList} ->
{ok, #state{log_fun = LogFun,
conn_list = ConnList
}};
error ->
Msg = "mysql: Failed adding first MySQL connection handler to my list, exiting",
log(LogFun, error, Msg),
{error, Msg}
end;
{error, Reason} ->
log(LogFun, error, "mysql: Failed starting first MySQL connection handler, exiting"),
{stop, {error, Reason}}
end.
%%--------------------------------------------------------------------
%% Function: handle_call(Msg, From, State)
%% Descrip.: Handling call messages.
%% Returns : {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% Function: handle_call({fetch, Id, Query}, From, State)
%% Id = term(), connection-group id
%% Query = string(), MySQL query
%% Descrip.: Make a MySQL query. Use the first connection matching Id
%% in our connection-list. Don't block the mysql_dispatcher
%% by returning {noreply, ...} here and let the mysql_conn
%% do gen_server:reply(...) when it has an answer.
%% Returns : {noreply, NewState} |
%% {reply, {error, Reason}, State}
%% NewState = state record()
%% Reason = atom() | string()
%%--------------------------------------------------------------------
handle_call({fetch, Id, Query}, From, State) ->
log(State#state.log_fun, debug, "mysql: fetch ~p (id ~p)", [Query, Id]),
case get_next_mysql_connection_for_id(Id, State#state.conn_list) of
{ok, MysqlConn, RestOfConnList} when is_record(MysqlConn, mysql_connection) ->
mysql_conn:fetch(MysqlConn#mysql_connection.conn_pid, Query, From),
%% move this mysql socket to the back of the list
NewConnList = RestOfConnList ++ [MysqlConn],
%% The ConnPid process does a gen_server:reply() when it has an answer
{noreply, State#state{conn_list = NewConnList}};
nomatch ->
%% we have no active connection matching Id
{reply, {error, no_connection}, State}
end;
%%--------------------------------------------------------------------
%% Function: handle_call({add_mysql_connection, Conn}, From, State)
%% Conn = mysql_connection record()
%% Descrip.: Add Conn to our list of connections.
%% Returns : {reply, Reply, NewState}
%% Reply = ok | {error, Reason}
%% NewState = state record()
%% Reason = string()
%%--------------------------------------------------------------------
handle_call({add_mysql_connection, Conn}, _From, State) when is_record(Conn, mysql_connection) ->
case add_mysql_conn(Conn, State#state.conn_list) of
{ok, NewConnList} ->
{Id, ConnPid} = {Conn#mysql_connection.id, Conn#mysql_connection.conn_pid},
log(State#state.log_fun, normal, "mysql: Added connection with id '~p' (pid ~p) to my list",
[Id, ConnPid]),
{reply, ok, State#state{conn_list = NewConnList}};
error ->
{reply, {error, "failed adding MySQL connection to my list"}, State}
end;
%%--------------------------------------------------------------------
%% Function: handle_call(get_logfun, From, State)
%% Descrip.: Fetch our logfun.
%% Returns : {reply, {ok, LogFun}, State}
%% LogFun = undefined | function() with arity 3
%%--------------------------------------------------------------------
handle_call(get_logfun, _From, State) ->
{reply, {ok, State#state.log_fun}, State};
handle_call(Unknown, _From, State) ->
log(State#state.log_fun, error, "mysql: Received unknown gen_server call : ~p", [Unknown]),
{reply, {error, "unknown gen_server call in mysql client"}, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State)
%% Descrip.: Handling cast messages
%% Returns : {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_cast(Unknown, State) ->
log(State#state.log_fun, error, "mysql: Received unknown gen_server cast : ~p", [Unknown]),
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Msg, State)
%% Descrip.: Handling all non call/cast messages
%% Returns : {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% Function: handle_info({'DOWN', ...}, State)
%% Descrip.: Handle a message that one of our monitored processes
%% (mysql_conn processes in our connection list) has exited.
%% Remove the entry from our list.
%% Returns : {noreply, NewState} |
%% {stop, normal, State}
%% NewState = state record()
%%
%% Note : For now, we stop if our connection list becomes empty.
%% We should try to reconnect for a while first, to not
%% eventually stop the whole OTP application if the MySQL-
%% server is shut down and the mysql_dispatcher was super-
%% vised by an OTP supervisor.
%%--------------------------------------------------------------------
handle_info({'DOWN', _MonitorRef, process, Pid, Info}, State) ->
LogFun = State#state.log_fun,
case remove_mysql_connection_using_pid(Pid, State#state.conn_list, []) of
{ok, Conn, NewConnList} ->
LogLevel = case Info of
normal -> normal;
_ -> error
end,
log(LogFun, LogLevel, "mysql: MySQL connection pid ~p exited : ~p", [Pid, Info]),
log(LogFun, normal, "mysql: Removed MySQL connection with pid ~p from list",
[Pid]),
case Conn#mysql_connection.reconnect of
true ->
start_reconnect(Conn, LogFun);
false ->
ok
end,
{noreply, State#state{conn_list = NewConnList}};
nomatch ->
log(LogFun, error, "mysql: Received 'DOWN' signal from pid ~p not in my list", [Pid]),
{noreply, State}
end;
handle_info(Info, State) ->
log(State#state.log_fun, error, "mysql: Received unknown signal : ~p", [Info]),
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State)
%% Descrip.: Shutdown the server
%% Returns : Reason
%%--------------------------------------------------------------------
terminate(Reason, State) ->
LogFun = State#state.log_fun,
LogLevel = case Reason of
normal -> debug;
_ -> error
end,
log(LogFun, LogLevel, "mysql: Terminating with reason : ~p", [Reason]),
Reason.
%%--------------------------------------------------------------------
%% Function: code_change(_OldVsn, State, _Extra)
%% Descrip.: Convert process state when code is changed
%% Returns : {ok, State}
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%====================================================================
%% Internal functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: add_mysql_conn(Conn, ConnList)
%% Conn = mysql_connection record()
%% ConnList = list() of mysql_connection record()
%% Descrip.: Set up process monitoring of the mysql_conn process and
%% then add it (first) to ConnList.
%% Returns : NewConnList = list() of mysql_connection record()
%%--------------------------------------------------------------------
add_mysql_conn(Conn, ConnList) when is_record(Conn, mysql_connection), is_list(ConnList) ->
erlang:monitor(process, Conn#mysql_connection.conn_pid),
{ok, [Conn | ConnList]}.
%%--------------------------------------------------------------------
%% Function: remove_mysql_connection_using_pid(Pid, ConnList)
%% Pid = pid()
%% ConnList = list() of mysql_connection record()
%% Descrip.: Removes the first mysql_connection in ConnList that has
%% a pid matching Pid.
%% Returns : {ok, Conn, NewConnList} | nomatch
%% Conn = mysql_connection record()
%% NewConnList = list() of mysql_connection record()
%%--------------------------------------------------------------------
remove_mysql_connection_using_pid(Pid, [#mysql_connection{conn_pid = Pid} = H | T], Res) ->
{ok, H, lists:reverse(Res) ++ T};
remove_mysql_connection_using_pid(Pid, [H | T], Res) when is_record(H, mysql_connection) ->
remove_mysql_connection_using_pid(Pid, T, [H | Res]);
remove_mysql_connection_using_pid(_Pid, [], _Res) ->
nomatch.
%%--------------------------------------------------------------------
%% Function: get_next_mysql_connection_for_id(Id, ConnList)
%% Id = term(), connection-group id
%% ConnList = list() of mysql_connection record()
%% Descrip.: Find the first mysql_connection in ConnList that has an
%% id matching Id.
%% Returns : {ok, Conn, NewConnList} | nomatch
%% Conn = mysql_connection record()
%% NewConnList = list() of mysql_connection record(), same
%% as ConnList but without Conn
%%--------------------------------------------------------------------
get_next_mysql_connection_for_id(Id, ConnList) ->
get_next_mysql_connection_for_id(Id, ConnList, []).
get_next_mysql_connection_for_id(Id, [#mysql_connection{id = Id} = H | T], Res) ->
{ok, H, lists:reverse(Res) ++ T};
get_next_mysql_connection_for_id(Id, [H | T], Res) when is_record(H, mysql_connection) ->
get_next_mysql_connection_for_id(Id, T, [H | Res]);
get_next_mysql_connection_for_id(_Id, [], _Res) ->
nomatch.
%%--------------------------------------------------------------------
%% Function: start_reconnect(Conn, LogFun)
%% Conn = mysql_connection record()
%% LogFun = undefined | function() with arity 3
%% Descrip.: Spawns a process that will try to re-establish a new
%% connection instead of the one in Conn which has just
%% died.
%% Returns : ok
%%--------------------------------------------------------------------
start_reconnect(Conn, LogFun) when is_record(Conn, mysql_connection) ->
Pid = spawn(fun () ->
reconnect_loop(Conn#mysql_connection{conn_pid = undefined}, LogFun, 0)
end),
{Id, Host, Port} = {Conn#mysql_connection.id, Conn#mysql_connection.host, Conn#mysql_connection.port},
log(LogFun, debug, "mysql: Started pid ~p to try and reconnect to ~p:~s:~p (replacing "
"connection with pid ~p)", [Pid, Id, Host, Port, Conn#mysql_connection.conn_pid]),
ok.
%%--------------------------------------------------------------------
%% Function: reconnect_loop(Conn, LogFun, 0)
%% Conn = mysql_connection record()
%% LogFun = undefined | function() with arity 3
%% Descrip.: Loop indefinately until we are able to reconnect to the
%% server specified in the now dead connection Conn.
%% Returns : ok
%%--------------------------------------------------------------------
reconnect_loop(Conn, LogFun, N) when is_record(Conn, mysql_connection) ->
{Id, Host, Port} = {Conn#mysql_connection.id, Conn#mysql_connection.host, Conn#mysql_connection.port},
case connect(Id,
Host,
Port,
Conn#mysql_connection.user,
Conn#mysql_connection.password,
Conn#mysql_connection.database,
Conn#mysql_connection.reconnect) of
{ok, ConnPid} ->
log(LogFun, debug, "mysql_reconnect: Managed to reconnect to ~p:~s:~p (connection pid ~p)",
[Id, Host, Port, ConnPid]),
ok;
{error, Reason} ->
%% log every once in a while
NewN = case N of
10 ->
log(LogFun, debug, "mysql_reconnect: Still unable to connect to ~p:~s:~p (~p)",
[Id, Host, Port, Reason]),
0;
_ ->
N + 1
end,
%% sleep between every unsuccessfull attempt
timer:sleep(20 * 1000),
reconnect_loop(Conn, LogFun, NewN)
end.

View File

@ -0,0 +1,6 @@
%% MySQL result record:
-record(mysql_result,
{fieldinfo=[],
rows=[],
affectedrows=0,
error=""}).

View File

@ -0,0 +1,18 @@
-module(mysql_app).
-export([start/0, stop/0]).
-behavior(application).
-export([start/2, stop/1]).
start() ->
application:start(mysql).
start(_Type, _Args) ->
mysql_sup:start_link().
stop() ->
application:stop(mysql).
stop(_State) ->
ok.

View File

@ -0,0 +1,194 @@
%%%-------------------------------------------------------------------
%%% File : mysql_auth.erl
%%% Author : Fredrik Thulin <ft@it.su.se>
%%% Descrip.: MySQL client authentication functions.
%%% Created : 4 Aug 2005 by Fredrik Thulin <ft@it.su.se>
%%%
%%% Note : All MySQL code was written by Magnus Ahltorp, originally
%%% in the file mysql.erl - I just moved it here.
%%%
%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan
%%% See the file COPYING
%%%
%%%-------------------------------------------------------------------
-module(mysql_auth).
%%--------------------------------------------------------------------
%% External exports (should only be used by the 'mysql_conn' module)
%%--------------------------------------------------------------------
-export([
do_old_auth/7,
do_new_auth/8
]).
%%--------------------------------------------------------------------
%% Macros
%%--------------------------------------------------------------------
-define(LONG_PASSWORD, 1).
-define(LONG_FLAG, 4).
-define(PROTOCOL_41, 512).
-define(TRANSACTIONS, 8192).
-define(SECURE_CONNECTION, 32768).
-define(CONNECT_WITH_DB, 8).
-define(MAX_PACKET_SIZE, 1000000).
%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: do_old_auth(Sock, RecvPid, SeqNum, User, Password, Salt1,
%% LogFun)
%% Sock = term(), gen_tcp socket
%% RecvPid = pid(), receiver process pid
%% SeqNum = integer(), first sequence number we should use
%% User = string(), MySQL username
%% Password = string(), MySQL password
%% Salt1 = string(), salt 1 from server greeting
%% LogFun = undefined | function() of arity 3
%% Descrip.: Perform old-style MySQL authentication.
%% Returns : result of mysql_conn:do_recv/3
%%--------------------------------------------------------------------
do_old_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, LogFun) ->
Auth = password_old(Password, Salt1),
Packet2 = make_auth(User, Auth),
do_send(Sock, Packet2, SeqNum, LogFun),
mysql_conn:do_recv(LogFun, RecvPid, SeqNum).
%%--------------------------------------------------------------------
%% Function: do_new_auth(Sock, RecvPid, SeqNum, User, Password, Salt1,
%% Salt2, LogFun)
%% Sock = term(), gen_tcp socket
%% RecvPid = pid(), receiver process pid
%% SeqNum = integer(), first sequence number we should use
%% User = string(), MySQL username
%% Password = string(), MySQL password
%% Salt1 = string(), salt 1 from server greeting
%% Salt2 = string(), salt 2 from server greeting
%% LogFun = undefined | function() of arity 3
%% Descrip.: Perform MySQL authentication.
%% Returns : result of mysql_conn:do_recv/3
%%--------------------------------------------------------------------
do_new_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, Salt2, LogFun) ->
Auth = password_new(Password, Salt1 ++ Salt2),
Packet2 = make_new_auth(User, Auth, none),
do_send(Sock, Packet2, SeqNum, LogFun),
case mysql_conn:do_recv(LogFun, RecvPid, SeqNum) of
{ok, Packet3, SeqNum2} ->
case Packet3 of
<<254:8>> ->
AuthOld = password_old(Password, Salt1),
do_send(Sock, <<AuthOld/binary, 0:8>>, SeqNum2 + 1, LogFun),
mysql_conn:do_recv(LogFun, RecvPid, SeqNum2 + 1);
_ ->
{ok, Packet3, SeqNum2}
end;
{error, Reason} ->
{error, Reason}
end.
%%====================================================================
%% Internal functions
%%====================================================================
password_old(Password, Salt) ->
{P1, P2} = hash(Password),
{S1, S2} = hash(Salt),
Seed1 = P1 bxor S1,
Seed2 = P2 bxor S2,
List = rnd(9, Seed1, Seed2),
{L, [Extra]} = lists:split(8, List),
list_to_binary(lists:map(fun (E) ->
E bxor (Extra - 64)
end, L)).
%% part of do_old_auth/4, which is part of mysql_init/4
make_auth(User, Password) ->
Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS,
Maxsize = 0,
UserB = list_to_binary(User),
PasswordB = Password,
<<Caps:16/little, Maxsize:24/little, UserB/binary, 0:8,
PasswordB/binary>>.
%% part of do_new_auth/4, which is part of mysql_init/4
make_new_auth(User, Password, Database) ->
DBCaps = case Database of
none ->
0;
_ ->
?CONNECT_WITH_DB
end,
Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS bor
?PROTOCOL_41 bor ?SECURE_CONNECTION bor DBCaps,
Maxsize = ?MAX_PACKET_SIZE,
UserB = list_to_binary(User),
PasswordL = size(Password),
DatabaseB = case Database of
none ->
<<>>;
_ ->
list_to_binary(Database)
end,
<<Caps:32/little, Maxsize:32/little, 8:8, 0:23/integer-unit:8,
UserB/binary, 0:8, PasswordL:8, Password/binary, DatabaseB/binary>>.
hash(S) ->
hash(S, 1345345333, 305419889, 7).
hash([C | S], N1, N2, Add) ->
N1_1 = N1 bxor (((N1 band 63) + Add) * C + N1 * 256),
N2_1 = N2 + ((N2 * 256) bxor N1_1),
Add_1 = Add + C,
hash(S, N1_1, N2_1, Add_1);
hash([], N1, N2, _Add) ->
Mask = (1 bsl 31) - 1,
{N1 band Mask , N2 band Mask}.
rnd(N, Seed1, Seed2) ->
Mod = (1 bsl 30) - 1,
rnd(N, [], Seed1 rem Mod, Seed2 rem Mod).
rnd(0, List, _, _) ->
lists:reverse(List);
rnd(N, List, Seed1, Seed2) ->
Mod = (1 bsl 30) - 1,
NSeed1 = (Seed1 * 3 + Seed2) rem Mod,
NSeed2 = (NSeed1 + Seed2 + 33) rem Mod,
Float = (float(NSeed1) / float(Mod))*31,
Val = trunc(Float)+64,
rnd(N - 1, [Val | List], NSeed1, NSeed2).
dualmap(_F, [], []) ->
[];
dualmap(F, [E1 | R1], [E2 | R2]) ->
[F(E1, E2) | dualmap(F, R1, R2)].
bxor_binary(B1, B2) ->
list_to_binary(dualmap(fun (E1, E2) ->
E1 bxor E2
end, binary_to_list(B1), binary_to_list(B2))).
password_new(Password, Salt) ->
%% Check for blank password
case length(Password) of
0 ->
<<>>;
_ ->
Stage1 = crypto:sha(Password),
Stage2 = crypto:sha(Stage1),
Res = crypto:sha_final(
crypto:sha_update(
crypto:sha_update(crypto:sha_init(), Salt),
Stage2)
),
bxor_binary(Res, Stage1)
end.
do_send(Sock, Packet, Num, LogFun) ->
mysql:log(LogFun, debug, "mysql_auth send packet ~p: ~p", [Num, Packet]),
Data = <<(size(Packet)):24/little, Num:8, Packet/binary>>,
gen_tcp:send(Sock, Data).

View File

@ -0,0 +1,656 @@
%%%-------------------------------------------------------------------
%%% File : mysql_conn.erl
%%% Author : Fredrik Thulin <ft@it.su.se>
%%% Descrip.: MySQL connection handler, handles de-framing of messages
%%% received by the MySQL receiver process.
%%% Created : 5 Aug 2005 by Fredrik Thulin <ft@it.su.se>
%%% Modified: 11 Jan 2006 by Mickael Remond <mickael.remond@process-one.net>
%%%
%%% Note : All MySQL code was written by Magnus Ahltorp, originally
%%% in the file mysql.erl - I just moved it here.
%%%
%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan
%%% See the file COPYING
%%%
%%%
%%% This module handles a single connection to a single MySQL server.
%%% You can use it stand-alone, or through the 'mysql' module if you
%%% want to have more than one connection to the server, or
%%% connections to different servers.
%%%
%%% To use it stand-alone, set up the connection with
%%%
%%% {ok, Pid} = mysql_conn:start(Host, Port, User, Password,
%%% Database, LogFun)
%%%
%%% Host = string()
%%% Port = integer()
%%% User = string()
%%% Password = string()
%%% Database = string()
%%% LogFun = undefined | (gives logging to console)
%%% function() of arity 3 (Level, Fmt, Args)
%%%
%%% Note: In stand-alone mode you have to start Erlang crypto application by
%%% yourself with crypto:start()
%%%
%%% and then make MySQL querys with
%%%
%%% Result = mysql_conn:fetch(Pid, Query, self())
%%%
%%% Result = {data, MySQLRes} |
%%% {updated, MySQLRes} |
%%% {error, MySQLRes}
%%% Where: MySQLRes = #mysql_result
%%%
%%% Actual data can be extracted from MySQLRes by calling the following API
%%% functions:
%%% - on data received:
%%% FieldInfo = mysql:get_result_field_info(MysqlRes)
%%% AllRows = mysql:get_result_rows(MysqlRes)
%%% with FieldInfo = list() of {Table, Field, Length, Name}
%%% and AllRows = list() of list() representing records
%%% - on update:
%%% Affected= mysql:get_result_affected_rows(MysqlRes)
%%% with Affected = integer()
%%% - on error:
%%% Reason = mysql:get_result_reason(MysqlRes)
%%% with Reason = string()
%%%-------------------------------------------------------------------
-module(mysql_conn).
%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([start/6,
start_link/6,
fetch/3,
fetch/4,
squery/4
]).
%%--------------------------------------------------------------------
%% External exports (should only be used by the 'mysql_auth' module)
%%--------------------------------------------------------------------
-export([do_recv/3
]).
-include("mysql.hrl").
-record(state, {
mysql_version,
log_fun,
recv_pid,
socket,
data
}).
-define(SECURE_CONNECTION, 32768).
-define(MYSQL_QUERY_OP, 3).
-define(DEFAULT_STANDALONE_TIMEOUT, 5000).
-define(DEFAULT_RESULT_TYPE, list).
-define(MYSQL_4_0, 40). %% Support for MySQL 4.0.x
-define(MYSQL_4_1, 41). %% Support for MySQL 4.1.x et 5.0.x
%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start(Host, Port, User, Password, Database, LogFun)
%% Function: start_link(Host, Port, User, Password, Database, LogFun)
%% Host = string()
%% Port = integer()
%% User = string()
%% Password = string()
%% Database = string()
%% LogFun = undefined | function() of arity 3
%% Descrip.: Starts a mysql_conn process that connects to a MySQL
%% server, logs in and chooses a database.
%% Returns : {ok, Pid} | {error, Reason}
%% Pid = pid()
%% Reason = string()
%%--------------------------------------------------------------------
start(Host, Port, User, Password, Database, LogFun) when is_list(Host), is_integer(Port), is_list(User),
is_list(Password), is_list(Database) ->
ConnPid = self(),
Pid = spawn(fun () ->
init(Host, Port, User, Password, Database, LogFun, ConnPid)
end),
post_start(Pid, LogFun).
start_link(Host, Port, User, Password, Database, LogFun) when is_list(Host), is_integer(Port), is_list(User),
is_list(Password), is_list(Database) ->
ConnPid = self(),
Pid = spawn_link(fun () ->
init(Host, Port, User, Password, Database, LogFun, ConnPid)
end),
post_start(Pid, LogFun).
%% part of start/6 or start_link/6:
post_start(Pid, _LogFun) ->
%%Timeout = get_option(timeout, Options, ?DEFAULT_STANDALONE_TIMEOUT),
%%TODO find a way to get configured Options here
Timeout= ?DEFAULT_STANDALONE_TIMEOUT,
receive
{mysql_conn, Pid, ok} ->
{ok, Pid};
{mysql_conn, Pid, {error, Reason}} ->
{error, Reason}
% Unknown ->
% mysql:log(_LogFun, error, "mysql_conn: Received unknown signal, exiting"),
% mysql:log(_LogFun, debug, "mysql_conn: Unknown signal : ~p", [Unknown]),
% {error, "unknown signal received"}
after Timeout ->
{error, "timed out"}
end.
%%--------------------------------------------------------------------
%% Function: fetch(Pid, Query, From)
%% fetch(Pid, Query, From, Timeout)
%% Pid = pid(), mysql_conn to send fetch-request to
%% Query = string(), MySQL query in verbatim
%% From = pid() or term(), use a From of self() when
%% using this module for a single connection,
%% or pass the gen_server:call/3 From argument if
%% using a gen_server to do the querys (e.g. the
%% mysql_dispatcher)
%% Timeout = integer() | infinity, gen_server timeout value
%% Descrip.: Send a query and wait for the result if running stand-
%% alone (From = self()), but don't block the caller if we
%% are not running stand-alone (From = gen_server From).
%% Returns : ok | (non-stand-alone mode)
%% {data, #mysql_result} | (stand-alone mode)
%% {updated, #mysql_result} | (stand-alone mode)
%% {error, #mysql_result} (stand-alone mode)
%% FieldInfo = term()
%% Rows = list() of [string()]
%% Reason = term()
%%--------------------------------------------------------------------
fetch(Pid, Query, From) ->
squery(Pid, Query, From, []).
fetch(Pid, Query, From, Timeout) ->
squery(Pid, Query, From, [{timeout, Timeout}]).
squery(Pid, Query, From, Options) when is_pid(Pid), is_list(Query) ->
Self = self(),
Timeout = get_option(timeout, Options, ?DEFAULT_STANDALONE_TIMEOUT),
Pid ! {fetch, Query, From, Options},
case From of
Self ->
%% We are not using a mysql_dispatcher, await the response
receive
{fetch_result, Pid, Result} ->
Result
after Timeout ->
{error, "query timed out"}
end;
_ ->
%% From is gen_server From, Pid will do gen_server:reply() when it has an answer
ok
end.
%%--------------------------------------------------------------------
%% Function: do_recv(LogFun, RecvPid, SeqNum)
%% LogFun = undefined | function() with arity 3
%% RecvPid = pid(), mysql_recv process
%% SeqNum = undefined | integer()
%% Descrip.: Wait for a frame decoded and sent to us by RecvPid.
%% Either wait for a specific frame if SeqNum is an integer,
%% or just any frame if SeqNum is undefined.
%% Returns : {ok, Packet, Num} |
%% {error, Reason}
%% Reason = term()
%%
%% Note : Only to be used externally by the 'mysql_auth' module.
%%--------------------------------------------------------------------
do_recv(LogFun, RecvPid, SeqNum) when is_function(LogFun); LogFun == undefined, SeqNum == undefined ->
receive
{mysql_recv, RecvPid, data, Packet, Num} ->
%%mysql:log(LogFun, debug, "mysql_conn: recv packet ~p: ~p", [Num, Packet]),
{ok, Packet, Num};
{mysql_recv, RecvPid, closed, _E} ->
{error, "mysql_recv: socket was closed"}
end;
do_recv(LogFun, RecvPid, SeqNum) when is_function(LogFun); LogFun == undefined, is_integer(SeqNum) ->
ResponseNum = SeqNum + 1,
receive
{mysql_recv, RecvPid, data, Packet, ResponseNum} ->
%%mysql:log(LogFun, debug, "mysql_conn: recv packet ~p: ~p", [ResponseNum, Packet]),
{ok, Packet, ResponseNum};
{mysql_recv, RecvPid, closed, _E} ->
{error, "mysql_recv: socket was closed"}
end.
%%====================================================================
%% Internal functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Host, Port, User, Password, Database, LogFun,
%% Parent)
%% Host = string()
%% Port = integer()
%% User = string()
%% Password = string()
%% Database = string()
%% LogFun = undefined | function() of arity 3
%% Parent = pid() of process starting this mysql_conn
%% Descrip.: Connect to a MySQL server, log in and chooses a database.
%% Report result of this to Parent, and then enter loop() if
%% we were successfull.
%% Returns : void() | does not return
%%--------------------------------------------------------------------
init(Host, Port, User, Password, Database, LogFun, Parent) ->
case mysql_recv:start_link(Host, Port, LogFun, self()) of
{ok, RecvPid, Sock} ->
case mysql_init(Sock, RecvPid, User, Password, LogFun) of
{ok, Version} ->
case do_query(Sock, RecvPid, LogFun, "use " ++ Database, Version, [{result_type, binary}]) of
{error, MySQLRes} ->
mysql:log(LogFun, error, "mysql_conn: Failed changing to database ~p : ~p",
[Database, mysql:get_result_reason(MySQLRes)]),
Parent ! {mysql_conn, self(), {error, failed_changing_database}};
%% ResultType: data | updated
{_ResultType, _MySQLRes} ->
Parent ! {mysql_conn, self(), ok},
State = #state{mysql_version=Version,
recv_pid = RecvPid,
socket = Sock,
log_fun = LogFun,
data = <<>>
},
loop(State)
end;
{error, _Reason} ->
Parent ! {mysql_conn, self(), {error, login_failed}}
end;
E ->
mysql:log(LogFun, error, "mysql_conn: Failed connecting to ~p:~p : ~p",
[Host, Port, E]),
Parent ! {mysql_conn, self(), {error, connect_failed}}
end.
%%--------------------------------------------------------------------
%% Function: loop(State)
%% State = state record()
%% Descrip.: Wait for signals asking us to perform a MySQL query, or
%% signals that the socket was closed.
%% Returns : error | does not return
%%--------------------------------------------------------------------
loop(State) ->
RecvPid = State#state.recv_pid,
receive
{fetch, Query, GenSrvFrom, Options} ->
%% GenSrvFrom is either a gen_server:call/3 From term(), or a pid if no
%% gen_server was used to make the query
Res = do_query(State, Query, Options),
case is_pid(GenSrvFrom) of
true ->
%% The query was not sent using gen_server mechanisms
GenSrvFrom ! {fetch_result, self(), Res};
false ->
gen_server:reply(GenSrvFrom, Res)
end,
loop(State);
{mysql_recv, RecvPid, data, Packet, Num} ->
mysql:log(State#state.log_fun, error, "mysql_conn: Received MySQL data when not expecting any "
"(num ~p) - ignoring it", [Num]),
mysql:log(State#state.log_fun, error, "mysql_conn: Unexpected MySQL data (num ~p) :~n~p",
[Num, Packet]),
loop(State);
Unknown ->
mysql:log(State#state.log_fun, error, "mysql_conn: Received unknown signal, exiting"),
mysql:log(State#state.log_fun, debug, "mysql_conn: Unknown signal : ~p", [Unknown]),
error
end.
%%--------------------------------------------------------------------
%% Function: mysql_init(Sock, RecvPid, User, Password, LogFun)
%% Sock = term(), gen_tcp socket
%% RecvPid = pid(), mysql_recv process
%% User = string()
%% Password = string()
%% LogFun = undefined | function() with arity 3
%% Descrip.: Try to authenticate on our new socket.
%% Returns : ok | {error, Reason}
%% Reason = string()
%%--------------------------------------------------------------------
mysql_init(Sock, RecvPid, User, Password, LogFun) ->
case do_recv(LogFun, RecvPid, undefined) of
{ok, Packet, InitSeqNum} ->
{Version, Salt1, Salt2, Caps} = greeting(Packet, LogFun),
AuthRes =
case Caps band ?SECURE_CONNECTION of
?SECURE_CONNECTION ->
mysql_auth:do_new_auth(Sock, RecvPid, InitSeqNum + 1, User, Password, Salt1, Salt2, LogFun);
_ ->
mysql_auth:do_old_auth(Sock, RecvPid, InitSeqNum + 1, User, Password, Salt1, LogFun)
end,
case AuthRes of
{ok, <<0:8, _Rest/binary>>, _RecvNum} ->
{ok,Version};
{ok, <<255:8, Code:16/little, Message/binary>>, _RecvNum} ->
mysql:log(LogFun, error, "mysql_conn: init error ~p: ~p~n", [Code, binary_to_list(Message)]),
{error, binary_to_list(Message)};
{ok, RecvPacket, _RecvNum} ->
mysql:log(LogFun, error, "mysql_conn: init unknown error ~p~n", [binary_to_list(RecvPacket)]),
{error, binary_to_list(RecvPacket)};
{error, Reason} ->
mysql:log(LogFun, error, "mysql_conn: init failed receiving data : ~p~n", [Reason]),
{error, Reason}
end;
{error, Reason} ->
{error, Reason}
end.
%% part of mysql_init/4
greeting(Packet, LogFun) ->
<<Protocol:8, Rest/binary>> = Packet,
{Version, Rest2} = asciz(Rest),
<<_TreadID:32/little, Rest3/binary>> = Rest2,
{Salt, Rest4} = asciz(Rest3),
<<Caps:16/little, Rest5/binary>> = Rest4,
<<ServerChar:16/binary-unit:8, Rest6/binary>> = Rest5,
{Salt2, _Rest7} = asciz(Rest6),
mysql:log(LogFun, debug, "mysql_conn: greeting version ~p (protocol ~p) salt ~p caps ~p serverchar ~p salt2 ~p",
[Version, Protocol, Salt, Caps, ServerChar, Salt2]),
{normalize_version(Version, LogFun), Salt, Salt2, Caps}.
%% part of greeting/2
asciz(Data) when binary(Data) ->
mysql:asciz_binary(Data, []);
asciz(Data) when list(Data) ->
{String, [0 | Rest]} = lists:splitwith(fun (C) ->
C /= 0
end, Data),
{String, Rest}.
%%--------------------------------------------------------------------
%% Function: get_query_response(LogFun, RecvPid)
%% LogFun = undefined | function() with arity 3
%% RecvPid = pid(), mysql_recv process
%% Version = integer(), Representing MySQL version used
%% Descrip.: Wait for frames until we have a complete query response.
%% Returns : {data, #mysql_result}
%% {updated, #mysql_result}
%% {error, #mysql_result}
%% FieldInfo = list() of term()
%% Rows = list() of [string()]
%% AffectedRows = int()
%% Reason = term()
%%--------------------------------------------------------------------
get_query_response(LogFun, RecvPid, Version, Options) ->
case do_recv(LogFun, RecvPid, undefined) of
{ok, <<Fieldcount:8, Rest/binary>>, _} ->
case Fieldcount of
0 ->
%% No Tabular data
<<AffectedRows:8, _Rest2/binary>> = Rest,
{updated, #mysql_result{affectedrows=AffectedRows}};
255 ->
<<_Code:16/little, Message/binary>> = Rest,
{error, #mysql_result{error=binary_to_list(Message)}};
_ ->
%% Tabular data received
case get_fields(LogFun, RecvPid, [], Version) of
{ok, Fields} ->
ResultType = get_option(result_type, Options, ?DEFAULT_RESULT_TYPE),
case get_rows(Fieldcount, LogFun, RecvPid, ResultType, []) of
{ok, Rows} ->
{data, #mysql_result{fieldinfo=Fields, rows=Rows}};
{error, Reason} ->
{error, #mysql_result{error=Reason}}
end;
{error, Reason} ->
{error, #mysql_result{error=Reason}}
end
end;
{error, Reason} ->
{error, #mysql_result{error=Reason}}
end.
%%--------------------------------------------------------------------
%% Function: get_fields(LogFun, RecvPid, [], Version)
%% LogFun = undefined | function() with arity 3
%% RecvPid = pid(), mysql_recv process
%% Version = integer(), Representing MySQL version used
%% Descrip.: Received and decode field information.
%% Returns : {ok, FieldInfo} |
%% {error, Reason}
%% FieldInfo = list() of term()
%% Reason = term()
%%--------------------------------------------------------------------
%% Support for MySQL 4.0.x:
get_fields(LogFun, RecvPid, Res, ?MYSQL_4_0) ->
case do_recv(LogFun, RecvPid, undefined) of
{ok, Packet, _Num} ->
case Packet of
<<254:8>> ->
{ok, lists:reverse(Res)};
<<254:8, Rest/binary>> when size(Rest) < 8 ->
{ok, lists:reverse(Res)};
_ ->
{Table, Rest} = get_with_length(Packet),
{Field, Rest2} = get_with_length(Rest),
{LengthB, Rest3} = get_with_length(Rest2),
LengthL = size(LengthB) * 8,
<<Length:LengthL/little>> = LengthB,
{Type, Rest4} = get_with_length(Rest3),
{_Flags, _Rest5} = get_with_length(Rest4),
This = {binary_to_list(Table),
binary_to_list(Field),
Length,
%% TODO: Check on MySQL 4.0 if types are specified
%% using the same 4.1 formalism and could
%% be expanded to atoms:
binary_to_list(Type)},
get_fields(LogFun, RecvPid, [This | Res], ?MYSQL_4_0)
end;
{error, Reason} ->
{error, Reason}
end;
%% Support for MySQL 4.1.x and 5.x:
get_fields(LogFun, RecvPid, Res, ?MYSQL_4_1) ->
case do_recv(LogFun, RecvPid, undefined) of
{ok, Packet, _Num} ->
case Packet of
<<254:8>> ->
{ok, lists:reverse(Res)};
<<254:8, Rest/binary>> when size(Rest) < 8 ->
{ok, lists:reverse(Res)};
_ ->
{_Catalog, Rest} = get_with_length(Packet),
{_Database, Rest2} = get_with_length(Rest),
{Table, Rest3} = get_with_length(Rest2),
%% OrgTable is the real table name if Table is an alias
{_OrgTable, Rest4} = get_with_length(Rest3),
{Field, Rest5} = get_with_length(Rest4),
%% OrgField is the real field name if Field is an alias
{_OrgField, Rest6} = get_with_length(Rest5),
<<_Metadata:8/little, _Charset:16/little,
Length:32/little, Type:8/little,
_Flags:16/little, _Decimals:8/little,
_Rest7/binary>> = Rest6,
This = {binary_to_list(Table),
binary_to_list(Field),
Length,
get_field_datatype(Type)},
get_fields(LogFun, RecvPid, [This | Res], ?MYSQL_4_1)
end;
{error, Reason} ->
{error, Reason}
end.
%%--------------------------------------------------------------------
%% Function: get_rows(N, LogFun, RecvPid, [])
%% N = integer(), number of rows to get
%% LogFun = undefined | function() with arity 3
%% RecvPid = pid(), mysql_recv process
%% Descrip.: Receive and decode a number of rows.
%% Returns : {ok, Rows} |
%% {error, Reason}
%% Rows = list() of [string()]
%%--------------------------------------------------------------------
get_rows(N, LogFun, RecvPid, ResultType, Res) ->
case do_recv(LogFun, RecvPid, undefined) of
{ok, Packet, _Num} ->
case Packet of
<<254:8, Rest/binary>> when size(Rest) < 8 ->
{ok, lists:reverse(Res)};
_ ->
{ok, This} = get_row(N, Packet, ResultType, []),
get_rows(N, LogFun, RecvPid, ResultType, [This | Res])
end;
{error, Reason} ->
{error, Reason}
end.
%% part of get_rows/4
get_row(0, _Data, _ResultType, Res) ->
{ok, lists:reverse(Res)};
get_row(N, Data, ResultType, Res) ->
{Col, Rest} = get_with_length(Data),
This = case Col of
null ->
null;
_ ->
if
ResultType == list ->
binary_to_list(Col);
ResultType == binary ->
Col
end
end,
get_row(N - 1, Rest, ResultType, [This | Res]).
get_with_length(<<251:8, Rest/binary>>) ->
{null, Rest};
get_with_length(<<252:8, Length:16/little, Rest/binary>>) ->
split_binary(Rest, Length);
get_with_length(<<253:8, Length:24/little, Rest/binary>>) ->
split_binary(Rest, Length);
get_with_length(<<254:8, Length:64/little, Rest/binary>>) ->
split_binary(Rest, Length);
get_with_length(<<Length:8, Rest/binary>>) when Length < 251 ->
split_binary(Rest, Length).
%%--------------------------------------------------------------------
%% Function: do_query(State, Query)
%% do_query(Sock, RecvPid, LogFun, Query)
%% Sock = term(), gen_tcp socket
%% RecvPid = pid(), mysql_recv process
%% LogFun = undefined | function() with arity 3
%% Query = string()
%% Descrip.: Send a MySQL query and block awaiting it's response.
%% Returns : result of get_query_response/2 | {error, Reason}
%%--------------------------------------------------------------------
do_query(State, Query, Options) when is_record(State, state) ->
do_query(State#state.socket,
State#state.recv_pid,
State#state.log_fun,
Query,
State#state.mysql_version,
Options
).
do_query(Sock, RecvPid, LogFun, Query, Version, Options) when is_pid(RecvPid),
is_list(Query) ->
Packet = list_to_binary([?MYSQL_QUERY_OP, Query]),
case do_send(Sock, Packet, 0, LogFun) of
ok ->
get_query_response(LogFun, RecvPid, Version, Options);
{error, Reason} ->
Msg = io_lib:format("Failed sending data on socket : ~p", [Reason]),
{error, Msg}
end.
%%--------------------------------------------------------------------
%% Function: do_send(Sock, Packet, SeqNum, LogFun)
%% Sock = term(), gen_tcp socket
%% Packet = binary()
%% SeqNum = integer(), packet sequence number
%% LogFun = undefined | function() with arity 3
%% Descrip.: Send a packet to the MySQL server.
%% Returns : result of gen_tcp:send/2
%%--------------------------------------------------------------------
do_send(Sock, Packet, SeqNum, _LogFun) when is_binary(Packet), is_integer(SeqNum) ->
Data = <<(size(Packet)):24/little, SeqNum:8, Packet/binary>>,
%%mysql:log(LogFun, debug, "mysql_conn: send packet ~p: ~p", [SeqNum, Data]),
gen_tcp:send(Sock, Data).
%%--------------------------------------------------------------------
%% Function: normalize_version(Version, LogFun)
%% Version = string()
%% LogFun = undefined | function() with arity 3
%% Descrip.: Return a flag corresponding to the MySQL version used.
%% The protocol used depends on this flag.
%% Returns : Version = string()
%%--------------------------------------------------------------------
normalize_version([$4,$.,$0|_T], LogFun) ->
mysql:log(LogFun, debug, "Switching to MySQL 4.0.x protocol.~n"),
?MYSQL_4_0;
normalize_version([$4,$.,$1|_T], _LogFun) ->
?MYSQL_4_1;
normalize_version([$5|_T], _LogFun) ->
%% MySQL version 5.x protocol is compliant with MySQL 4.1.x:
?MYSQL_4_1;
normalize_version(_Other, LogFun) ->
mysql:log(LogFun, error, "MySQL version not supported: MySQL Erlang module might not work correctly.~n"),
%% Error, but trying the oldest protocol anyway:
?MYSQL_4_0.
%%--------------------------------------------------------------------
%% Function: get_field_datatype(DataType)
%% DataType = integer(), MySQL datatype
%% Descrip.: Return MySQL field datatype as description string
%% Returns : String, MySQL datatype
%%--------------------------------------------------------------------
get_field_datatype(0) -> 'DECIMAL';
get_field_datatype(1) -> 'TINY';
get_field_datatype(2) -> 'SHORT';
get_field_datatype(3) -> 'LONG';
get_field_datatype(4) -> 'FLOAT';
get_field_datatype(5) -> 'DOUBLE';
get_field_datatype(6) -> 'NULL';
get_field_datatype(7) -> 'TIMESTAMP';
get_field_datatype(8) -> 'LONGLONG';
get_field_datatype(9) -> 'INT24';
get_field_datatype(10) -> 'DATE';
get_field_datatype(11) -> 'TIME';
get_field_datatype(12) -> 'DATETIME';
get_field_datatype(13) -> 'YEAR';
get_field_datatype(14) -> 'NEWDATE';
get_field_datatype(16) -> 'BIT';
get_field_datatype(246) -> 'DECIMAL';
get_field_datatype(247) -> 'ENUM';
get_field_datatype(248) -> 'SET';
get_field_datatype(249) -> 'TINYBLOB';
get_field_datatype(250) -> 'MEDIUM_BLOG';
get_field_datatype(251) -> 'LONG_BLOG';
get_field_datatype(252) -> 'BLOB';
get_field_datatype(253) -> 'VAR_STRING';
get_field_datatype(254) -> 'STRING';
get_field_datatype(255) -> 'GEOMETRY'.
%%--------------------------------------------------------------------
%% Function: get_option(Key1, Options, Default) -> Value1
%% Options = [Option]
%% Option = {Key2, Value2}
%% Key1 = Key2 = atom()
%% Value1 = Value2 = Default = term()
%% Descrip.: Return the option associated with Key passed to squery/4
%%--------------------------------------------------------------------
get_option(Key, Options, Default) ->
case lists:keysearch(Key, 1, Options) of
{value, {_, Value}} ->
Value;
false ->
Default
end.

View File

@ -0,0 +1,161 @@
%%%-------------------------------------------------------------------
%%% File : mysql_recv.erl
%%% Author : Fredrik Thulin <ft@it.su.se>
%%% Descrip.: Handles data being received on a MySQL socket. Decodes
%%% per-row framing and sends each row to parent.
%%%
%%% Created : 4 Aug 2005 by Fredrik Thulin <ft@it.su.se>
%%%
%%% Note : All MySQL code was written by Magnus Ahltorp, originally
%%% in the file mysql.erl - I just moved it here.
%%%
%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan
%%% See the file COPYING
%%%
%%% Signals this receiver process can send to it's parent
%%% (the parent is a mysql_conn connection handler) :
%%%
%%% {mysql_recv, self(), data, Packet, Num}
%%% {mysql_recv, self(), closed, {error, Reason}}
%%% {mysql_recv, self(), closed, normal}
%%%
%%% Internally (from inside init/4 to start_link/4) the
%%% following signals may be sent to the parent process :
%%%
%%% {mysql_recv, self(), init, {ok, Sock}}
%%% {mysql_recv, self(), init, {error, E}}
%%%
%%%-------------------------------------------------------------------
-module(mysql_recv).
%%--------------------------------------------------------------------
%% External exports (should only be used by the 'mysql_conn' module)
%%--------------------------------------------------------------------
-export([start_link/4
]).
-record(state, {
socket,
parent,
log_fun,
data
}).
-define(SECURE_CONNECTION, 32768).
-define(CONNECT_TIMEOUT, 5000).
%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link(Host, Port, LogFun, Parent)
%% Host = string()
%% Port = integer()
%% LogFun = undefined | function() of arity 3
%% Parent = pid(), process that should get received frames
%% Descrip.: Start a process that connects to Host:Port and waits for
%% data. When it has received a MySQL frame, it sends it to
%% Parent and waits for the next frame.
%% Returns : {ok, RecvPid, Socket} |
%% {error, Reason}
%% RecvPid = pid(), receiver process pid
%% Socket = term(), gen_tcp socket
%% Reason = atom() | string()
%%--------------------------------------------------------------------
start_link(Host, Port, LogFun, Parent) when is_list(Host), is_integer(Port) ->
RecvPid =
spawn_link(fun () ->
init(Host, Port, LogFun, Parent)
end),
%% wait for the socket from the spawned pid
receive
{mysql_recv, RecvPid, init, {error, E}} ->
{error, E};
{mysql_recv, RecvPid, init, {ok, Socket}} ->
{ok, RecvPid, Socket}
after ?CONNECT_TIMEOUT ->
catch exit(RecvPid, kill),
{error, "timeout"}
end.
%%====================================================================
%% Internal functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init((Host, Port, LogFun, Parent)
%% Host = string()
%% Port = integer()
%% LogFun = undefined | function() of arity 3
%% Parent = pid(), process that should get received frames
%% Descrip.: Connect to Host:Port and then enter receive-loop.
%% Returns : error | never returns
%%--------------------------------------------------------------------
init(Host, Port, LogFun, Parent) ->
case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of
{ok, Sock} ->
Parent ! {mysql_recv, self(), init, {ok, Sock}},
State = #state{socket = Sock,
parent = Parent,
log_fun = LogFun,
data = <<>>
},
loop(State);
E ->
mysql:log(LogFun, error, "mysql_recv: Failed connecting to ~p:~p : ~p",
[Host, Port, E]),
Msg = lists:flatten(io_lib:format("connect failed : ~p", [E])),
Parent ! {mysql_recv, self(), init, {error, Msg}}
end.
%%--------------------------------------------------------------------
%% Function: loop(State)
%% State = state record()
%% Descrip.: The main loop. Wait for data from our TCP socket and act
%% on received data or signals that our socket was closed.
%% Returns : error | never returns
%%--------------------------------------------------------------------
loop(State) ->
Sock = State#state.socket,
receive
{tcp, Sock, InData} ->
NewData = list_to_binary([State#state.data, InData]),
%% send data to parent if we have enough data
Rest = sendpacket(State#state.parent, NewData),
loop(State#state{data = Rest});
{tcp_error, Sock, Reason} ->
mysql:log(State#state.log_fun, error, "mysql_recv: Socket ~p closed : ~p", [Sock, Reason]),
State#state.parent ! {mysql_recv, self(), closed, {error, Reason}},
error;
{tcp_closed, Sock} ->
mysql:log(State#state.log_fun, debug, "mysql_recv: Socket ~p closed", [Sock]),
State#state.parent ! {mysql_recv, self(), closed, normal},
error
end.
%%--------------------------------------------------------------------
%% Function: sendpacket(Parent, Data)
%% Parent = pid()
%% Data = binary()
%% Descrip.: Check if we have received one or more complete frames by
%% now, and if so - send them to Parent.
%% Returns : Rest = binary()
%%--------------------------------------------------------------------
%% send data to parent if we have enough data
sendpacket(Parent, Data) ->
case Data of
<<Length:24/little, Num:8, D/binary>> ->
if
Length =< size(D) ->
{Packet, Rest} = split_binary(D, Length),
Parent ! {mysql_recv, self(), data, Packet, Num},
sendpacket(Parent, Rest);
true ->
Data
end;
_ ->
Data
end.

View File

@ -0,0 +1,22 @@
-module(mysql_sup).
-export([start_link/0]).
-behaviour(supervisor).
-export([init/1]).
%% @spec start_link() -> Result
%% Result = {ok,Pid} | ignore | {error,Error}
%% Pid = pid()
%% Error = {already_started,Pid} | shutdown | term()
start_link() ->
supervisor:start_link(mysql_sup, []).
init([]) ->
MysqlConfig = [pool, "localhost", "flukso", "your_mysql_password_here", "flukso"],
Mysql = {mysql,
{mysql, start_link, MysqlConfig},
permanent, 3000, worker, [mysql]},
Processes = [Mysql],
{ok, {{one_for_one, 5, 10}, Processes}}.

View File

@ -0,0 +1,39 @@
## -*- makefile -*-
######################################################################
## Erlang
ERL := erl
ERLC := $(ERL)c
INCLUDE_DIRS := ../include $(wildcard ../deps/*/include)
EBIN_DIRS := $(wildcard ../deps/*/ebin)
ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %)
ifndef no_debug_info
ERLC_FLAGS += +debug_info
endif
ifdef debug
ERLC_FLAGS += -Ddebug
endif
EBIN_DIR := ../ebin
EMULATOR := beam
ERL_SOURCES := $(wildcard *.erl)
ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../include/*.hrl)
ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.$(EMULATOR))
ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR))
APP_FILES := $(wildcard *.app)
EBIN_FILES = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app)
MODULES = $(ERL_SOURCES:%.erl=%)
../ebin/%.app: %.app
cp $< $@
$(EBIN_DIR)/%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $<
./%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o . $<

View File

@ -11,4 +11,4 @@
{registered, []},
{mod, {flukso_app, []}},
{env, []},
{applications, [kernel, stdlib, crypto]}]}.
{applications, [kernel, stdlib, crypto, erlrrd, mysql, webmachine]}]}.

View File

@ -19,8 +19,9 @@ ensure_started(App) ->
%% @doc Starts the app for inclusion in a supervisor tree
start_link() ->
flukso_deps:ensure(),
ensure_started(erlrrd),
ensure_started(crypto),
ensure_started(erlrrd),
ensure_started(mysql),
ensure_started(webmachine),
flukso_sup:start_link().
@ -28,17 +29,18 @@ start_link() ->
%% @doc Start the flukso server.
start() ->
flukso_deps:ensure(),
ensure_started(erlrrd),
ensure_started(crypto),
ensure_started(erlrrd),
ensure_started(mysql),
ensure_started(webmachine),
application:start(flukso).
application:start(flukso).
%% @spec stop() -> ok
%% @doc Stop the flukso server.
stop() ->
Res = application:stop(flukso),
application:stop(erlrrd),
application:stop(webmachine),
application:stop(mysql),
application:stop(erlrrd),
application:stop(crypto),
Res.