From 3de06ee609e78464bf09abeb92413a5c1d4a5d78 Mon Sep 17 00:00:00 2001 From: Bart Van Der Meerssche Date: Sat, 13 Mar 2010 13:00:51 +0000 Subject: [PATCH] api: initial import of mysql driver in webmachine --- web/api/flukso/Makefile | 4 +- web/api/flukso/deps/erlrrd/ebin/erlrrd.app | 12 - web/api/flukso/deps/erlrrd/ebin/erlrrd.beam | Bin 41096 -> 0 bytes .../flukso/deps/erlrrd/ebin/erlrrd_app.beam | Bin 1204 -> 0 bytes .../flukso/deps/erlrrd/ebin/erlrrd_sup.beam | Bin 1448 -> 0 bytes web/api/flukso/deps/mysql/Makefile | 11 + web/api/flukso/deps/mysql/doc/edoc-info | 3 + web/api/flukso/deps/mysql/doc/erlang.png | Bin 0 -> 2109 bytes web/api/flukso/deps/mysql/doc/index.html | 17 + .../flukso/deps/mysql/doc/modules-frame.html | 15 + web/api/flukso/deps/mysql/doc/mysql.html | 145 ++++ web/api/flukso/deps/mysql/doc/mysql_auth.html | 36 + web/api/flukso/deps/mysql/doc/mysql_conn.html | 60 ++ web/api/flukso/deps/mysql/doc/mysql_recv.html | 30 + .../deps/mysql/doc/overview-summary.html | 15 + .../flukso/deps/mysql/doc/packages-frame.html | 11 + web/api/flukso/deps/mysql/doc/stylesheet.css | 55 ++ web/api/flukso/deps/mysql/src/Makefile | 20 + web/api/flukso/deps/mysql/src/mysql.app | 12 + web/api/flukso/deps/mysql/src/mysql.erl | 656 ++++++++++++++++++ web/api/flukso/deps/mysql/src/mysql.hrl | 6 + web/api/flukso/deps/mysql/src/mysql_app.erl | 18 + web/api/flukso/deps/mysql/src/mysql_auth.erl | 194 ++++++ web/api/flukso/deps/mysql/src/mysql_conn.erl | 656 ++++++++++++++++++ web/api/flukso/deps/mysql/src/mysql_recv.erl | 161 +++++ web/api/flukso/deps/mysql/src/mysql_sup.erl | 22 + web/api/flukso/deps/mysql/support/include.mk | 39 ++ web/api/flukso/src/flukso.app | 2 +- web/api/flukso/src/flukso.erl | 12 +- 29 files changed, 2193 insertions(+), 19 deletions(-) delete mode 100644 web/api/flukso/deps/erlrrd/ebin/erlrrd.app delete mode 100644 web/api/flukso/deps/erlrrd/ebin/erlrrd.beam delete mode 100644 web/api/flukso/deps/erlrrd/ebin/erlrrd_app.beam delete mode 100644 web/api/flukso/deps/erlrrd/ebin/erlrrd_sup.beam create mode 100644 web/api/flukso/deps/mysql/Makefile create mode 100644 web/api/flukso/deps/mysql/doc/edoc-info create mode 100644 web/api/flukso/deps/mysql/doc/erlang.png create mode 100644 web/api/flukso/deps/mysql/doc/index.html create mode 100644 web/api/flukso/deps/mysql/doc/modules-frame.html create mode 100644 web/api/flukso/deps/mysql/doc/mysql.html create mode 100644 web/api/flukso/deps/mysql/doc/mysql_auth.html create mode 100644 web/api/flukso/deps/mysql/doc/mysql_conn.html create mode 100644 web/api/flukso/deps/mysql/doc/mysql_recv.html create mode 100644 web/api/flukso/deps/mysql/doc/overview-summary.html create mode 100644 web/api/flukso/deps/mysql/doc/packages-frame.html create mode 100644 web/api/flukso/deps/mysql/doc/stylesheet.css create mode 100644 web/api/flukso/deps/mysql/src/Makefile create mode 100644 web/api/flukso/deps/mysql/src/mysql.app create mode 100644 web/api/flukso/deps/mysql/src/mysql.erl create mode 100644 web/api/flukso/deps/mysql/src/mysql.hrl create mode 100644 web/api/flukso/deps/mysql/src/mysql_app.erl create mode 100644 web/api/flukso/deps/mysql/src/mysql_auth.erl create mode 100644 web/api/flukso/deps/mysql/src/mysql_conn.erl create mode 100644 web/api/flukso/deps/mysql/src/mysql_recv.erl create mode 100644 web/api/flukso/deps/mysql/src/mysql_sup.erl create mode 100644 web/api/flukso/deps/mysql/support/include.mk diff --git a/web/api/flukso/Makefile b/web/api/flukso/Makefile index 361dbd2..3e6720e 100644 --- a/web/api/flukso/Makefile +++ b/web/api/flukso/Makefile @@ -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)) diff --git a/web/api/flukso/deps/erlrrd/ebin/erlrrd.app b/web/api/flukso/deps/erlrrd/ebin/erlrrd.app deleted file mode 100644 index af306e2..0000000 --- a/web/api/flukso/deps/erlrrd/ebin/erlrrd.app +++ /dev/null @@ -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]}]}. diff --git a/web/api/flukso/deps/erlrrd/ebin/erlrrd.beam b/web/api/flukso/deps/erlrrd/ebin/erlrrd.beam deleted file mode 100644 index f0d148dca38fd297642f79642bedae4c3ddce2be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41096 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�ql=n!aCu0*dv+vKCv2{oB&Q< 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 GfdK$|MR&mf diff --git a/web/api/flukso/deps/erlrrd/ebin/erlrrd_sup.beam b/web/api/flukso/deps/erlrrd/ebin/erlrrd_sup.beam deleted file mode 100644 index 4cb24f690859012d6247b3becd2f9da7b618b2b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1448 zcmZ?s4>Dw6U|rzk>gelOlAp`Kz>ovPTwJL|IYmV&@x`SDT*W1cMJ4e$nR(eLb;SOADYzz>MIftZz1fJsP6fKh>mLBPRC*nwHuL5P{% zfWd)D7|7sI5MW?504iW$bbyHXDF`qIFbNe2FeW&-GcXG?7BC4lF)%g&75fP&FirsS z1Q-`MFoSgaDHt#_1eX+rfGqUPEdbFbK+FQgtU#I>h}nRc35q#^G#3yv0x^uui`r3>EQrkkUU5ohz$ZDnjMHi;xO}oOt4)bvq9oaKA9yTH)H^DWyKuZGl5(U3IZ$_ zE^27-YaWwYwrne>>;mu7JtB+N&+Ip6c3`l`Q|B~dSC4fMu#DWBa?R8?e_2Fl0dwL8 znVBnRt`}OiSwhchoo2cyOD!q}dNP}V)kM!s&lqG2OG##KY6e>p zFag|5RxF=AW*c5QcK&9|&(^Ps6aW2fdd9((JXfnv&dFuxJ(uo1 zC%!H_ulI4mO$n3F^KNo4xcT2NUxKd>iN&7_vp;r?2`{lpZsZ? z)M?ktlFxoHI8i#cs$0H2OvgTJ^5d(fVlk25x+QNM(%BVn_cm-b+r8dp`^tl#*S%u@ z&OJw1qO)w`S3LpVt%2*DOROKN%708h+#0fA#wN!5%b69g*YEjcEM=2boOCSseQovo bzw>LqC0W!7=Ec72d#PL_TF;pKGVUP&4pZ$b diff --git a/web/api/flukso/deps/mysql/Makefile b/web/api/flukso/deps/mysql/Makefile new file mode 100644 index 0000000..2f6cace --- /dev/null +++ b/web/api/flukso/deps/mysql/Makefile @@ -0,0 +1,11 @@ +all: + (cd src;$(MAKE) all) + +edoc: + (cd src;$(MAKE) edoc) + +test: + (cd src;$(MAKE) test) + +clean: + (cd src;$(MAKE) clean) diff --git a/web/api/flukso/deps/mysql/doc/edoc-info b/web/api/flukso/deps/mysql/doc/edoc-info new file mode 100644 index 0000000..db35f7c --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/edoc-info @@ -0,0 +1,3 @@ +{application,mysql}. +{packages,[]}. +{modules,[mysql,mysql_auth,mysql_conn,mysql_recv]}. diff --git a/web/api/flukso/deps/mysql/doc/erlang.png b/web/api/flukso/deps/mysql/doc/erlang.png new file mode 100644 index 0000000000000000000000000000000000000000..987a618e2403af895bfaf8c2f929e3a4f3746659 GIT binary patch literal 2109 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 literal 0 HcmV?d00001 diff --git a/web/api/flukso/deps/mysql/doc/index.html b/web/api/flukso/deps/mysql/doc/index.html new file mode 100644 index 0000000..0626291 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/index.html @@ -0,0 +1,17 @@ + + + +The mysql application + + + + + + +<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> + + + \ No newline at end of file diff --git a/web/api/flukso/deps/mysql/doc/modules-frame.html b/web/api/flukso/deps/mysql/doc/modules-frame.html new file mode 100644 index 0000000..9737a4f --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/modules-frame.html @@ -0,0 +1,15 @@ + + + +The mysql application + + + +

Modules

+ + + + +
mysql
mysql_auth
mysql_conn
mysql_recv
+ + \ No newline at end of file diff --git a/web/api/flukso/deps/mysql/doc/mysql.html b/web/api/flukso/deps/mysql/doc/mysql.html new file mode 100644 index 0000000..5c4e284 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql.html @@ -0,0 +1,145 @@ + + + +Module mysql + + + + +
+ +

Module mysql

+ + +

Behaviours: gen_server.

+ +

Function Index

+ + + + + + + + + + + + + + + + + + + + +
asciz_binary/2
code_change/3
connect/7
fetch/2
fetch/3
get_result_affected_rows/1
get_result_field_info/1
get_result_reason/1
get_result_rows/1
handle_call/3
handle_cast/2
handle_info/2
init/1
log/3
log/4
quote/1
start_link/5
start_link/6
start_link/7
terminate/2
+ +

Function Details

+ +

asciz_binary/2

+
+

asciz_binary() -> term()

+
+ +

code_change/3

+
+

code_change() -> term()

+
+ +

connect/7

+
+

connect() -> term()

+
+ +

fetch/2

+
+

fetch() -> term()

+
+ +

fetch/3

+
+

fetch() -> term()

+
+ +

get_result_affected_rows/1

+
+

get_result_affected_rows() -> term()

+
+ +

get_result_field_info/1

+
+

get_result_field_info() -> term()

+
+ +

get_result_reason/1

+
+

get_result_reason() -> term()

+
+ +

get_result_rows/1

+
+

get_result_rows() -> term()

+
+ +

handle_call/3

+
+

handle_call() -> term()

+
+ +

handle_cast/2

+
+

handle_cast() -> term()

+
+ +

handle_info/2

+
+

handle_info() -> term()

+
+ +

init/1

+
+

init() -> term()

+
+ +

log/3

+
+

log() -> term()

+
+ +

log/4

+
+

log() -> term()

+
+ +

quote/1

+
+

quote() -> term()

+
+ +

start_link/5

+
+

start_link() -> term()

+
+ +

start_link/6

+
+

start_link() -> term()

+
+ +

start_link/7

+
+

start_link() -> term()

+
+ +

terminate/2

+
+

terminate() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/mysql_auth.html b/web/api/flukso/deps/mysql/doc/mysql_auth.html new file mode 100644 index 0000000..403930d --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql_auth.html @@ -0,0 +1,36 @@ + + + +Module mysql_auth + + + + +
+ +

Module mysql_auth

+ + + +

Function Index

+ + +
do_new_auth/8
do_old_auth/7
+ +

Function Details

+ +

do_new_auth/8

+
+

do_new_auth() -> term()

+
+ +

do_old_auth/7

+
+

do_old_auth() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/mysql_conn.html b/web/api/flukso/deps/mysql/doc/mysql_conn.html new file mode 100644 index 0000000..353089e --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql_conn.html @@ -0,0 +1,60 @@ + + + +Module mysql_conn + + + + +
+ +

Module mysql_conn

+ + + +

Function Index

+ + + + + + +
do_recv/3
fetch/3
fetch/4
squery/4
start/6
start_link/6
+ +

Function Details

+ +

do_recv/3

+
+

do_recv() -> term()

+
+ +

fetch/3

+
+

fetch() -> term()

+
+ +

fetch/4

+
+

fetch() -> term()

+
+ +

squery/4

+
+

squery() -> term()

+
+ +

start/6

+
+

start() -> term()

+
+ +

start_link/6

+
+

start_link() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/mysql_recv.html b/web/api/flukso/deps/mysql/doc/mysql_recv.html new file mode 100644 index 0000000..e0d7943 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql_recv.html @@ -0,0 +1,30 @@ + + + +Module mysql_recv + + + + +
+ +

Module mysql_recv

+ + + +

Function Index

+ +
start_link/4
+ +

Function Details

+ +

start_link/4

+
+

start_link() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/overview-summary.html b/web/api/flukso/deps/mysql/doc/overview-summary.html new file mode 100644 index 0000000..b2a1853 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/overview-summary.html @@ -0,0 +1,15 @@ + + + +The mysql application + + + + +

The mysql application

+ +
+ +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/packages-frame.html b/web/api/flukso/deps/mysql/doc/packages-frame.html new file mode 100644 index 0000000..3c63b4a --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/packages-frame.html @@ -0,0 +1,11 @@ + + + +The mysql application + + + +

Packages

+
+ + \ No newline at end of file diff --git a/web/api/flukso/deps/mysql/doc/stylesheet.css b/web/api/flukso/deps/mysql/doc/stylesheet.css new file mode 100644 index 0000000..e426a90 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/stylesheet.css @@ -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 +} diff --git a/web/api/flukso/deps/mysql/src/Makefile b/web/api/flukso/deps/mysql/src/Makefile new file mode 100644 index 0000000..af7b8d9 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/Makefile @@ -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 diff --git a/web/api/flukso/deps/mysql/src/mysql.app b/web/api/flukso/deps/mysql/src/mysql.app new file mode 100644 index 0000000..60c65d3 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql.app @@ -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]}]}. diff --git a/web/api/flukso/deps/mysql/src/mysql.erl b/web/api/flukso/deps/mysql/src/mysql.erl new file mode 100644 index 0000000..7dde778 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql.erl @@ -0,0 +1,656 @@ +%%%------------------------------------------------------------------- +%%% File : mysql.erl +%%% Author : Magnus Ahltorp +%%% Descrip.: MySQL client. +%%% +%%% Created : 4 Aug 2005 by Magnus Ahltorp +%%% +%%% 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(<>, 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. diff --git a/web/api/flukso/deps/mysql/src/mysql.hrl b/web/api/flukso/deps/mysql/src/mysql.hrl new file mode 100644 index 0000000..aee5611 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql.hrl @@ -0,0 +1,6 @@ +%% MySQL result record: +-record(mysql_result, + {fieldinfo=[], + rows=[], + affectedrows=0, + error=""}). diff --git a/web/api/flukso/deps/mysql/src/mysql_app.erl b/web/api/flukso/deps/mysql/src/mysql_app.erl new file mode 100644 index 0000000..46e629e --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_app.erl @@ -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. diff --git a/web/api/flukso/deps/mysql/src/mysql_auth.erl b/web/api/flukso/deps/mysql/src/mysql_auth.erl new file mode 100644 index 0000000..bfa226d --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_auth.erl @@ -0,0 +1,194 @@ +%%%------------------------------------------------------------------- +%%% File : mysql_auth.erl +%%% Author : Fredrik Thulin +%%% Descrip.: MySQL client authentication functions. +%%% Created : 4 Aug 2005 by Fredrik Thulin +%%% +%%% 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, <>, 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, + <>. + +%% 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, + <>. + +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). diff --git a/web/api/flukso/deps/mysql/src/mysql_conn.erl b/web/api/flukso/deps/mysql/src/mysql_conn.erl new file mode 100644 index 0000000..414890f --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_conn.erl @@ -0,0 +1,656 @@ +%%%------------------------------------------------------------------- +%%% File : mysql_conn.erl +%%% Author : Fredrik Thulin +%%% Descrip.: MySQL connection handler, handles de-framing of messages +%%% received by the MySQL receiver process. +%%% Created : 5 Aug 2005 by Fredrik Thulin +%%% Modified: 11 Jan 2006 by Mickael Remond +%%% +%%% 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) -> + <> = Packet, + {Version, Rest2} = asciz(Rest), + <<_TreadID:32/little, Rest3/binary>> = Rest2, + {Salt, Rest4} = asciz(Rest3), + <> = Rest4, + <> = 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, <>, _} -> + case Fieldcount of + 0 -> + %% No Tabular data + <> = 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, + <> = 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(<>) 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. diff --git a/web/api/flukso/deps/mysql/src/mysql_recv.erl b/web/api/flukso/deps/mysql/src/mysql_recv.erl new file mode 100644 index 0000000..d770d9b --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_recv.erl @@ -0,0 +1,161 @@ +%%%------------------------------------------------------------------- +%%% File : mysql_recv.erl +%%% Author : Fredrik Thulin +%%% 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 +%%% +%%% 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 + <> -> + 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. diff --git a/web/api/flukso/deps/mysql/src/mysql_sup.erl b/web/api/flukso/deps/mysql/src/mysql_sup.erl new file mode 100644 index 0000000..06c35a9 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_sup.erl @@ -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}}. diff --git a/web/api/flukso/deps/mysql/support/include.mk b/web/api/flukso/deps/mysql/support/include.mk new file mode 100644 index 0000000..065e409 --- /dev/null +++ b/web/api/flukso/deps/mysql/support/include.mk @@ -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 . $< diff --git a/web/api/flukso/src/flukso.app b/web/api/flukso/src/flukso.app index b139907..f88de78 100644 --- a/web/api/flukso/src/flukso.app +++ b/web/api/flukso/src/flukso.app @@ -11,4 +11,4 @@ {registered, []}, {mod, {flukso_app, []}}, {env, []}, - {applications, [kernel, stdlib, crypto]}]}. + {applications, [kernel, stdlib, crypto, erlrrd, mysql, webmachine]}]}. diff --git a/web/api/flukso/src/flukso.erl b/web/api/flukso/src/flukso.erl index 698849e..701351b 100644 --- a/web/api/flukso/src/flukso.erl +++ b/web/api/flukso/src/flukso.erl @@ -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. -