diff --git a/.gitignore b/.gitignore index 248d1ab..e4d4e35 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ Makefile *.bundle *.so *.a +*.rbc mkmf.log pkg/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f12c9..f90d6f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,20 @@ # Changelog -## 0.1.9 (HEAD) +## 0.1.9 (July 17th, 2010) * Support async ActiveRecord access with fibers and EventMachine (mperham) +* string encoding support for 1.9, respecting Encoding.default_internal +* added support for rake-compiler (tenderlove) +* bugfixes for ActiveRecord driver +** one minor bugfix for TimeZone support +** fix the select_rows method to return what it should according to the docs (r-stu31) +* Mysql2::Client#fields method added - returns the array of field names from a resultset, as strings +* Sequel adapter +** bugfix regarding sybolized field names (Eric Wong) +** fix query logging in Sequel adapter +* Lots of nice code cleanup (tenderlove) +** Mysql2::Error definition moved to pure-Ruby +** Mysql2::client#initialize definition moved to pure-Ruby +** Mysql2::Result partially moved to pure-Ruby ## 0.1.8 (June 2nd, 2010) * fixes for AR adapter for timezone juggling diff --git a/VERSION b/VERSION index 699c6c6..1a03094 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.8 +0.1.9 diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index d96119d..f3297de 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -5,6 +5,9 @@ require 'rubygems' require 'benchmark' require 'active_record' +ActiveRecord::Base.default_timezone = 'Pacific Time (US & Canada)' +ActiveRecord::Base.time_zone_aware_attributes = true + number_of = 10 mysql2_opts = { :adapter => 'mysql2', diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c new file mode 100644 index 0000000..7fce439 --- /dev/null +++ b/ext/mysql2/client.c @@ -0,0 +1,518 @@ +#include +#include + +VALUE cMysql2Client; +extern VALUE mMysql2, cMysql2Error, intern_encoding_from_charset; +extern ID sym_id, sym_version, sym_async; + +#define REQUIRE_OPEN_DB(_ctxt) \ + if(!_ctxt->net.vio) { \ + rb_raise(cMysql2Error, "closed MySQL connection"); \ + return Qnil; \ + } + +/* + * used to pass all arguments to mysql_real_connect while inside + * rb_thread_blocking_region + */ +struct nogvl_connect_args { + MYSQL *mysql; + const char *host; + const char *user; + const char *passwd; + const char *db; + unsigned int port; + const char *unix_socket; + unsigned long client_flag; +}; + +/* + * used to pass all arguments to mysql_send_query while inside + * rb_thread_blocking_region + */ +struct nogvl_send_query_args { + MYSQL *mysql; + VALUE sql; +}; + +/* + * non-blocking mysql_*() functions that we won't be wrapping since + * they do not appear to hit the network nor issue any interruptible + * or blocking system calls. + * + * - mysql_affected_rows() + * - mysql_error() + * - mysql_fetch_fields() + * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths + * - mysql_field_count() + * - mysql_get_client_info() + * - mysql_get_client_version() + * - mysql_get_server_info() + * - mysql_get_server_version() + * - mysql_insert_id() + * - mysql_num_fields() + * - mysql_num_rows() + * - mysql_options() + * - mysql_real_escape_string() + * - mysql_ssl_set() + */ + +static VALUE rb_raise_mysql2_error(MYSQL *client) { + VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client)); + rb_funcall(e, rb_intern("error_number="), 1, INT2NUM(mysql_errno(client))); + rb_funcall(e, rb_intern("sql_state="), 1, rb_tainted_str_new2(mysql_sqlstate(client))); + rb_exc_raise(e); + return Qnil; +} + +static VALUE nogvl_init(void *ptr) { + MYSQL * client = (MYSQL *)ptr; + + /* may initialize embedded server and read /etc/services off disk */ + mysql_init(client); + + return client ? Qtrue : Qfalse; +} + +static VALUE nogvl_connect(void *ptr) { + struct nogvl_connect_args *args = ptr; + MYSQL *client; + + client = mysql_real_connect(args->mysql, args->host, + args->user, args->passwd, + args->db, args->port, args->unix_socket, + args->client_flag); + + return client ? Qtrue : Qfalse; +} + +static void rb_mysql_client_free(void * ptr) { + MYSQL * client = (MYSQL *)ptr; + + /* + * we'll send a QUIT message to the server, but that message is more of a + * formality than a hard requirement since the socket is getting shutdown + * anyways, so ensure the socket write does not block our interpreter + */ + int fd = client->net.fd; + int flags; + + if (fd >= 0) { + /* + * if the socket is dead we have no chance of blocking, + * so ignore any potential fcntl errors since they don't matter + */ + flags = fcntl(fd, F_GETFL); + if (flags > 0 && !(flags & O_NONBLOCK)) + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + + /* It's safe to call mysql_close() on an already closed connection. */ + mysql_close(client); + xfree(ptr); +} + +static VALUE nogvl_close(void * ptr) { + MYSQL *client = (MYSQL *)ptr; + mysql_close(client); + client->net.fd = -1; + return Qnil; +} + +static VALUE allocate(VALUE klass) { + MYSQL * client; + + return Data_Make_Struct( + klass, + MYSQL, + NULL, + rb_mysql_client_free, + client + ); +} + +static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket) { + MYSQL * client; + struct nogvl_connect_args args; + + Data_Get_Struct(self, MYSQL, client); + + args.host = NIL_P(host) ? "localhost" : StringValuePtr(host); + args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); + args.port = NIL_P(port) ? 3306 : NUM2INT(port); + args.user = NIL_P(user) ? NULL : StringValuePtr(user); + args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass); + args.db = NIL_P(database) ? NULL : StringValuePtr(database); + args.mysql = client; + args.client_flag = 0; + + if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) + { + // unable to connect + return rb_raise_mysql2_error(client); + } + + return self; +} + +/* + * Immediately disconnect from the server, normally the garbage collector + * will disconnect automatically when a connection is no longer needed. + * Explicitly closing this will free up server resources sooner than waiting + * for the garbage collector. + */ +static VALUE rb_mysql_client_close(VALUE self) { + MYSQL *client; + + Data_Get_Struct(self, MYSQL, client); + + rb_thread_blocking_region(nogvl_close, client, RUBY_UBF_IO, 0); + + return Qnil; +} + +/* + * mysql_send_query is unlikely to block since most queries are small + * enough to fit in a socket buffer, but sometimes large UPDATE and + * INSERTs will cause the process to block + */ +static VALUE nogvl_send_query(void *ptr) { + struct nogvl_send_query_args *args = ptr; + int rv; + const char *sql = StringValuePtr(args->sql); + long sql_len = RSTRING_LEN(args->sql); + + rv = mysql_send_query(args->mysql, sql, sql_len); + + return rv == 0 ? Qtrue : Qfalse; +} + +/* + * even though we did rb_thread_select before calling this, a large + * response can overflow the socket buffers and cause us to eventually + * block while calling mysql_read_query_result + */ +static VALUE nogvl_read_query_result(void *ptr) { + MYSQL * client = ptr; + my_bool res = mysql_read_query_result(client); + + return res == 0 ? Qtrue : Qfalse; +} + +/* mysql_store_result may (unlikely) read rows off the socket */ +static VALUE nogvl_store_result(void *ptr) { + MYSQL * client = ptr; + return (VALUE)mysql_store_result(client); +} + +static VALUE rb_mysql_client_async_result(VALUE self) { + MYSQL * client; + MYSQL_RES * result; + + Data_Get_Struct(self, MYSQL, client); + + REQUIRE_OPEN_DB(client); + if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) { + return rb_raise_mysql2_error(client); + } + + result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0); + if (result == NULL) { + if (mysql_field_count(client) != 0) { + rb_raise_mysql2_error(client); + } + return Qnil; + } + + VALUE resultObj = rb_mysql_result_to_obj(result); + rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding")); + return resultObj; +} + +static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { + struct nogvl_send_query_args args; + fd_set fdset; + int fd, retval; + int async = 0; + VALUE opts; + VALUE rb_async; + + MYSQL * client; + + if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { + if ((rb_async = rb_hash_aref(opts, sym_async)) != Qnil) { + async = rb_async == Qtrue ? 1 : 0; + } + } + + Check_Type(args.sql, T_STRING); +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); + // ensure the string is in the encoding the connection is expecting + args.sql = rb_str_export_to_enc(args.sql, conn_enc); +#endif + + Data_Get_Struct(self, MYSQL, client); + + REQUIRE_OPEN_DB(client); + + args.mysql = client; + if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { + return rb_raise_mysql2_error(client); + } + + if (!async) { + // the below code is largely from do_mysql + // http://github.com/datamapper/do + fd = client->net.fd; + for(;;) { + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + + retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL); + + if (retval < 0) { + rb_sys_fail(0); + } + + if (retval > 0) { + break; + } + } + + return rb_mysql_client_async_result(self); + } else { + return Qnil; + } +} + +static VALUE rb_mysql_client_escape(VALUE self, VALUE str) { + MYSQL * client; + VALUE newStr; + unsigned long newLen, oldLen; + + Check_Type(str, T_STRING); +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); + // ensure the string is in the encoding the connection is expecting + str = rb_str_export_to_enc(str, conn_enc); +#endif + + oldLen = RSTRING_LEN(str); + char escaped[(oldLen*2)+1]; + + Data_Get_Struct(self, MYSQL, client); + + REQUIRE_OPEN_DB(client); + newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), RSTRING_LEN(str)); + if (newLen == oldLen) { + // no need to return a new ruby string if nothing changed + return str; + } else { + newStr = rb_str_new(escaped, newLen); +#ifdef HAVE_RUBY_ENCODING_H + rb_enc_associate(newStr, conn_enc); + if (default_internal_enc) { + newStr = rb_str_export_to_enc(newStr, default_internal_enc); + } +#endif + return newStr; + } +} + +static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE self) { + VALUE version = rb_hash_new(), client_info; +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); +#endif + + rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version())); + client_info = rb_str_new2(mysql_get_client_info()); +#ifdef HAVE_RUBY_ENCODING_H + rb_enc_associate(client_info, conn_enc); + if (default_internal_enc) { + client_info = rb_str_export_to_enc(client_info, default_internal_enc); + } +#endif + rb_hash_aset(version, sym_version, client_info); + return version; +} + +static VALUE rb_mysql_client_server_info(VALUE self) { + MYSQL * client; + VALUE version, server_info; +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); +#endif + + Data_Get_Struct(self, MYSQL, client); + REQUIRE_OPEN_DB(client); + + version = rb_hash_new(); + rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client))); + server_info = rb_str_new2(mysql_get_server_info(client)); +#ifdef HAVE_RUBY_ENCODING_H + rb_enc_associate(server_info, conn_enc); + if (default_internal_enc) { + server_info = rb_str_export_to_enc(server_info, default_internal_enc); + } +#endif + rb_hash_aset(version, sym_version, server_info); + return version; +} + +static VALUE rb_mysql_client_socket(VALUE self) { + MYSQL * client; + Data_Get_Struct(self, MYSQL, client); + REQUIRE_OPEN_DB(client); + return INT2NUM(client->net.fd); +} + +static VALUE rb_mysql_client_last_id(VALUE self) { + MYSQL * client; + Data_Get_Struct(self, MYSQL, client); + REQUIRE_OPEN_DB(client); + return ULL2NUM(mysql_insert_id(client)); +} + +static VALUE rb_mysql_client_affected_rows(VALUE self) { + MYSQL * client; + Data_Get_Struct(self, MYSQL, client); + REQUIRE_OPEN_DB(client); + return ULL2NUM(mysql_affected_rows(client)); +} + +static VALUE set_reconnect(VALUE self, VALUE value) { + my_bool reconnect; + MYSQL * client; + + Data_Get_Struct(self, MYSQL, client); + + if(!NIL_P(value)) { + reconnect = value == Qfalse ? 0 : 1; + + /* set default reconnect behavior */ + if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) { + /* TODO: warning - unable to set reconnect behavior */ + rb_warn("%s\n", mysql_error(client)); + } + } + return value; +} + +static VALUE set_connect_timeout(VALUE self, VALUE value) { + unsigned int connect_timeout = 0; + MYSQL * client; + + Data_Get_Struct(self, MYSQL, client); + + if(!NIL_P(value)) { + connect_timeout = NUM2INT(value); + if(0 == connect_timeout) return value; + + /* set default connection timeout behavior */ + if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) { + /* TODO: warning - unable to set connection timeout */ + rb_warn("%s\n", mysql_error(client)); + } + } + return value; +} + +static VALUE set_charset_name(VALUE self, VALUE value) { + char * charset_name; + MYSQL * client; + + Data_Get_Struct(self, MYSQL, client); + +#ifdef HAVE_RUBY_ENCODING_H + VALUE new_encoding, old_encoding; + new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value); + if (new_encoding == Qnil) { + rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(value)); + } else { + old_encoding = rb_iv_get(self, "@encoding"); + if (old_encoding == Qnil) { + rb_iv_set(self, "@encoding", new_encoding); + } + } +#endif + + charset_name = StringValuePtr(value); + + if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) { + /* TODO: warning - unable to set charset */ + rb_warn("%s\n", mysql_error(client)); + } + + return value; +} + +static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) { + MYSQL * client; + Data_Get_Struct(self, MYSQL, client); + + if(!NIL_P(ca) || !NIL_P(key)) { + mysql_ssl_set(client, + NIL_P(key) ? NULL : StringValuePtr(key), + NIL_P(cert) ? NULL : StringValuePtr(cert), + NIL_P(ca) ? NULL : StringValuePtr(ca), + NIL_P(capath) ? NULL : StringValuePtr(capath), + NIL_P(cipher) ? NULL : StringValuePtr(cipher)); + } + + return self; +} + +static VALUE init_connection(VALUE self) { + MYSQL * client; + Data_Get_Struct(self, MYSQL, client); + + if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) { + /* TODO: warning - not enough memory? */ + return rb_raise_mysql2_error(client); + } + + return self; +} + +/* call-seq: client.create_statement # => Mysql2::Statement + * + * Create a new prepared statement. + */ +static VALUE create_statement(VALUE self) { + MYSQL * client; + MYSQL_STMT * stmt; + + Data_Get_Struct(self, MYSQL, client); + stmt = mysql_stmt_init(client); + + return Data_Wrap_Struct(cMysql2Statement, 0, mysql_stmt_close, stmt); +} + +void init_mysql2_client() { + cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject); + + rb_define_alloc_func(cMysql2Client, allocate); + + rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); + rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1); + rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1); + rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0); + rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0); + rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0); + rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0); + rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0); + rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0); + rb_define_method(cMysql2Client, "create_statement", create_statement, 0); + + rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1); + rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); + rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1); + rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); + rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0); + rb_define_private_method(cMysql2Client, "connect", rb_connect, 6); +} \ No newline at end of file diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h new file mode 100644 index 0000000..a962303 --- /dev/null +++ b/ext/mysql2/client.h @@ -0,0 +1,34 @@ +#ifndef MYSQL2_CLIENT_H +#define MYSQL2_CLIENT_H + +/* + * partial emulation of the 1.9 rb_thread_blocking_region under 1.8, + * this is enough for dealing with blocking I/O functions in the + * presence of threads. + */ +#ifndef HAVE_RB_THREAD_BLOCKING_REGION + +#include +#define RUBY_UBF_IO ((rb_unblock_function_t *)-1) +typedef void rb_unblock_function_t(void *); +typedef VALUE rb_blocking_function_t(void *); +static VALUE +rb_thread_blocking_region( + rb_blocking_function_t *func, void *data1, + RB_MYSQL_UNUSED rb_unblock_function_t *ubf, + RB_MYSQL_UNUSED void *data2) +{ + VALUE rv; + + TRAP_BEG; + rv = func(data1); + TRAP_END; + + return rv; +} + +#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ + +void init_mysql2_client(); + +#endif \ No newline at end of file diff --git a/ext/mysql2/mysql2_ext.c b/ext/mysql2/mysql2_ext.c index 0eeb11c..2e5c589 100644 --- a/ext/mysql2/mysql2_ext.c +++ b/ext/mysql2/mysql2_ext.c @@ -1,464 +1,7 @@ #include -VALUE mMysql2, cMysql2Client; -VALUE cMysql2Error, intern_encoding_from_charset; -ID sym_id, sym_version, sym_async; - -#define REQUIRE_OPEN_DB(_ctxt) \ - if(!_ctxt->net.vio) { \ - rb_raise(cMysql2Error, "closed MySQL connection"); \ - return Qnil; \ - } - -/* - * non-blocking mysql_*() functions that we won't be wrapping since - * they do not appear to hit the network nor issue any interruptible - * or blocking system calls. - * - * - mysql_affected_rows() - * - mysql_error() - * - mysql_fetch_fields() - * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths - * - mysql_field_count() - * - mysql_get_client_info() - * - mysql_get_client_version() - * - mysql_get_server_info() - * - mysql_get_server_version() - * - mysql_insert_id() - * - mysql_num_fields() - * - mysql_num_rows() - * - mysql_options() - * - mysql_real_escape_string() - * - mysql_ssl_set() - */ - -static VALUE rb_raise_mysql2_error(MYSQL *client) { - VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client)); - rb_funcall(e, rb_intern("error_number="), 1, INT2NUM(mysql_errno(client))); - rb_funcall(e, rb_intern("sql_state="), 1, rb_tainted_str_new2(mysql_sqlstate(client))); - rb_exc_raise(e); - return Qnil; -} - -static VALUE nogvl_init(void *ptr) { - MYSQL * client = (MYSQL *)ptr; - - /* may initialize embedded server and read /etc/services off disk */ - mysql_init(client); - - return client ? Qtrue : Qfalse; -} - -static VALUE nogvl_connect(void *ptr) { - struct nogvl_connect_args *args = ptr; - MYSQL *client; - - client = mysql_real_connect(args->mysql, args->host, - args->user, args->passwd, - args->db, args->port, args->unix_socket, - args->client_flag); - - return client ? Qtrue : Qfalse; -} - -static void rb_mysql_client_free(void * ptr) { - MYSQL * client = (MYSQL *)ptr; - - /* - * we'll send a QUIT message to the server, but that message is more of a - * formality than a hard requirement since the socket is getting shutdown - * anyways, so ensure the socket write does not block our interpreter - */ - int fd = client->net.fd; - int flags; - - if (fd >= 0) { - /* - * if the socket is dead we have no chance of blocking, - * so ignore any potential fcntl errors since they don't matter - */ - flags = fcntl(fd, F_GETFL); - if (flags > 0 && !(flags & O_NONBLOCK)) - fcntl(fd, F_SETFL, flags | O_NONBLOCK); - } - - /* It's safe to call mysql_close() on an already closed connection. */ - mysql_close(client); - xfree(ptr); -} - -static VALUE nogvl_close(void * ptr) { - mysql_close((MYSQL *)ptr); - return Qnil; -} - -static VALUE allocate(VALUE klass) -{ - MYSQL * client; - - return Data_Make_Struct( - klass, - MYSQL, - NULL, - rb_mysql_client_free, - client - ); -} - -static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket) -{ - MYSQL * client; - struct nogvl_connect_args args; - - Data_Get_Struct(self, MYSQL, client); - - args.host = NIL_P(host) ? "localhost" : StringValuePtr(host); - args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); - args.port = NIL_P(port) ? 3306 : NUM2INT(port); - args.user = NIL_P(user) ? NULL : StringValuePtr(user); - args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass); - args.db = NIL_P(database) ? NULL : StringValuePtr(database); - args.mysql = client; - args.client_flag = 0; - - if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) - { - // unable to connect - return rb_raise_mysql2_error(client); - } - - return self; -} - -/* - * Immediately disconnect from the server, normally the garbage collector - * will disconnect automatically when a connection is no longer needed. - * Explicitly closing this will free up server resources sooner than waiting - * for the garbage collector. - */ -static VALUE rb_mysql_client_close(VALUE self) { - MYSQL *client; - - Data_Get_Struct(self, MYSQL, client); - - REQUIRE_OPEN_DB(client); - rb_thread_blocking_region(nogvl_close, client, RUBY_UBF_IO, 0); - - return Qnil; -} - -/* - * mysql_send_query is unlikely to block since most queries are small - * enough to fit in a socket buffer, but sometimes large UPDATE and - * INSERTs will cause the process to block - */ -static VALUE nogvl_send_query(void *ptr) { - struct nogvl_send_query_args *args = ptr; - int rv; - const char *sql = StringValuePtr(args->sql); - long sql_len = RSTRING_LEN(args->sql); - - rv = mysql_send_query(args->mysql, sql, sql_len); - - return rv == 0 ? Qtrue : Qfalse; -} - -/* - * even though we did rb_thread_select before calling this, a large - * response can overflow the socket buffers and cause us to eventually - * block while calling mysql_read_query_result - */ -static VALUE nogvl_read_query_result(void *ptr) { - MYSQL * client = ptr; - my_bool res = mysql_read_query_result(client); - - return res == 0 ? Qtrue : Qfalse; -} - -/* mysql_store_result may (unlikely) read rows off the socket */ -static VALUE nogvl_store_result(void *ptr) { - MYSQL * client = ptr; - return (VALUE)mysql_store_result(client); -} - -static VALUE rb_mysql_client_async_result(VALUE self) { - MYSQL * client; - MYSQL_RES * result; - - Data_Get_Struct(self, MYSQL, client); - - REQUIRE_OPEN_DB(client); - if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) { - return rb_raise_mysql2_error(client); - } - - result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0); - if (result == NULL) { - if (mysql_field_count(client) != 0) { - rb_raise_mysql2_error(client); - } - return Qnil; - } - - VALUE resultObj = rb_mysql_result_to_obj(result); - rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding")); - return resultObj; -} - -static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { - struct nogvl_send_query_args args; - fd_set fdset; - int fd, retval; - int async = 0; - VALUE opts; - VALUE rb_async; - - MYSQL * client; - - if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { - if ((rb_async = rb_hash_aref(opts, sym_async)) != Qnil) { - async = rb_async == Qtrue ? 1 : 0; - } - } - - Check_Type(args.sql, T_STRING); -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); - // ensure the string is in the encoding the connection is expecting - args.sql = rb_str_export_to_enc(args.sql, conn_enc); -#endif - - Data_Get_Struct(self, MYSQL, client); - - REQUIRE_OPEN_DB(client); - - args.mysql = client; - if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { - return rb_raise_mysql2_error(client); - } - - if (!async) { - // the below code is largely from do_mysql - // http://github.com/datamapper/do - fd = client->net.fd; - for(;;) { - FD_ZERO(&fdset); - FD_SET(fd, &fdset); - - retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL); - - if (retval < 0) { - rb_sys_fail(0); - } - - if (retval > 0) { - break; - } - } - - return rb_mysql_client_async_result(self); - } else { - return Qnil; - } -} - -static VALUE rb_mysql_client_escape(VALUE self, VALUE str) { - MYSQL * client; - VALUE newStr; - unsigned long newLen, oldLen; - - Check_Type(str, T_STRING); -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *default_internal_enc = rb_default_internal_encoding(); - rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); - // ensure the string is in the encoding the connection is expecting - str = rb_str_export_to_enc(str, conn_enc); -#endif - - oldLen = RSTRING_LEN(str); - char escaped[(oldLen*2)+1]; - - Data_Get_Struct(self, MYSQL, client); - - REQUIRE_OPEN_DB(client); - newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), RSTRING_LEN(str)); - if (newLen == oldLen) { - // no need to return a new ruby string if nothing changed - return str; - } else { - newStr = rb_str_new(escaped, newLen); -#ifdef HAVE_RUBY_ENCODING_H - rb_enc_associate(newStr, conn_enc); - if (default_internal_enc) { - newStr = rb_str_export_to_enc(newStr, default_internal_enc); - } -#endif - return newStr; - } -} - -static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE self) { - VALUE version = rb_hash_new(), client_info; -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *default_internal_enc = rb_default_internal_encoding(); - rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); -#endif - - rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version())); - client_info = rb_str_new2(mysql_get_client_info()); -#ifdef HAVE_RUBY_ENCODING_H - rb_enc_associate(client_info, conn_enc); - if (default_internal_enc) { - client_info = rb_str_export_to_enc(client_info, default_internal_enc); - } -#endif - rb_hash_aset(version, sym_version, client_info); - return version; -} - -static VALUE rb_mysql_client_server_info(VALUE self) { - MYSQL * client; - VALUE version, server_info; -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *default_internal_enc = rb_default_internal_encoding(); - rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); -#endif - - Data_Get_Struct(self, MYSQL, client); - REQUIRE_OPEN_DB(client); - - version = rb_hash_new(); - rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client))); - server_info = rb_str_new2(mysql_get_server_info(client)); -#ifdef HAVE_RUBY_ENCODING_H - rb_enc_associate(server_info, conn_enc); - if (default_internal_enc) { - server_info = rb_str_export_to_enc(server_info, default_internal_enc); - } -#endif - rb_hash_aset(version, sym_version, server_info); - return version; -} - -static VALUE rb_mysql_client_socket(VALUE self) { - MYSQL * client; - Data_Get_Struct(self, MYSQL, client); - REQUIRE_OPEN_DB(client); - return INT2NUM(client->net.fd); -} - -static VALUE rb_mysql_client_last_id(VALUE self) { - MYSQL * client; - Data_Get_Struct(self, MYSQL, client); - REQUIRE_OPEN_DB(client); - return ULL2NUM(mysql_insert_id(client)); -} - -static VALUE rb_mysql_client_affected_rows(VALUE self) { - MYSQL * client; - Data_Get_Struct(self, MYSQL, client); - REQUIRE_OPEN_DB(client); - return ULL2NUM(mysql_affected_rows(client)); -} - -static VALUE set_reconnect(VALUE self, VALUE value) -{ - my_bool reconnect; - MYSQL * client; - - Data_Get_Struct(self, MYSQL, client); - - if(!NIL_P(value)) { - reconnect = value == Qfalse ? 0 : 1; - - /* set default reconnect behavior */ - if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) { - /* TODO: warning - unable to set reconnect behavior */ - rb_warn("%s\n", mysql_error(client)); - } - } - return value; -} - -static VALUE set_connect_timeout(VALUE self, VALUE value) -{ - unsigned int connect_timeout = 0; - MYSQL * client; - - Data_Get_Struct(self, MYSQL, client); - - if(!NIL_P(value)) { - connect_timeout = NUM2INT(value); - if(0 == connect_timeout) return value; - - /* set default connection timeout behavior */ - if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) { - /* TODO: warning - unable to set connection timeout */ - rb_warn("%s\n", mysql_error(client)); - } - } - return value; -} - -static VALUE set_charset_name(VALUE self, VALUE value) -{ - char * charset_name; - MYSQL * client; - - Data_Get_Struct(self, MYSQL, client); - -#ifdef HAVE_RUBY_ENCODING_H - VALUE new_encoding, old_encoding; - new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value); - if (new_encoding == Qnil) { - rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(value)); - } else { - old_encoding = rb_iv_get(self, "@encoding"); - if (old_encoding == Qnil) { - rb_iv_set(self, "@encoding", new_encoding); - } - } -#endif - - charset_name = StringValuePtr(value); - - if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) { - /* TODO: warning - unable to set charset */ - rb_warn("%s\n", mysql_error(client)); - } - - return value; -} - -static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) -{ - MYSQL * client; - Data_Get_Struct(self, MYSQL, client); - - if(!NIL_P(ca) || !NIL_P(key)) { - mysql_ssl_set(client, - NIL_P(key) ? NULL : StringValuePtr(key), - NIL_P(cert) ? NULL : StringValuePtr(cert), - NIL_P(ca) ? NULL : StringValuePtr(ca), - NIL_P(capath) ? NULL : StringValuePtr(capath), - NIL_P(cipher) ? NULL : StringValuePtr(cipher)); - } - - return self; -} - -static VALUE init_connection(VALUE self) -{ - MYSQL * client; - Data_Get_Struct(self, MYSQL, client); - - if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) { - /* TODO: warning - not enough memory? */ - return rb_raise_mysql2_error(client); - } - - return self; -} +VALUE mMysql2, cMysql2Error, intern_encoding_from_charset; +ID sym_id, sym_version, sym_async; /* call-seq: client.create_statement # => Mysql2::Statement * @@ -477,37 +20,16 @@ static VALUE create_statement(VALUE self) /* Ruby Extension initializer */ void Init_mysql2() { - mMysql2 = rb_define_module("Mysql2"); - cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject); - - rb_define_alloc_func(cMysql2Client, allocate); - - rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); - rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1); - rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1); - rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0); - rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0); - rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0); - rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0); - rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0); - rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0); - rb_define_method(cMysql2Client, "create_statement", create_statement, 0); - - rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1); - rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); - rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1); - rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); - rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0); - rb_define_private_method(cMysql2Client, "connect", rb_connect, 6); - + mMysql2 = rb_define_module("Mysql2"); cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); - init_mysql2_result(); - init_mysql2_statement(); - - sym_id = ID2SYM(rb_intern("id")); - sym_version = ID2SYM(rb_intern("version")); - sym_async = ID2SYM(rb_intern("async")); + sym_id = ID2SYM(rb_intern("id")); + sym_version = ID2SYM(rb_intern("version")); + sym_async = ID2SYM(rb_intern("async")); intern_encoding_from_charset = rb_intern("encoding_from_charset"); + + init_mysql2_client(); + init_mysql2_result(); + init_mysql2_statement(); } diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index d2a915e..ecdc546 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -26,74 +26,8 @@ #define RB_MYSQL_UNUSED #endif +#include #include #include -extern VALUE mMysql2; - -/* Mysql2::Error */ -extern VALUE cMysql2Error; - -/* Mysql2::Result */ -typedef struct { - VALUE fields; - VALUE rows; - unsigned int numberOfFields; - unsigned long numberOfRows; - unsigned long lastRowProcessed; - short int resultFreed; - MYSQL_RES *result; -} mysql2_result_wrapper; -#define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj)); - -/* - * used to pass all arguments to mysql_real_connect while inside - * rb_thread_blocking_region - */ -struct nogvl_connect_args { - MYSQL *mysql; - const char *host; - const char *user; - const char *passwd; - const char *db; - unsigned int port; - const char *unix_socket; - unsigned long client_flag; -}; - -/* - * used to pass all arguments to mysql_send_query while inside - * rb_thread_blocking_region - */ -struct nogvl_send_query_args { - MYSQL *mysql; - VALUE sql; -}; - -/* - * partial emulation of the 1.9 rb_thread_blocking_region under 1.8, - * this is enough for dealing with blocking I/O functions in the - * presence of threads. - */ -#ifndef HAVE_RB_THREAD_BLOCKING_REGION -# include -# define RUBY_UBF_IO ((rb_unblock_function_t *)-1) -typedef void rb_unblock_function_t(void *); -typedef VALUE rb_blocking_function_t(void *); -static VALUE -rb_thread_blocking_region( - rb_blocking_function_t *func, void *data1, - RB_MYSQL_UNUSED rb_unblock_function_t *ubf, - RB_MYSQL_UNUSED void *data2) -{ - VALUE rv; - - TRAP_BEG; - rv = func(data1); - TRAP_END; - - return rv; -} -#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ - #endif diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 5b12b87..6a2df6c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -7,16 +7,16 @@ rb_encoding *binaryEncoding; ID sym_symbolize_keys; ID intern_new, intern_utc, intern_encoding_from_charset_code; -VALUE cBigDecimal, cDate, cDateTime; VALUE cMysql2Result; -extern VALUE cMysql2Client; +VALUE cBigDecimal, cDate, cDateTime; +extern VALUE mMysql2, cMysql2Client, cMysql2Error, intern_encoding_from_charset; static void rb_mysql_result_mark(void * wrapper) { - mysql2_result_wrapper * w = wrapper; - if (w) { - rb_gc_mark(w->fields); - rb_gc_mark(w->rows); - } + mysql2_result_wrapper * w = wrapper; + if (w) { + rb_gc_mark(w->fields); + rb_gc_mark(w->rows); + } } /* this may be called manually or during GC */ diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 3b9eeab..c466a7c 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -4,4 +4,16 @@ void init_mysql2_result(); VALUE rb_mysql_result_to_obj(MYSQL_RES * r); +typedef struct { + VALUE fields; + VALUE rows; + unsigned int numberOfFields; + unsigned long numberOfRows; + unsigned long lastRowProcessed; + short int resultFreed; + MYSQL_RES *result; +} mysql2_result_wrapper; + +#define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj)); + #endif diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 456c97d..6332459 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -1,6 +1,7 @@ #include VALUE cMysql2Statement; +extern VALUE mMysql2, cMysql2Error; /* call-seq: stmt.prepare(sql) * diff --git a/lib/active_record/connection_adapters/em_mysql2_adapter.rb b/lib/active_record/connection_adapters/em_mysql2_adapter.rb index 0077e13..e26c9df 100644 --- a/lib/active_record/connection_adapters/em_mysql2_adapter.rb +++ b/lib/active_record/connection_adapters/em_mysql2_adapter.rb @@ -42,17 +42,21 @@ module Mysql2 end def query(sql, opts={}) - super(sql, opts.merge(:async => true)) - deferable = ::EM::DefaultDeferrable.new - ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true - fiber = Fiber.current - deferable.callback do |result| - fiber.resume(result) + if EM.reactor_running? + super(sql, opts.merge(:async => true)) + deferable = ::EM::DefaultDeferrable.new + ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true + fiber = Fiber.current + deferable.callback do |result| + fiber.resume(result) + end + deferable.errback do |err| + fiber.resume(err) + end + Fiber.yield + else + super(sql, opts) end - deferable.errback do |err| - fiber.resume(err) - end - Fiber.yield end end end diff --git a/lib/active_record/connection_adapters/mysql2_adapter.rb b/lib/active_record/connection_adapters/mysql2_adapter.rb index 4e2e357..13e847a 100644 --- a/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -14,7 +14,7 @@ module ActiveRecord module ConnectionAdapters class Mysql2Column < Column - BOOL = "tinyint(1)".freeze + BOOL = "tinyint(1)" def extract_default(default) if sql_type =~ /blob/i || type == :text if default.blank? @@ -86,8 +86,9 @@ module ActiveRecord private def simplified_type(field_type) return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL) - return :string if field_type =~ /enum/i + return :string if field_type =~ /enum/i or field_type =~ /set/i return :integer if field_type =~ /year/i + return :binary if field_type =~ /bit/i super end @@ -130,8 +131,8 @@ module ActiveRecord cattr_accessor :emulate_booleans self.emulate_booleans = true - ADAPTER_NAME = 'MySQL'.freeze - PRIMARY = "PRIMARY".freeze + ADAPTER_NAME = 'MySQL2' + PRIMARY = "PRIMARY" LOST_CONNECTION_ERROR_MESSAGES = [ "Server shutdown in progress", @@ -139,10 +140,10 @@ module ActiveRecord "Lost connection to MySQL server during query", "MySQL server has gone away" ] - QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze + QUOTED_TRUE, QUOTED_FALSE = '1', '0' NATIVE_DATABASE_TYPES = { - :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze, + :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", :string => { :name => "varchar", :limit => 255 }, :text => { :name => "text" }, :integer => { :name => "int", :limit => 4 }, @@ -231,6 +232,7 @@ module ActiveRecord # CONNECTION MANAGEMENT ==================================== def active? + return false unless @connection @connection.query 'select 1' true rescue Mysql2::Error @@ -238,7 +240,13 @@ module ActiveRecord end def reconnect! - reset! + disconnect! + connect + end + + # this is set to true in 2.3, but we don't want it to be + def requires_reloading? + false end def disconnect! @@ -250,7 +258,7 @@ module ActiveRecord def reset! disconnect! - @connection = Mysql2::Client.new(@config) + connect end # DATABASE STATEMENTS ====================================== @@ -263,7 +271,6 @@ module ActiveRecord select(sql, name).map { |row| row.values } end - # Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it. def execute(sql, name = nil) if name == :skip_logging @connection.query(sql) @@ -547,7 +554,6 @@ module ActiveRecord end end - # TODO: implement error_number method on Mysql2::Exception def translate_exception(exception, message) return super unless exception.respond_to?(:error_number) @@ -563,7 +569,8 @@ module ActiveRecord private def connect - # no-op + @connection = Mysql2::Client.new(@config) + configure_connection end def configure_connection @@ -595,4 +602,4 @@ module ActiveRecord end end end -end \ No newline at end of file +end diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 9e159f8..1d0ef4b 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -12,5 +12,5 @@ require 'mysql2/field' # # A modern, simple and very fast Mysql library for Ruby - binding to libmysql module Mysql2 - VERSION = "0.1.8" + VERSION = "0.1.9" end diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index e49ed4b..d80bb89 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -8,6 +8,7 @@ module Mysql2 send(:"#{key}=", opts[key]) end # force the encoding to utf8 + @encoding = nil self.charset_name = opts[:encoding] || 'utf8' ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslciper)) diff --git a/lib/sequel/adapters/mysql2.rb b/lib/sequel/adapters/mysql2.rb index 09d49f1..26b74bf 100644 --- a/lib/sequel/adapters/mysql2.rb +++ b/lib/sequel/adapters/mysql2.rb @@ -120,8 +120,7 @@ module Sequel # yield the connection if a block is given. def _execute(conn, sql, opts) begin - # r = log_yield(sql){conn.query(sql)} - r = conn.query(sql) + r = log_yield(sql){conn.query(sql)} if opts[:type] == :select yield r if r elsif block_given? diff --git a/mysql2.gemspec b/mysql2.gemspec index 6b6d12c..1d67599 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{mysql2} - s.version = "0.1.8" + s.version = "0.1.9" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brian Lopez"] - s.date = %q{2010-07-06} + s.date = %q{2010-07-17} s.email = %q{seniorlopez@gmail.com} s.extensions = ["ext/mysql2/extconf.rb"] s.extra_rdoc_files = [ @@ -34,7 +34,9 @@ Gem::Specification.new do |s| "ext/mysql2/mysql2_ext.h", "ext/mysql2/result.c", "ext/mysql2/result.h", + "lib/active_record/connection_adapters/em_mysql2_adapter.rb", "lib/active_record/connection_adapters/mysql2_adapter.rb", + "lib/active_record/fiber_patches.rb", "lib/arel/engines/sql/compilers/mysql2_compiler.rb", "lib/mysql2.rb", "lib/mysql2/client.rb", diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7165f48..7898390 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -38,26 +38,19 @@ describe Mysql2::Client do end it "should respond to #close" do - @client.should respond_to :close + @client.should respond_to(:close) end it "should be able to close properly" do @client.close.should be_nil end - it "should raise an exception when closed twice" do - @client.close.should be_nil - lambda { - @client.close - }.should raise_error(Mysql2::Error) - end - it "should respond to #query" do - @client.should respond_to :query + @client.should respond_to(:query) end it "should respond to #escape" do - @client.should respond_to :escape + @client.should respond_to(:escape) end it "#escape should return a new SQL-escape version of the passed string" do @@ -70,7 +63,7 @@ describe Mysql2::Client do end it "should respond to #info" do - @client.should respond_to :info + @client.should respond_to(:info) end it "#info should return a hash containing the client version ID and String" do @@ -102,7 +95,7 @@ describe Mysql2::Client do end it "should respond to #server_info" do - @client.should respond_to :server_info + @client.should respond_to(:server_info) end it "#server_info should return a hash containing the client version ID and String" do @@ -134,7 +127,7 @@ describe Mysql2::Client do end it "should respond to #socket" do - @client.should respond_to :socket + @client.should respond_to(:socket) end it "#socket should return a Fixnum (file descriptor from C)" do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 2fa0bb8..900101a 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -15,7 +15,7 @@ describe Mysql2::Result do end it "should respond to #each" do - @result.should respond_to :each + @result.should respond_to(:each) end it "should raise a Mysql2::Error exception upon a bad query" do