Git Product home page Git Product logo

emysql's Introduction

Emysql

Build Status

To use this driver, see the Samples further down in this README document.


Emysql implements a stable driver toward the MySQL database. It currently support fairly recent versions (somewhere around 5.3+) and it is considered fairly stable in production.

The driver has several technical shortcomings:

  • No clear protocol / connection pool separation
  • No clear protocol / socket separation
  • A very complicated connection pool management
  • Uses the textual protocol in a lot of places where it shouldthe binary protocol
  • The API could be better

However, this is probably the best MySQL driver out there for Erlang. The erlang-mysql-driver uses a problematic connection pool design for many use cases and is not suitable for general purpose use. This driver is.

Versioning

Semantic versioning is used. Versions are given as x.y.z:

  • x changes break backward compatibility
  • y changes add new functionality—but does not break compatibility backwards
  • z changes recognizes a new version, but is not supposed to break anything

Changelog of recent changes

Emysql 0.4.1

Spring cleaning in the repository:

  • Removed a lot of modules which is not part of the official API from the documentation.
  • Deprecated the use of emysql_util for emysql.
  • Made tests actually do something with the emysql_util calls.
  • Moved function calls from emysql_util into the modules where they belong.

Change should be backwards compatible.

Emysql 0.4.0

Introduced changes by Garrett Smith:

  • Support connection timeouts in gen_tcp:connect/3. This allows the driver to better handle connection timeouts upon initial connections.
  • Introduce emysql:add_pool/2 as a proplist-based way of initializing a pool. This is going to be the preferred way of initializing pools in the future.

Emysql 0.3.2

  • We should be on Travis CI now with full tests running
  • Pulled in lots of smaller changes which should be rather cosmetic fixes.
  • R16 warnings eliminated (Jesse Gumm)
  • Tried to consolidate simple parts of the driver

Emysql 0.3.0

Note: Automatic conversion to the encoding of the underlying MySQL server was removed in the 0.3.x branch. If you specify, e.g., utf-8 encoding, then the MySQL server will reject wrongly-encoded strings and data, but the driver will not perform any encoding by itself anymore.

It is now the driver callers responsibility to ensure that data is properly encoded. This change makes it possible to pass binary BLOB data to the MySQL server once again.

Overview:

This is an Erlang MySQL driver, based on a rewrite at Electronic Arts. Easy to use, strong connection pooling, prepared statements & stored procedures. Optimized for a central node architecture and OLTP.

While you can use mysql via ODBC, you should see better performance when using a driver like Emysql. For samples and docs see below. Read the brief on choosing a package and about the history of the various MySQL drivers.

Emysql is a cleaner rewrite of erlang-mysql-driver, see History. This fork is a direct continuation of the original emysql with fixes, updates, more documentation and samples.

This is the master branch. Should you run into problems, please report them by opening an issue at github and try if they go away by checking out the 'stable' branch. Thank you.


Which fork/package should I use? Likely this one, but see Choosing. Why are there so many? See History. Who used this fork? Electronic Arts. How do I ...? See Samples. Hello ...? See Samples.

Download: https://github.com/Eonblast/Emysql/archives/master Docs: http://eonblast.github.com/Emysql/ Issues: https://github.com/Eonblast/Emysql/issues Repository: https://github.com/Eonblast/Emysql


Contents


Choosing

Best

In most cases, especially for high performance and stability, this package, Emysql, will be the best choice. It was rewritten from the ground up to overcome fundamental issues of 'erlang-mysql-driver'. It also has some usable docs meanwhile.

Simple

If you are looking for the plain necessities, you should use the ejabberd mysql driver. It is simple, battle tested and stable. There are comprehensive instructions in the source comments.

Transaction

This driver currently does not support transactions.

For mnesia-style transactions, one of the multiple 'erlang-mysql-drivers' may suite you best. There are quite many branches of it out there, and they are based on the same project as the ejabberd driver. To learn more about out the differences between the drivers, see the mysql driver history.

Samples

Hello World

This is a hello world program. Follow the three steps below to try it out.

-module(a_hello).
-export([run/0]).

run() ->

        crypto:start(),
        application:start(emysql),

        emysql:add_pool(hello_pool, [{size,1},
                     {user,"hello_username"},
                     {password,"hello_password"},
                     {database,"hello_database"},
                     {encoding,utf8}]),

        emysql:execute(hello_pool,
                <<"INSERT INTO hello_table SET hello_text = 'Hello World!'">>),

        Result = emysql:execute(hello_pool,
                <<"select hello_text from hello_table">>),

        io:format("~n~p~n", [Result]).

We'll be coming back to this source to make it run on your machine in a minute. But let's look at the basic building blocks first:

Executing an SQL Statement

emysql:execute(my_pool, <<"SELECT * from mytable">>).

For the exact spec, see below, Usage. Regarding the 'pool', also see below.

Executing a Prepared Statement

emysql:prepare(my_stmt, <<"SELECT * from mytable WHERE id = ?">>).

emysql:execute(my_pool, my_stmt, [1]).

Executing Stored Procedures

emysql:execute(my_pool, <<"create procedure my_sp() begin select * from mytable; end">>).

emysql:execute(my_pool, <<"call my_sp();">>).

Result Record

-record(result_packet, {seq_num, field_list, rows, extra}).

Converting Row Data To Records

-record(foo, {bar, baz}).

Result = emysql:execute(pool1, <<"select bar, baz from foo">>).
Recs = emysql:as_record(Result, foo, record_info(fields, foo)).
Bars = [Foo#foo.bar || Foo <- Recs].

Adding a Connection to the Connection Pool

Emysql uses a sophisticated connection pooling mechanism.

emysql:add_pool(my_pool, [{size,1}, {user,"myuser"}, {password,"mypass"},
        {host,"myhost"}, {port,3306},{database,"mydatabase"}, {encoding,utf8}]).

Arbitrary post-connection start-up commands can be added with the proplist key atom start_cmds:

emysql:add_pool(my_pool, [{size,1}, {user,"myuser"}, {password,"mypass"},
        {host,"myhost"}, {port,3306},{database,"mydatabase"}, {encoding,utf8},
        {start_cmds,[
                <<"SET TIME_ZONE='+00:00'">>,
                <<"SET SQL_MODE='STRICT_ALL_TABLES'">>
        ]}]).

Running Hello World

Let's run the hello world sample from above:

1. Build Emysql

Build emysql.app, using make:

$ cd Emysql
$ make

Or use rebar:

$ cd Emysql
$ ./rebar compile

Both yield an option to install but this is not needed for the samples.

2. Make a Sample Database

For use in the above sample (and all of those below, too), create a local mysql database. You should have a mysql server installed and running:

$ mysql [-u<user> -p]
mysql> create database hello_database;
mysql> use hello_database;
mysql> create table hello_table (hello_text char(20));
mysql> grant all privileges on hello_database.* to hello_username@localhost identified by 'hello_password';

3. Paste & Run Hello

Be sure to have ./ebin in your Erlang path. The hello-world source as shown above already waits in the Emysql directory, as hello.erl. Just compile and run it:

$ erlc hello.erl
$ erl -pa ./ebin -s hello run -s init stop -noshell

That's it. If you need to blindly repeat that more often some time, you can also use

$ make hello

There are more sample programs:

More Samples

Sample programs are in ./samples.

To run the samples, create the database as listed above at localhost, and simply run the compile & run batches:

$ cd samples
$ ./a_hello
$ ./b_raw
$ ./c_rows_as_records
$ ./d_prepared_statement
$ ./e_stored_procedure
$ ./f_load_from_file
$ ./g_rows_as_json

or (after building emysql.app and the database, as explained above), start a_hello etc. manually along these lines:

$ make
$ cd samples
$ erlc a_hello.erl
$ erl -pa ../ebin -s a_hello run -s init stop -noshell

Usage

General Notes on using Emysql, including the actual specs:

Starting an Application

The Emysql driver is an Erlang gen-server, and, application.

crypto:start(), % Only needed for testing. In a proper release, this would happen automatically
application:start(emysql).

Adding a Pool

To add a pool, use emysql:add_pool/2 which takes a proplist of parameters. If you need to use one of the emysql:add_pool/K variants, please be aware that they are deprecated in favor of add_pool/2 and will be removed in a future version. See the documentation for what options the add_pool call accepts.

More Record Types

Emysql usually operates with a number of different record types from the database. The driver returns "raw" responses as "packets" and leaves it up to the calling application to handle the result, usually by using one of the conversion routines in emysql to turn the data into a more suitable format.

-record(result_packet, {seq_num, field_list, rows, extra}).

-record(ok_packet, {seq_num, affected_rows, insert_id, status, warning_count, msg}).

-record(error_packet, {seq_num, code, msg}).

For other record types, see include/emysql.hrl.

Executing SQL Statements

% emysql:execute(PoolName, Statement) -> result_packet() | ok_packet() | error_packet()
% PoolName = atom()
% Statement = string() | binary()

emysql:execute(mypoolname, <<"SELECT * from mytable">>).
# result_packet{field_list=[...], rows=[...]}

emysql:execute(mypoolname, <<"UPDATE mytable SET bar = 'baz' WHERE id = 1">>).
# ok_packet{affected_rows=1}

Executing Prepared Statements

	% emysql:prepare(StmtName, Statement) -> ok
	% StmtName = atom()
	% Statement = binary() | string()
	
	emysql:prepare(my_stmt, <<"SELECT * from mytable WHERE id = ?">>).
	# ok

	% emysql:execute(PoolName, StmtName, Args) -> result_packet() | ok_packet() | error_packet()
	% StmtName = atom()
	% Args = [term()]
	
	emysql:execute(mypoolname, my_stmt, [1]).
	#result_packet{field_list=[...], rows=[...]}

Executing Stored Procedures

% emysql:execute(PoolName, StmtName, Args) -> result_packet() | ok_packet() | error_packet()
% StmtName = atom()
% Args = [term()]

emysql:execute(hello_pool,
	<<"create procedure sp_hello() begin select * from hello_table; end">>).
{ok_packet,1,0,0,2,0,[]}

emysql:execute(hello_pool, <<"call sp_hello();">>).
[{result_packet,6,
                [{field,2,<<"def">>,<<"hello_database">>,<<"hello_table">>,
                        <<"hello_table">>,<<"hello_text">>,<<"hello_text">>,
                        254,<<>>,33,60,0,0}],
                [[<<"Hello World!">>],[<<"Hello World!">>]],
                <<>>},
 {ok_packet,7,0,0,34,0,[]}]

Note that you are getting back a list of results here.

Converting Row Data To Records

% emysql:as_record(ResultPacket, RecordName, Fields) -> Result
% ResultPacket = result_packet()
% RecordName = atom() (the name of the record to generate)
% Fields = [atom()] (the field names to generate for each record)
% Result = [record()]

-module(fetch_example).
-record(foo, {bar, baz, bat}).

fetch_foo() ->
        Result = emysql:execute(pool1, <<"select bar, baz, bat from foo">>),
        Recs = emysql:as_record(Result, foo, record_info(fields, foo)),
        [begin
                io:format("foo: ~p, ~p, ~p~n", [Foo#foo.bar, Foo#foo.baz, Foo#foo.bat])
         end || Foo <- Recs].

Converting Row Data To JSON

% emysql:as_json(ResultPacket) -> Result
% Result = [json()]

Result = emysql:execute(pool1, <<"select bar, baz from foo">>),

JSON = emysql:as_json(Result).
% [[{<<"bar">>,<<"bar_value">>}, {<<"baz">>,<<"baz_value">>}], ...]

Note that you are getting back a list of erlang terms in accordance with EEP-18. For actual utf8 binary JSON string you will need external library like jsx or jiffy

Loading Data From a File

emysql:execute(hello_pool,
        <<"LOAD DATA INFILE 'hello.txt' INTO TABLE hello_table (hello_text)">>).

Note, you must grant:

grant file on *.* to hello_username@localhost identified by'hello_password';

You need to give LOCAL or an absolute path, else the file is expected in the database server root. To use the current directory:

{ok, Dir} = file:get_cwd(),
emysql:execute(hello_pool,
    list_to_binary("LOAD DATA INFILE '" ++ Dir ++
        "/hello.txt' INTO TABLE hello_table (hello_text)")).

Tests

Please add a Common Test suite if you are proposing a pull request!

Basic Tests

Some Common Tests (Unit Tests) have been added in the test folder. They have no significant coverage yet but can help to test the basics. They might also help you find trip ups in your system set up (environment and basics suites).

For the basic tests you only need the test database set up and a mysql server running, the same as described above for the samples:

$ mysql [-u<user> -p]
mysql> create database hello_database;
mysql> use hello_database;
mysql> create table hello_table (hello_text char(20));
mysql> grant all privileges on hello_database.* to hello_username@localhost identified by 'hello_password';

To run the basic tests, at the command line type:

make test

Some tests can take up to half a minute to finish on a slow machine.

These tests currently check access to the database (environment suite) the same functionality as the samples (basics suite) and for race conditions as described in issue #9. Thank you, Ransom!

To look at the tests, open logs/index.html in a browser:

open logs/index.html # Will work on a Mac for instance

History

Open Source Erlang MySQL driver efforts are a fractured matter. You may find yourself digging in the sources to find out about their relationships with each other - and which one to pick. Here is a brief history.

Yxa: The first Erlang MySQL driver, in ~270 lines of code, seems to have been written between 2001 and 2004 by Magnus Ahltorp at the Swedish Royal Institute of Technology. It exposes low level, blocking functions to talk 4.0 protocol with a MySQL server. In 2005 Fredrik Thulin brought the driver into its current modular form to use it for the the SIP proxy Yxa while working at the Stockholm University. It has three process layers: a high level, round-robin connection pooling module; then, middle-man, single-connection, blocking processes that do the bit-level wrangling with the MySQL protocol. This module, mysql_conn, can also be used as a single-connection, stand-alone driver. And below this, there are small, protocol-agnostic receiver processes that handle the socket communication with the server, no matter the contents. Fredrik implemented gen-server behavior, added logging, error messages, upgraded authentication, and thoroughly commented the source. This mysql driver is working, complete and stable since at least 2007, it is available as part of Yxa 1.0 (hosted on github). It has no support for transactions or stored procedures. It is the basis for the following two packages. Its basic modular division and general functionality were not changed but only enhanced and it had originally been agreed upon that the Yxa branch should receive and merge the contributions of the later forks upstream. Unfortunately, that did not come to pass.

ejabberd: In 2006, a fork of the Yxa driver was created by Mickael Remond at Process One to become part of the successful instant messaging server ejabberd (also hosted on github). It can be assumed to be as stable as the Yxa branch, didn't change a byte in the lowest level, but only slightly enhanced the higher level. The difference from the original Yxa branch consists mainly of added inspection functions that help using the query results, and of an independent adoption of the MySQL 4.1 client-server protocol. The original Yxa branch has meanwhile adopted EDoc comment format, which makes the sources look more different than they actually are. You can find a Jan 2011 diff between Yxa and the ejabberd version here, and one ignoring comments here. These two branches could be merged quite easily, probably without any change in functionality at all.

erlang-mysql-driver: The storied life of this branch began in 2006 when Yariv Sadan created a fork from the ejabberd branch, made it a standalone project, gave it the maximally generic name that stuck, and hosted it on Google Code. Before he moved on to work at Facebook, he added high-level handling of prepared statements and transactions, and at long last completed some loose ends with the connection pooling that had been known to be lagging since the Yxa version. There were changes both in the original Yxa and the ejabberd branch after the forking off that never made their way into this fork, but in all likelihood they must be minor. It is not always obvious if the changes in erlang-mysql-driver had reached their intended final form. The separation of the three process layers seems to have suffered and complicated enhancements as the highest layer module, mysql.erl, started to become entangled with the second, mysql_conn.erl. Maybe that had a part in why the upstream merge never happened. The main repository of this fork lay dormant since Oct '07 when in Feb '10, Dave Smith, the rebar guy, started making some updates and put them on github. The driver is now enjoying a couple of active forks that make a convincing case for the github Network graphs.

A parallel fork from Yariv's branch, not entangled with Dave's tree, is the one by Nick Gerakines. I suspect it could technically be the strongest of the erlang-mysql-driver forks, with a lot of clean up work by smart guys put in, although it is generally less well known. And much less forked. In the end, the branch was abandoned for Emysql. In all branches, documentation beyond the source comments remains lacking.

Emysql was created from scratch in 2009, specifically to achieve better stability and throughput. It was proposed and written by Jacob Vorreuter at Electronic Arts and deployed at Shawn Fanning's Rupture.com, a social network site for gamers. Initially, Nick Gerakines, Jacob's boss at EA, rewrote large parts of erlang-mysql-server to clean it up. But some fundamental problems remained and when half a year in, they still could not overcome performance and stability issues, Nick gave Jacob the green light to rewrite it from the ground up because they felt that, in Jacob's words, the Yxa branch had been touched by so many people that it had become more complicated than necessary. According to Jacob, Bill Warnecke helped in the early design and testing. They abandoned the separation into three process layers and pulled the socket handling and bit-parsing into one module, coupling the functionality into direct function calls. It looks like they borrowed some chore lines from Magnus but generally created a new, straightforward architecture focused on providing a high performance node. Not only can Emysql open multiple connections, but multiple pools of multiple connections each to multiple database servers, which makes for a strong central OLTP node. Jacob says that Emysql is pretty stable and ran without issues in production at EA. Nick remembers: "The primary way we used it was to have a single Erlang node be the MySQL communication point and allow a number of connected nodes to connect through it to MySQL. We wanted very high throughput with many pids across a grid connecting to it and needed the ability to have a number of MySQL connections open for connection pooling." Rupture was killed in the consolidations of 2009. But Shawn could probably keep the money and we the fond memory of Napster and now, the glistening Emysql.

Eonblast Emysql is a continuation fork of Jacob's work, including all his commits and adding docs, samples, fixes and extensions. Henning Diedrich, Vitaliy Batichko, Chris Rempel, Patrick Atambo, Joel Meyer, Erik Seres, Alexey Lebedeff, Logan Owen, Seven Du, Brendon Hogger, Bart van Deenen, Ransom Richardson and Qing Liang have contributed to this branch. Support for stored procedures has been added, remaining issues are being addressed and there is work going on to bring Mnesia-style transactions. The fork is still close to the original, which currently lies dormant, but has started to add features and possibly increased stability.

Fredrik, Nick and Jacob helped shedding light on the matter. Thank you very much! Errors and omissions are mine. Please let me know about any errors you may spot. Thanks. - Henning

Links and References

Links

(+)maintained at the time of writing, Feb 2011.

TODO

  • decrementing pool size could close sockets that are in use
  • spawn individual conn_mgr gen_server processes for each pool
  • allow row results to be returned as binary

License

Copyright (c) 2009-2011 Bill Warnecke [email protected], Jacob Vorreuter [email protected], Henning Diedrich [email protected], Eonblast Corporation http://www.eonblast.com.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

emysql's People

Contributors

binarin avatar bpuzon avatar bva avatar choptastic avatar csrl avatar dj-al avatar eonblast avatar eseres avatar howleysv avatar jkingsbery avatar jlouis avatar moxford avatar mremond avatar partoa avatar qingliangcn avatar redink avatar saa avatar taybin avatar teburd avatar waj avatar zkessin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

emysql's Issues

Emysql transaction support

I need support for transactions in an application which is using Emysql. While looking through the source code of Emysql, I didn't find direct support for transactions. I was wondering if it is correct that Emysql doesn't support transactions (or should I just call "START TRANSACTION", or via Stored Procedures ) ?

If there is indeed no support for transactions, then is there any reason for it? What is your opinion if I would add it? I just want to make sure if I do it, it can be merged back later and is useful :)

building with make causes unnecessary braces around modules in emysql.app

It seems that in Makefile on line 17 there is an error in

sed -e 's/modules, []/{modules, [$(MODULES)]}/;s/%MAKETIME%/$(MAKETIME)/' < $&lt; &gt; $@

It takes modules without braces and converts into modules wrapped into braces, and this causes an error in generated emysql.app, something like

{{modules...}} instead of {modules...}

Best regards.

Documentation for safe SQL queries

Sorry if this isn't the right place for this request, but it looks like this is the Erlang SQL driver with the best documentation so I thought I would ask:

Can you provide any documentation or suggestions for making safe SQL queries (safe from SQL injection) using this Erlang MySQL driver? It's not entirely clear to me if the driver does some escaping by default or if every query should be run through emysql_util:encode(). Or would you recommend using another layer to prepare SQL statements (such as https://github.com/devinus/sqerl).

I think that adding this to the documentation or samples would help make sure Erlang code using this driver is as secure as possible.

Thanks for your work on the driver and the helpful documentation.

Chad

Encode datetime etc.

When you fetch a datetime value from the database, you get it as a tuple on the form {datetime,{2013,6,26},{1,0,0}} but when you want to construct a query containing a datetime you have to write it as a string like '2013-06-26 01:00:00'. It would be nice if emysql_util:encode/1,2 could accept the tuple form.

The same probably applies to time, date and possibly other mysql types.

Inefficient set_params implementation for prepared statements

I noticed that emysql_conn:set_params/4 in
https://github.com/Eonblast/Emysql/blob/master/src/emysql_conn.erl#L237 is a bit inefficient. For each parameter that is bound, it will do a emysql_tcp:send_and_recv_packet/3 .

emysql_tcp:send_and_recv_packet in https://github.com/Eonblast/Emysql/blob/master/src/emysql_tcp.erl#L36 already does framing (prefix a length + sequence number). So I think the protocol allows us to send all the parameters at once, or at least without the need to wait for all replies and send sequentially.

I will do some tests and if it turns out to indeed be possible and faster to send these differently (either one large sync call, or many async calls), then I will propose a change.

Makefile fix for bad install target

make install was failing while trying to create the output directories. It would create a single directory literally named: {ebin ,include} and the output files were all +x

diff --git a/Makefile b/Makefile
index 0ffd81a..9fd2f42 100644
--- a/Makefile
+++ b/Makefile
@@ -20,9 +20,9 @@ package: clean
        @rm -rf $(PKGNAME)-$(VERSION)/

 install:
-       mkdir -p $(prefix)/$(LIBDIR)/$(PKGNAME)-$(VERSION)/{ebin,include}
-       for i in ebin/*.beam ebin/*.app include/*.hrl; do install $$i $(prefix)/$(LIBDIR)/$(PKGNAME)-$(VERSION)/$$i ; done
+       @for i in ebin/*.beam ebin/*.app include/*.hrl; do install -m 644 -D $$i $(prefix)/$(LIBDIR)/$(PKGNAME)-$(VERSION)/$$i ; done

 test: all
        (cd t;$(MAKE))
-       prove t/*.t
\ No newline at end of file
+       prove t/*.t
+

A way to make encoding truly optional

Older versions of Sphinx (pre 2.0.2 more specifically)* do not implement "SET NAMES" command. I suggest adding an exception that, if undefined is passed as encoding, no command is sent for real (at the moment it tries SET NAMES undefined).

Would you agree with this change? It is backwards incompatible, but I really see no reason why would anyone want current behavior.

[*]: yes, we are using emysql for talking to Sphinx, works great.

race condition in wait_for_connection/1

process, that asks for a connection, will be placed in the queue, but may be skipped as timeouted because of using hack with process_info in pass_connection_to_waiting_pid.
Since it may not be successfull enough to finish receiving response in gen_server:call.

Deprecate add_pool/K for K > 2

The add_pool/K calls are a mess to work with operationally. They should be deprecated:

  • Improve error checking for add_pool/2 so it will bail if something is incorrectly given.
  • Map all the add_pool/K variants to add_pool/2
  • Plan a release where the calls have been removed.

encode(Val, binary, _) when is_float(Val) returns list instead of binary

The function in line 224 of emysql_util.erl returns a list when it should return a binary. This bug makes line 240 of emysql_conn.erl to fail when updating a table with a float value:

([email protected])12> emysql:execute(api_pool, update_activity_r, [0, 190.5, 293209]).
16:43:36.441 [error] Lager event handler error_logger_lager_h exited with reason {'EXIT',{function_clause,[{lager,log,[error,emulator,["Error in process <0.314.0> on node '[email protected]' with exit value: {badarg,[{erlang,byte_size,["190.5"],[]},{emysql_conn,set_params,4,[{file,"src/emysql_conn.erl"},{line,240}]},{emysql_conn,execute,3,[{file,"src/emysql_conn.erl"},{line,82}]},{emysql,'-monitor_work/3-fun-0-',4,[{file,"src/emysql... \n","\n"]],[{file,"src/lager.erl"},{line,86}]},{error_logger_lager_h,handle_event,2,[{file,"src/error_logger_lager_h.erl"},...]},...]}}
16:43:36.504 [debug] Lager installed handler error_logger_lager_h into error_logger
** exception exit: {{badarg,[{erlang,byte_size,["190.5"],[]},
{emysql_conn,set_params,4,
[{file,"src/emysql_conn.erl"},{line,240}]},
{emysql_conn,execute,3,
[{file,"src/emysql_conn.erl"},{line,82}]},
{emysql,'-monitor_work/3-fun-0-',4,
[{file,"src/emysql.erl"},{line,567}]}]},
{}}
in function emysql:monitor_work/3 (src/emysql.erl, line 592)

This bug can be fixed by simply changing line 226 of emysql_util.erl to:

list_to_binary(Res);

AFAIK this bug was first spotted by valinurovam:

#39

add exists pool leak tcp connect

add_pool leak tcp connect when the pool already exists. Because the code always emysql_conn:open_connections and emysql_conn_mgr:add_pool(Pool2), and when it find pool already exists, it just return error, not close these open connect. I think it must check the pool exists first.

add_pool(PoolId, Size, User, Passwd, Host, Port, DB, Encoding, StartCmds)
  when is_atom(PoolId),
       is_integer(Size),
       is_list(User),
       is_list(Passwd),
       is_list(Host),
       is_integer(Port),
       is_list(DB) orelse DB == undefined,
       is_atom(Encoding),
       is_list(StartCmds) ->
    Pool = #pool{
        pool_id = PoolId,
        size = Size,
        user = User,
        password = Passwd,
        host = Host,
        port = Port,
        database = DB,
        encoding = Encoding,
        start_cmds = StartCmds
    },
    Pool2 = case emysql_conn:open_connections(Pool) of
        {ok, Pool1} -> Pool1;
        {error, Reason} -> throw(Reason)
    end,
    emysql_conn_mgr:add_pool(Pool2).

now, I dirty use like this.

    case catch emysql_conn_mgr:wait_for_connection(Db) of
        {'EXIT', pool_not_found} ->
            emysql:add_pool(Db, Poolsize, User, Password, Host, Port, DataBase, utf8);
        Connection ->
            emysql_conn_mgr:pass_connection(Connection)
    end.

it maybe not the best choose.

Race Condition with multiple Databases

Hi all

I'm new here, but I seem to have stumbled upon a race condition where simultaneously executing queries on a certain pool end up using a connection that actually belongs to another pool. I've narrowed it down to a simple testcase using two sql tables in two database

mysqldump -udemo -pdemo test2
CREATE TABLE `test` ( `b` int(11) NOT NULL )
mysqldump -udemo -pdemo test1
CREATE TABLE `test` ( `a` int(11) NOT NULL )

I this erl file:

-module(em).

-include_lib("eunit/include/eunit.hrl").
-include_lib("emysql/include/emysql.hrl").

-compile(export_all).

t_test() ->

    crypto:start(),
    application:start(emysql),

    try emysql:remove_pool(test1) catch _:_ -> ok end,
    try emysql:remove_pool(test2) catch _:_ -> ok end,
    try emysql:remove_pool(test3) catch _:_ -> ok end,

    emysql:add_pool(test1, 1, "demo", "demo", "localhost", 3306, "test1", utf8),
    emysql:add_pool(test2, 1, "demo", "demo", "localhost", 3306, "test2", utf8),
    emysql:add_pool(test3, 1, "demo", "demo", "localhost", 3306, "test1", utf8),

    ?assertMatch( #result_packet{}, emysql:execute(test1, "select a from test")),

    ?assertMatch( #result_packet{}, emysql:execute(test2, "select b from test")),

    ?assertMatch( #result_packet{}, emysql:execute(test3, "select a from test")),

    F=fun() ->
        timer:sleep(100+random:uniform(500)),
        ?assertMatch( #result_packet{}, emysql:execute(test2, "select b from test")),
        ?assertMatch( #result_packet{}, emysql:execute(test3, "select a from test")),
        ok
    end,
    [spawn(F) || _ <- lists:seq(1,5)],
    timer:sleep(1000),
    ok.

Running em:test() with values of 1 for the nconnections in the emysql:add_pool(...) causes an assertMatch failure:

=ERROR REPORT==== 1-Feb-2012::14:53:06 ===
Error in process <0.10468.0> with exit value: {{assertMatch_failed,[{module,em},{line,29},{expression,"emysql : 
execute ( test2 , \"select b from test\" )"},{pattern,"# result_packet { }"},
{value,{error_packet,1,1054,<<5 bytes>>,"Unknown column 'b' in 'field list'"}}]},[{em,'-t_test/0-fun-3-'... 

From other tests in more complex situations with actual data in the columns, I know that it was actually talking on another connection than the one specified with add_pool. I also notice that when the error occurs, if you do a emysql_conn_mgr:pools() call, the order of the pools is changed from the initial order.

If you increase the connections in the add_pool call, the whole thing works fine and you get no errors.

Feature request: load monitoring

Hi all

Since I'm on a roll with emysql, I'd like to talk about a feature we could use. We can probably implement it here (at spilgames.com), but I'd like your thoughts on it.

What I'd like is some kind of emysql load monitor figure, so that I can slow down front end nodes if they start hammering emysql too hard, and also to tell the money guys to buy new hardware when required. I know that during testing I can hit the connection_lock_timeout quite easily (just spawn 50k requests at the same time :-). We plan to use emysql in a production environment where dozens of servers running probably 100's of Erlang nodes. I need some sort of load figure in emysql so that I can control the total flow through our cluster.

I was thinking of hooking something to emysql_conn_mgr:wait_for_connection/1 to count the number of requests that are waiting for a connection, and create an interface function emysql:install_load_callback that makes it possible to do something with it.

Is this in your opinion the best way/place to do it? If the code is good, would you like to include it in the eonblast/emysql repository? Any other suggestions?

Bart

Split off the pool!

Make the pool internally in the client pluggable. In particular, support using another pool, like, e.g., poolboy.

performance improvements, where to start

Hi all

I'm running into some performance issues, and would like to ask for your input on where to start with trying to improve the emysql performance.

My table contains 5 small integer columns, but can have a million (or more rows). Doing "select * from ..." with the normal mysql client takes about two seconds. Doing the same with emysql:execute takes about a 25 seconds.

Do you already have any idea where the bottleneck is, or should I start profiling everything? Should I make my query return smaller chunks of data?

Just asking for some input, I do have the time to work on it.

Thanks

Bart van Deenen, contractor at Spilgames.com

multi database transaction support (patch included)

Hi all

I've created a patch that allows me to execute mysql transactions. I've made a new api call emysql:execute_transaction that calls emysql_conn:execute_transaction. The basic idea is that the query is executed inside a "start transaction; ....; rollback|commit " , and that you pass a Pid to emysql that gets sent the results of the queries, and can then respond with commit or rollback (or timeout which also causes a rollback).

I have a simple test application (test.erl) which shows the functionality. Create one (or more) databases with tables 'uniq' and 'nonuniq' using the transaction_databases.sql, and run test:t() to see the result of the multi-db-transaction.
We're definitely going to take this in production here, in a more complicated setting, but the basic structure is shown in test.erl.

Have fun!

Bart ([email protected], consulting at spilgames.com).

Here's the diff:

diff --git a/README.md b/README.md
index 4194987..d1042bd 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,18 @@
-## Emysql 0.2.14
+## Spilgames
+
+Maintainer: Bart van Deenen ( [email protected], [email protected])
+
+### Tags:
+
+* tag 20120402   (no changes to external library). eonblast/emysql merged april 2nd
+
+### last update with eonblast library:
+
+* april 18th 2012
+
+
+ ## Emysql 0.2.14
+
 
 <hr/>
 **Please note: this commit of Apr '12 has incompatible changes for automatic UTF-8 conversion vs the Dec '11 commits.**
@@ -608,4 +622,4 @@ OTHER DEALINGS IN THE SOFTWARE.



-[Adding_a_Pool]:                 #Adding_a_Pool
\ No newline at end of file
+[Adding_a_Pool]:                 #Adding_a_Pool
diff --git a/src/emysql.erl b/src/emysql.erl
index 0847402..6cc13bf 100644
--- a/src/emysql.erl
+++ b/src/emysql.erl
@@ -100,7 +100,8 @@
            prepare/2,
            execute/2, execute/3, execute/4, execute/5,
            default_timeout/0,
-           modules/0   
+           modules/0   ,
+           execute_transaction/4
        ]).
 
 % for record and constant defines
@@ -432,6 +433,13 @@ execute(PoolId, Query, Timeout) when (is_list(Query) orelse is_binary(Query)) an
 execute(PoolId, StmtName, Timeout) when is_atom(StmtName), is_integer(Timeout) ->
    execute(PoolId, StmtName, [], Timeout).
 
+execute_transaction( PoolId, Query, Pid, Args) ->
+   Connection = emysql_conn_mgr:wait_for_connection(PoolId),
+    %-% io:format("~p execute got connection for pool id ~p: ~p~n",[self(), PoolId, Connection#emysql_connection.id]),
+   monitor_work(Connection, default_timeout(), {emysql_conn, execute_transaction, 
+       [Connection, Query, Pid, Args] }).
+   
+
 %% @spec execute(PoolId, Query|StmtName, Args, Timeout) -> Result | [Result]
 %%     PoolId = atom()
 %%     Query = binary() | string()
diff --git a/src/emysql_conn.erl b/src/emysql_conn.erl
index 8112354..30fb6ce 100644
--- a/src/emysql_conn.erl
+++ b/src/emysql_conn.erl
@@ -30,7 +30,8 @@
        execute/3, prepare/3, unprepare/2,
        open_connections/1, open_connection/1,
        reset_connection/3, close_connection/1,
-       open_n_connections/2, hstate/1
+       open_n_connections/2, hstate/1,
+       execute_transaction/4
 ]).
 
 -include("emysql.hrl").
@@ -44,6 +45,33 @@ set_encoding(Connection, Encoding) ->
    Packet = <<?COM_QUERY, "set names '", (erlang:atom_to_binary(Encoding, utf8))/binary, "'">>,
    emysql_tcp:send_and_recv_packet(Connection#emysql_connection.socket, Packet, 0).
 
+execute_transaction( Connection, Query, Pid, Args ) when is_list(Query) ->
+   execute_transaction(Connection,  << (emysql_util:to_binary(Query, Connection#emysql_connection.encoding))/binary>>,
+   Pid, Args);
+
+execute_transaction( Connection, Query, Pid, Args ) ->
+   StartTransaction = <<"START TRANSACTION; ">>,
+   Packet = <<?COM_QUERY, StartTransaction/binary, Query/binary>>,
+   Results  = emysql_tcp:send_and_recv_packet( Connection#emysql_connection.socket,
+       Packet, 0) ,
+
+   Pid ! {self(), {Results, Args} } ,
+   Sql = receive 
+       {Pid, commit} ->
+           <<"COMMIT">>;
+       _ ->
+           <<"ROLLBACK">>
+   after
+       1500 ->
+           io:format("~p timeout -> rollback ~n", [self()]),
+           <<"ROLLBACK">>
+   end,
+           
+   emysql_tcp:send_and_recv_packet( Connection#emysql_connection.socket,
+       <<?COM_QUERY, Sql/binary>>, 0) .
+           
+
+   
 execute(Connection, Query, []) when is_list(Query) ->
     %-% io:format("~p execute list: ~p using connection: ~p~n", [self(), iolist_to_binary(Query), Connection#emysql_connection.id]),
    Packet = <<?COM_QUERY, (emysql_util:to_binary(Query, Connection#emysql_connection.encoding))/binary>>,
diff --git a/test.erl b/test.erl
new file mode 100644
index 0000000..406ff4b
--- /dev/null
+++ b/test.erl
@@ -0,0 +1,129 @@
+-module(test).
+
+-compile(export_all).
+
+-include_lib("emysql/include/emysql.hrl").
+
+t() ->
+   error_logger:tty(false),
+   crypto:start(),
+   emysql:stop(),
+   emysql:start(),
+   emysql:add_pool(uniq_db, 1, "root", "ssp" , "localhost", 3306, "test1", utf8),
+   emysql:add_pool(nonuniq_db, 1, "root", "ssp" , "localhost", 3306, "test2", utf8),
+
+   emysql:execute( uniq_db, <<"truncate uniq">>),
+   emysql:execute( nonuniq_db, <<"truncate nonuniq">>),
+
+   emysql:execute( uniq_db, <<"insert into uniq values ( 1,1), (2,2)">>),
+   emysql:execute( nonuniq_db, <<"insert into nonuniq values ( 1,1), (2,2)">>),
+
+   show_dbs(),
+
+   % create a transient transaction process that spawns two processes that
+   % perform jobs in a transaction.
+   %
+   spawn_link( ?MODULE, transaction, [[
+       { uniq_db,    "insert into uniq values ( 1,1), (2,2)", 1 },
+       { nonuniq_db, "insert into nonuniq values ( 1,1), (2,2)", 2 }
+   ]]),
+
+   timer:sleep(100),
+   show_dbs(),
+
+
+   spawn_link( ?MODULE, transaction, [[
+       {  uniq_db, <<"insert into uniq values    ( 3,3), (4,4)">>, 1 },
+       {  nonuniq_db, <<"insert into nonuniq values ( 3,3), (4,4)">>, 2 }
+   ]]),
+
+   timer:sleep(100),
+   show_dbs(),
+   ok.
+
+%% 
+%% @doc entry point for transaction handler
+%%
+transaction( List ) ->
+   transaction(List, 0).
+
+%%
+%% @doc emysql:execute_transaction process spawner
+%%
+transaction( [], Ct) ->
+   transaction(Ct, 0, 0, []);
+
+%
+% Args is just a term() that we pass around to the pool process.
+%
+transaction( [ {Pool, Query, Args} | Rest ], Ct ) ->
+   io:format("Args:~p ~p doing ~p in multi-transaction ~n", [Args, Pool, Query]),
+   spawn( emysql, execute_transaction, [Pool, Query, self(), Args] ),
+   transaction( Rest, Ct+1).
+
+
+%% 
+%% @doc transaction response handler
+%%
+-spec transaction( 
+   Remaining :: non_neg_integer(),   % responses remaining
+   Goods :: non_neg_integer()        % count of good responses up to now
+   Bads  :: non_neg_integer()        % count of bad responses up to now
+   Pids  :: [pid()].                 % pids of pool processes.
+
+% everyting ok
+transaction( 0, _, 0, Pids) ->
+   lists:map( fun(P) -> P ! {self(), commit } end, Pids);
+
+% at least one bad
+transaction( 0, _, Bads, Pids) ->
+   io:format("~p actions went bad, we're doing a rollback ~n", [Bads]),
+   lists:map( fun(P) -> P ! {self(), rollback } end, Pids);
+   
+% still handling responses from transaction pool processes.
+transaction( Remaining, Goods, Bads, Pids) ->
+   receive
+       {Pid, {Results, Args}} ->
+           case is_not_ok(Results, Args) of 
+               true  -> transaction(Remaining-1, Goods, Bads+1, [Pid|Pids]);
+               false -> transaction(Remaining-1, Goods+1, Bads, [Pid|Pids])
+           end;
+       Other ->
+           io:format("Other: ~p~n", [Other])
+   after 1000 ->
+       io:format("Aborting transaction due to timeout"),
+       lists:map( fun(P) -> P ! {self(), rollback } end, Pids)
+   end.
+
+%%
+%% @doc check if a list of responses from emysql_tcp:send_and_recv_packet/3
+%% contains bad responses.
+%%
+-spec is_not_ok( [tuple()], [term()] ) -> boolean().
+
+is_not_ok( Results , Args) ->
+   lists:any( fun(R) ->
+       case R of
+           #eof_packet{status=M} ->
+               io:format("Args: ~p action eof ~p~n", [Args, M]),
+               true;
+           #error_packet{msg=M} ->
+               io:format("Args: ~p action error ~p~n", [Args, M]),
+               true;
+           _ -> false
+       end
+   end, Results).
+   
+
+show_dbs() ->
+   io:format("-----------------------------------------------------~n",[]),
+   #result_packet{rows=Ru2} = emysql:execute(uniq_db, <<"select * from uniq">>),
+   #result_packet{rows=Rnu2} = emysql:execute(nonuniq_db, <<"select * from nonuniq">>),
+
+   io:format("uniq db:    ~p~n", [Ru2]),
+   io:format("nonuniq db: ~p~n", [Rnu2]),
+   io:format("-----------------------------------------------------~n",[]).
+                   
+                   
+                   
+
diff --git a/transaction_databases.sql b/transaction_databases.sql
new file mode 100644
index 0000000..cc09c05
--- /dev/null
+++ b/transaction_databases.sql
@@ -0,0 +1,13 @@
+
+DROP TABLE IF EXISTS `uniq`;
+CREATE TABLE `uniq` (
+  `key` int(11) NOT NULL,
+  `value` int(11) NOT NULL,
+  PRIMARY KEY (`key`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+DROP TABLE IF EXISTS `nonuniq`;
+CREATE TABLE `nonuniq` (
+  `key` int(11) NOT NULL,
+  `value` int(11) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Named parameters for prepared statements and as_json output util

If you have query with lots of parameteres you get code which is far far away from readability and maintainability. Using named parameteres ":param_name" instead of positional "?" can be very helpfull.

It could be implemented as a simple wrapper or util, which will get ":params" list from query string and build function to extract those values from proplist passed as argument.

Similar processing can be done on output side. Along with "as_record()" could be something like "as_json()" - each result row transformed to [{<<"key">>, <<"val">>}, ...] proplist.

There is at least one use case, where this little two utils will be realy useful - webservice with JSON api. JSON data parsed into erlang are represented in [{<<"key">>, <<"val">>}, ...] format so with those two utils you can directly pass parsed JSON as parameter to query and pass query result directly to JSON decoder.

I can do this if you like this idea.

Running Make Throws Compile-time Errors

Following the included readme file leads to several error messages. When I ran the make command in the Emysql directory, the following error occurred:

$ make
mkdir -p ebin
(cd src;make)
make[1]: Entering directory /home/dan/fire/Emysql/src' erlc -W -I ../include +debug_info -o ../ebin emysql_app.erl erlc -W -I ../include +debug_info -o ../ebin emysql_auth.erl erlc -W -I ../include +debug_info -o ../ebin emysql_conn.erl ./emysql_conn.erl:206: undefined macro 'SERVER_MORE_RESULTS_EXISTS' ./emysql_conn.erl:29: function hstate/1 undefined make[1]: *** [../ebin/emysql_conn.beam] Error 1 make[1]: Leaving directory/home/dan/fire/Emysql/src'
make: *** [all] Error 2

This happens under an unprivileged account and the root account. The following are the particulars of my environment:

$ uname -a
Arch Linux 2.6.36-ARCH #1 SMP PREEMPT Mon Jan 24 18:41:03 CET 2011 x86_64 AMD Phenom(tm) II X6 1055T Processor AuthenticAMD GNU/Linux

$ pacman -Q erlang
erlang R14B-1

Although I have not tested this thoroughly to verify that there is no loss of funtionality, commenting out line 206 in the emysql_conn.erl file so it reads like this:

%%++ case (State band ?SERVER_MORE_RESULTS_EXISTS) of 0 -> ""; _-> "MORE_RESULTS_EXISTS " end

Allowed the make to complete successfully. I will post back if any loss of functionality occurs.

Exported records are far too generic

When exporting record names, one has to be somewhat wary of name clashes. A name like connection is very general and is often subject to a clash somewhere. Since we only having a global include file, we will often run into trouble here.

My suggestion is that we figure out exactly what records we need to export the name of to the rest of the world. If we can keep information local, I'd much rather do that. And when exporting, we should decide if we are to prefix them with emysql_. See for instance, csrl@776801e which is one such fix.

[proposal] time_zone support per connection pool

Hi,

Proposal for the new functionality:

  1. add timezone field to #pool{}.
  2. create emysql_conn:set_timezone/2 (very similar to emysql_conn:set_database/2).
  3. add backwards-compatible emysql:add_pool/9.

If you think that current emysql:add_pool/8 is becoming too big, I can refactor it to something like:

add_pool(PoolId, Size, User, Password, Host, Port, Opts)
Opts = [{key(), value()}].
key() = database | encoding | time_zone.

And analogously #pool{}.

Note: this would be backwards-compatible (but not intuitive):

add_pool(PoolId, Size, User, Password, Host, Port, Opts)
add_pool(PoolId, Size, User, Password, Host, Port, Database, Encoding)

And, if we agree on that, after transition period emysql_conn:add_pool/8 should be removed.

Let me know which one you prefer.

Consider crypto:sha/1 deprecation

In R16B01, certain crypto-functions have been deprecated and will be removed eventually. This means we need to handle this moving forward, or the driver won't be able to handle newer versions of Erlang.

This is not easy to handle since there is no backwards-compatible solution which would work in older Erlang-versions. One could use compile-time macros. Or we could simply make the bump when we hit R17 as a release.

load local infile not allowed

the sql is :
LOAD DATA low_priority local INFILE '/data/logs/behavior_intime/4399_41/t_log_role_create/2012_10_31/13/log.t_log_role_create.8.2012_10_31_13_14.log' INTO TABLE 4399_xlfc_41.t_log_role_create (pid ,role_id, role_name ,account_name ,create_time ,sex ,faction_id, category);

get this error:
{error_packet,1,1148,<<"42000">>,
"The used command is not allowed with this MySQL version"}

mysql version is 5.5.18

i compiled mysql with --enable-local-infile

Document LOAD DATA INFILE

LOAD DATA low_priority local INFILE '/data/logs/behavior_intime/4399_41/t_log_role_create/2012_10_31/13/log.t_log_role_create.8.2012_10_31_13_14.log' INTO TABLE 4399_xlfc_41.t_log_role_create (pid ,role_id, role_name ,account_name ,create_time ,sex ,faction_id, category);

Empty Password

In erlang-mysql-client under mysql_auth they handle empty passwords by not hashing anything at all, this works.

Emysql always assumes there's a password but many times there isn't one.

return value problem on execute stored procedure

Hi,

please see https://gist.github.com/790941, the SP was created by

create procedure sp_hello() begin select * from hello_table; end;

If I call run2(4) which is the same as the original code, everything is fine.

If I call run3(4), as the gist shows, it returns #result_packet and #ok_packet alternatively. Should I take this as normal and run twice if there is an SP ?

If I call run2(4) followed by run3(4), (uncomment the run2 line in the gist), then run2() start behaves like run3().

Can you look into it? Thanks.

Rewrite TCP Receive Loop

jlouis: "I'd do a simple

 gen_tcp:recv(Socket, PacketLength, ?TIMEOUT)

on the socket and then just wait for all data to arrive directly like this, or time out after a while. Then go again and wait if {error, timeout} occurs. It is simpler, pushes work to the C layer, and is probably going to run a faster route than this."

crash while connecting - emysql_auth - badmatch

I am getting an error trying to connect, for example:
$ erlc hello.erl
$ erl -pa ./ebin -s hello run -s init stop -noshell

{"init terminating in do_boot",{{badmatch,{"lN"+]sK_x_S",<<21 bytes>>}},[{emysql_auth,recv_greeting,1},
{emysql_auth,do_handshake,3},
{emysql_conn,open_connection,1},{emysql_conn,open_connections,1},
{emysql,add_pool,8},{hello,run,0},{init,start_it,1},{init,start_em,1}]}}

Crash dump was written to: erl_crash.dump
init terminating in do_boot ()

the demo user is set up correctly.

mac os x snow leopard
erlang version is R14A
mysql is 5.5.9

Proposal: Remove Strings As Lists and Locale Encoding

From csrl:
As a general comment - unrelated to this specific pull request, I'd love to see all of the string as list and locale encoding code removed from the project, and have it work only on binaries. Push the encoding to the application layer where it belongs. With the encoding in place in the emysql layer, one looses all of the benefits of iolists.

Problem with emysql_util:as_proplist

(adv-dev-mysql@localhost)10> emysql_util:as_proplist(Result).                                               
** exception error: no function clause matching emysql_util:dualmap(#Fun<emysql_util.0.107751508>,[],
                                                                    [41,<<"test1">>,5,<<"wfnf">>,4,<<"countff">>,21,<<"kiki">>]) (src/emysql_util.erl, line 224)
     in function  emysql_util:dualmap/3 (src/emysql_util.erl, line 227)
     in call from emysql_util:dualmap/3 (src/emysql_util.erl, line 227)

Code for as_proplist should be like for as_json.

as_proplist(#result_packet{field_list=_Cols,rows=_Vals}) when _Cols =:= undefined, 
                                                              _Vals =:= undefined ->
    [];
as_proplist(#result_packet{field_list=_Cols,rows=_Vals}) when is_list(_Cols), 
                                                              _Vals =:= undefined ->
    [];
as_proplist(#result_packet{field_list=_Cols,rows=_Vals}) when is_list(_Cols), 
                                                              _Vals =:= [] ->
    [];
as_proplist(Res = #result_packet{field_list=Cols,rows=Vals}) when is_list(Cols), 
                                                                  is_list(Vals) ->
    Fields = field_names(Res),
    [begin
        [{K, V} || {K, V} <- lists:zip(Fields, Val)]
    end || Val <- Vals].

May be it'll be better to use
erlang:binary_to_atom(K, utf8). %% OR erlang:binary_to_existing_atom/2.

Database connection error ({failed_to_connect_to_database,emfile})

Hi,
Following error coming when try to add pool. (This was initiated by parallel processes)
{'EXIT',{failed_to_connect_to_database,emfile}
Fllowing is the Erlang Code
case catch emysql:add_pool(ConnPoolId, 10, UName, Pw, host,Port, "test", utf8) of
ok ->

{ok, ConnPoolId};
{error, pool_already_exists} ->
already_exist;
{'EXIT',{failed_to_connect_to_database, emfile}} ->
io:fwrite("Else set_connection pn",[{{failed_to_connect_to_database, emfile}, DbName, ConnPoolId}]),
catch spawn(?MODULE, update_status,["failed_to_connect_to_database"]),
error;
Else ->
catch spawn(?MODULE, update_status,["database_error"]),
io:fwrite("Else set_connection pn",[{Else, DbName, ConnPoolId}]),
error
end.

Pls help to solve this issue.
Br,
Sanath

Clients killed when waiting for connections may leave the connections locked forever

In the example below killing the client that is queuing for a free connection leaves that connection in an unusable state.
In our case it got exposed as our application was timeouting and killing the process talking to emysql before emysql own timeouts could kick in.

%% Setup
[application:start(A) || A <- [crypto, emysql]].
application:set_env(emysql, default_timeout, 20000).
emysql:add_pool(h, 1, "user", "pass", "localhost", 3306, "db", utf8).

%% Bug trigger
F1 = fun() -> emysql:execute(h, "select sleep(10);", 15000) end.
F2 = fun() -> emysql:execute(h, "select 1") end.

spawn(F1).
f(P).
P = spawn(F2).
timer:sleep(1000).
exit(P, kill).

%% This will never work again
emysql:execute(h, "select 1").

remove io:format when using latin1 encoding

Hi, would it be possible to remove in the repository the io:format used to check if encoding has been done correctly... I can affirm that it does work correctly without any glitch, for using it a certain time now.

The code is in the emysql_util.erl file at line 196 and 198...

Unless there is a good reason to keep it in here, in this case I'll keep edit the file by hand, thanks !

record "connection" name clash with amqp_client record

I don't know who is right or wrong, but I thought that it was "best practice" to prefix all record names with the module name to avoid clashes because Erlang doesn't have name spaces for these things, yet?.

I have a situation where I am using Emysql and the amqp_client in the same module and guess what, they are both trying to define a record called "connection":

./lib/erlang/lib/emysql-0.2/include/emysql.hrl:30:-record(connection, ...
./lib/erlang/lib/rabbit_common-2.7.0/include/rabbit.hrl:30:-record(connection, {protocol, user, ...

I am going to change the Emysql code and rebuild and reinstall it into my local Erlang development zone and see how I get on, I will submit patches or something unless you beat me to it first! I fancy my chances of tinkering with Emyswl and not breaking it as higher than fiddling with the amqp_client at the moment.

All the best!
Sean Charles.

_UPDATE_* I have realised that his has been fixed and I have checked out this version:

Author: Logan Owen [email protected]
Date: Wed Aug 31 19:13:30 2011 -0400

avoid name collision with RabbitMQ for the connection record by renaming it to emysql_connection

BUT IT STILL STANDS that all of the records should be prefixed with "emysql_" as sooner or later the problem is bound to come up again. For now I will use this version and be happy. Is it worth me finding the time to modify all the records for you ?

While Creating .beam files in windows found Error

Hello

In windows ,i create manually .beam files using this command

erlc -I includes/ejabberd/include emysql_conn.erl

Which command i found from http://www.ejabberd.im/ejabberd-modules

All ".beam" files are created successfully except this two files...

emysql_conn.erl
emysql_util.erl

try with this command

erlc -I includes/ejabberd/include emysql_conn.erl
Error:
C:/Program Files/ejabberd-2.1.9/bin/emysql_conn.erl:55: function atom_to_binary/2 undefined
C:/Program Files/ejabberd-2.1.9/bin/emysql_conn.erl:79: function atom_to_binary/2 undefined

erlc -I includes/ejabberd/include emysql_conn.erl
Error:
C:/Program Files/ejabberd-2.1.9/bin/emysql_util.erl:57: function binary_to_atom/2 undefined

Please advice me how to create .beam files for ejabberd in windows system...

Thanks
Nishant Bhatt

fix for recv_greeting error handler in emysql_auth.erl

When failing to connect to a server, the error handler was implemented incorrectly causing it to crash:
** exception error: undefined function error_packet:code/1
in function emysql_auth:recv_greeting/1
in call from emysql_auth:do_handshake/3
in call from emysql_conn:open_connection/1
in call from emysql_conn:open_connections/1
in call from emysql:add_pool/8

this patch should fix it

diff --git a/src/emysql_auth.erl b/src/emysql_auth.erl
index 5670b1a..f96a422 100644
--- a/src/emysql_auth.erl
+++ b/src/emysql_auth.erl
@@ -44,8 +44,11 @@ recv_greeting(Sock) ->
        GreetingPacket = emysql_tcp:recv_packet(Sock),
        case GreetingPacket#packet.data of
                <<255, _/binary>> ->
-                       Error = emysql_tcp:package_server_response(Sock, GreetingPacket),
-                       exit({Error:code(), Error:msg()});
+                       #error_packet{
+                               code = Code,
+                               msg = Msg
+                       } = emysql_tcp:package_server_response(Sock, GreetingPacket),
+                       exit({Code, Msg});
                <<ProtocolVersion:8/integer, Rest1/binary>> ->
                        {ServerVersion, Rest2} = emysql_util:asciz(Rest1),
                    <<TreadID:32/little, Rest3/binary>> = Rest2,
@@ -112,4 +115,4 @@ password_old(Password, Salt) ->
     Seed2 = P2 bxor S2,
     List = emysql_util:rnd(9, Seed1, Seed2),
     {L, [Extra]} = lists:split(8, List),
-    list_to_binary(lists:map(fun (E) -> E bxor (Extra - 64) end, L)).
\ No newline at end of file
+    list_to_binary(lists:map(fun (E) -> E bxor (Extra - 64) end, L)).

make test-all fails on many encoding tests

make test-all fails on lots of tests. Most of them because emysql_util:quote/2 is undefined in 0.3, and a few extra errors. All tests used to work on 0.2.

@jlouis, I could fix them, but I guess you know the encoding subtleties in emysql way better than I do; it might just be necessary to remove most of the tests, so will not take a long time.

This came up while preparing emysql for CI environment.

Suddenly stops working

I have scenario where my emysql application stops processing queries.. This does not happen always but it happens rarely.

I did a netstat on the client machine and could see that socket was on CLOSE_WAIT state.

Once this state comes, all the queries suspends and nothing happens.

Revamp the TCP communication

The TCP communication at the protocol level is quite complex. There is a way simpler implementation, by observing the following:

  • Have separate send and recv calls so the caller controls the sending and receiving of messages.
  • Push command generation down so you only know about commands and protocol specific encoding in the TCP stack itself.
  • Use a #state{} record which can track the buffer continuation on the socket. This would simplify almost all of the code quite a lot.

connection lock forever when losting connection

I tested a bit of connection-auto-recover when lost connection with only one pool

First I run a query and make sure it works, then stop mysql, and start, then run execute results one exception, but following executes get recovered as expected.

  1. emysql:execute(mysql2, "select 1").
    ** exception exit: {failed_to_recv_packet_header,closed}
    in function emysql:monitor_work/3

  2. emysql:execute(mysql2, "select 1").
    {result_packet,5,
    [{field,2,<<"def">>,<<>>,<<>>,<<>>,<<"1">>,<<>>,8,<<>>,63,1,
    129,0}],
    [[1]],
    <<>>}

Then, I closed mysql again, and execute returns econnrefused

  1. emysql:execute(mysql2, "select 1").
    ** exception exit: {failed_to_connect_to_database,econnrefused}
    in function emysql_conn:open_connection/1
    in call from emysql_conn:reset_connection/2
    in call from emysql:monitor_work/3

Then I started mysql again, all following executes returns connection_lock_timeout and the connection cannot be recovered.

  1. emysql:execute(mysql2, "select 1").
    ** exception exit: connection_lock_timeout
    in function emysql_conn_mgr:wait_for_connection/1
    in call from emysql:execute/4

So I guess the lock should be released on the 1) case of econnrefused etc.

I haven't test other error cases.

Thanks.

Split emysql.hrl into an external and internal part

Currently, everything is just shoved directly into one large header file. But a lot of the definitions could be kept module-local and should not be part of the general header file.

At least:

  • Commands should go to the protocol specific parts of the code base.

Save binary data as is

Tell me pls, how can i save binary data as is with prepared statements if my connection work on utf8 encoding.
Now it is imposible, cause all given variables translated with unicode:characters_to_binary. and if my binary list dont has utf8 bytes, they converted. Its abnormal. I need to save my bin data as is.

Name Collision for "connection" record

I am utilizing Emysql in a project which also uses RabbitMQ. Both libraries have records called connection, so to solve the issue, it would be helpful if the connection record was renamed to something more specific to the project, such as emysql_connection.

I can supply a patch if need be.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.