Merge branch 'master' into stmt
This commit is contained in:
commit
ee4fd98611
52
README.rdoc
52
README.rdoc
@ -58,6 +58,52 @@ How about with symbolized keys?
|
|||||||
# do something with row, it's ready to rock
|
# do something with row, it's ready to rock
|
||||||
end
|
end
|
||||||
|
|
||||||
|
== Cascading config
|
||||||
|
|
||||||
|
The default config hash is at:
|
||||||
|
|
||||||
|
Mysql2::Client.default_query_options
|
||||||
|
|
||||||
|
which defaults to:
|
||||||
|
|
||||||
|
{:async => false, :as => :hash, :symbolize_keys => false}
|
||||||
|
|
||||||
|
that can be used as so:
|
||||||
|
|
||||||
|
# these are the defaults all Mysql2::Client instances inherit
|
||||||
|
Mysql2::Client.default_query_options.merge!(:as => :array)
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
# this will change the defaults for all future results returned by the #query method _for this connection only_
|
||||||
|
c = Mysql2::Client.new
|
||||||
|
c.query_options.merge!(:symbolize_keys => true)
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
# this will set the options for the Mysql2::Result instance returned from the #query method
|
||||||
|
c = Mysql2::Client.new
|
||||||
|
c.query(sql, :symbolize_keys => true)
|
||||||
|
|
||||||
|
== Result types
|
||||||
|
|
||||||
|
=== Array of Arrays
|
||||||
|
|
||||||
|
Pass the {:as => :array} option to any of the above methods of configuration
|
||||||
|
|
||||||
|
=== Array of Hashes
|
||||||
|
|
||||||
|
The default result type is set to :hash, but you can override a previous setting to something else with {:as => :hash}
|
||||||
|
|
||||||
|
=== Others...
|
||||||
|
|
||||||
|
I may add support for {:as => :csv} or even {:as => :json} to allow for *much* more efficient generation of those data types from result sets.
|
||||||
|
If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
|
||||||
|
|
||||||
|
== Timezones
|
||||||
|
|
||||||
|
You can set the :timezone option to :local or :utc to tell Mysql2 which timezone you'd like to have Time objects in
|
||||||
|
|
||||||
== Async
|
== Async
|
||||||
|
|
||||||
Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
|
Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
|
||||||
@ -151,11 +197,11 @@ then iterating over every row using an #each like method yielding a block:
|
|||||||
# These results are from the query_with_mysql_casting.rb script in the benchmarks folder
|
# These results are from the query_with_mysql_casting.rb script in the benchmarks folder
|
||||||
user system total real
|
user system total real
|
||||||
Mysql2
|
Mysql2
|
||||||
0.890000 0.190000 1.080000 ( 2.028887)
|
0.750000 0.180000 0.930000 ( 1.821655)
|
||||||
do_mysql
|
do_mysql
|
||||||
1.740000 0.220000 1.960000 ( 2.909290)
|
1.650000 0.200000 1.850000 ( 2.811357)
|
||||||
Mysql
|
Mysql
|
||||||
7.330000 0.350000 7.680000 ( 8.013160)
|
7.500000 0.210000 7.710000 ( 8.065871)
|
||||||
|
|
||||||
== Special Thanks
|
== Special Thanks
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ require 'rubygems'
|
|||||||
require 'benchmark'
|
require 'benchmark'
|
||||||
require 'active_record'
|
require 'active_record'
|
||||||
|
|
||||||
ActiveRecord::Base.default_timezone = 'Pacific Time (US & Canada)'
|
ActiveRecord::Base.default_timezone = :local
|
||||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||||
|
|
||||||
number_of = 10
|
number_of = 10
|
||||||
|
@ -45,8 +45,8 @@ Benchmark.bmbm do |x|
|
|||||||
x.report do
|
x.report do
|
||||||
puts "Mysql2"
|
puts "Mysql2"
|
||||||
number_of.times do
|
number_of.times do
|
||||||
mysql2_result = mysql2.query sql
|
mysql2_result = mysql2.query sql, :symbolize_keys => true
|
||||||
mysql2_result.each(:symbolize_keys => true) do |res|
|
mysql2_result.each do |res|
|
||||||
# puts res.inspect
|
# puts res.inspect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -17,8 +17,8 @@ Benchmark.bmbm do |x|
|
|||||||
x.report do
|
x.report do
|
||||||
puts "Mysql2"
|
puts "Mysql2"
|
||||||
number_of.times do
|
number_of.times do
|
||||||
mysql2_result = mysql2.query sql
|
mysql2_result = mysql2.query sql, :symbolize_keys => true
|
||||||
mysql2_result.each(:symbolize_keys => true) do |res|
|
mysql2_result.each do |res|
|
||||||
# puts res.inspect
|
# puts res.inspect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
VALUE cMysql2Client;
|
VALUE cMysql2Client;
|
||||||
extern VALUE mMysql2, cMysql2Error, intern_encoding_from_charset;
|
extern VALUE mMysql2, cMysql2Error, intern_encoding_from_charset;
|
||||||
extern ID sym_id, sym_version, sym_async;
|
extern ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
|
||||||
|
extern ID intern_merge;
|
||||||
|
|
||||||
#define REQUIRE_OPEN_DB(_ctxt) \
|
#define REQUIRE_OPEN_DB(_ctxt) \
|
||||||
if(!_ctxt->net.vio) { \
|
if(!_ctxt->net.vio) { \
|
||||||
@ -225,7 +226,12 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VALUE resultObj = rb_mysql_result_to_obj(result);
|
VALUE resultObj = rb_mysql_result_to_obj(result);
|
||||||
|
// pass-through query options for result construction later
|
||||||
|
rb_iv_set(resultObj, "@query_options", rb_obj_dup(rb_iv_get(self, "@query_options")));
|
||||||
|
|
||||||
|
#ifdef HAVE_RUBY_ENCODING_H
|
||||||
rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding"));
|
rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding"));
|
||||||
|
#endif
|
||||||
return resultObj;
|
return resultObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,29 +240,31 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|||||||
fd_set fdset;
|
fd_set fdset;
|
||||||
int fd, retval;
|
int fd, retval;
|
||||||
int async = 0;
|
int async = 0;
|
||||||
VALUE opts;
|
VALUE opts, defaults;
|
||||||
VALUE rb_async;
|
|
||||||
|
|
||||||
MYSQL *client;
|
MYSQL *client;
|
||||||
|
|
||||||
|
Data_Get_Struct(self, MYSQL, client);
|
||||||
|
REQUIRE_OPEN_DB(client);
|
||||||
|
args.mysql = client;
|
||||||
|
|
||||||
|
defaults = rb_iv_get(self, "@query_options");
|
||||||
if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
|
if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
|
||||||
if ((rb_async = rb_hash_aref(opts, sym_async)) != Qnil) {
|
opts = rb_funcall(defaults, intern_merge, 1, opts);
|
||||||
async = rb_async == Qtrue ? 1 : 0;
|
rb_iv_set(self, "@query_options", opts);
|
||||||
|
|
||||||
|
if (rb_hash_aref(opts, sym_async) == Qtrue) {
|
||||||
|
async = 1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
opts = defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
Check_Type(args.sql, T_STRING);
|
|
||||||
#ifdef HAVE_RUBY_ENCODING_H
|
#ifdef HAVE_RUBY_ENCODING_H
|
||||||
rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
|
rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
|
||||||
// ensure the string is in the encoding the connection is expecting
|
// ensure the string is in the encoding the connection is expecting
|
||||||
args.sql = rb_str_export_to_enc(args.sql, conn_enc);
|
args.sql = rb_str_export_to_enc(args.sql, conn_enc);
|
||||||
#endif
|
#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) {
|
if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
||||||
return rb_raise_mysql2_error(client);
|
return rb_raise_mysql2_error(client);
|
||||||
}
|
}
|
||||||
@ -280,7 +288,12 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rb_mysql_client_async_result(self);
|
VALUE result = rb_mysql_client_async_result(self);
|
||||||
|
|
||||||
|
// pass-through query options for result construction later
|
||||||
|
rb_iv_set(result, "@query_options", rb_obj_dup(opts));
|
||||||
|
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#include <mysql2_ext.h>
|
#include <mysql2_ext.h>
|
||||||
|
|
||||||
VALUE mMysql2, cMysql2Error, intern_encoding_from_charset;
|
VALUE mMysql2, cMysql2Error, intern_encoding_from_charset;
|
||||||
ID sym_id, sym_version, sym_async;
|
ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as,
|
||||||
|
sym_array, sym_timezone, sym_utc, sym_local;
|
||||||
|
ID intern_merge;
|
||||||
|
|
||||||
/* call-seq: client.create_statement # => Mysql2::Statement
|
/* call-seq: client.create_statement # => Mysql2::Statement
|
||||||
*
|
*
|
||||||
@ -23,9 +25,17 @@ void Init_mysql2() {
|
|||||||
mMysql2 = rb_define_module("Mysql2");
|
mMysql2 = rb_define_module("Mysql2");
|
||||||
cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
|
cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
|
||||||
|
|
||||||
|
intern_merge = rb_intern("merge");
|
||||||
|
|
||||||
|
sym_timezone = ID2SYM(rb_intern("timezone"));
|
||||||
|
sym_utc = ID2SYM(rb_intern("utc"));
|
||||||
|
sym_local = ID2SYM(rb_intern("local"));
|
||||||
|
sym_array = ID2SYM(rb_intern("array"));
|
||||||
|
sym_as = ID2SYM(rb_intern("as"));
|
||||||
sym_id = ID2SYM(rb_intern("id"));
|
sym_id = ID2SYM(rb_intern("id"));
|
||||||
sym_version = ID2SYM(rb_intern("version"));
|
sym_version = ID2SYM(rb_intern("version"));
|
||||||
sym_async = ID2SYM(rb_intern("async"));
|
sym_async = ID2SYM(rb_intern("async"));
|
||||||
|
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
||||||
|
|
||||||
intern_encoding_from_charset = rb_intern("encoding_from_charset");
|
intern_encoding_from_charset = rb_intern("encoding_from_charset");
|
||||||
|
|
||||||
|
@ -4,12 +4,13 @@
|
|||||||
rb_encoding *binaryEncoding;
|
rb_encoding *binaryEncoding;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ID sym_symbolize_keys;
|
ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code;
|
||||||
ID intern_new, intern_utc, intern_encoding_from_charset_code;
|
|
||||||
|
|
||||||
VALUE cMysql2Result;
|
VALUE cMysql2Result;
|
||||||
VALUE cBigDecimal, cDate, cDateTime;
|
VALUE cBigDecimal, cDate, cDateTime;
|
||||||
extern VALUE mMysql2, cMysql2Client, cMysql2Error, intern_encoding_from_charset;
|
extern VALUE mMysql2, cMysql2Client, cMysql2Error, intern_encoding_from_charset;
|
||||||
|
extern ID sym_symbolize_keys, sym_as, sym_array, sym_timezone, sym_local, sym_utc;
|
||||||
|
extern ID intern_merge;
|
||||||
|
|
||||||
static void rb_mysql_result_mark(void * wrapper) {
|
static void rb_mysql_result_mark(void * wrapper) {
|
||||||
mysql2_result_wrapper * w = wrapper;
|
mysql2_result_wrapper * w = wrapper;
|
||||||
@ -85,12 +86,12 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
|
|||||||
return rb_field;
|
return rb_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKeys, int asArray) {
|
||||||
VALUE rowHash, opts, block;
|
VALUE rowVal;
|
||||||
mysql2_result_wrapper * wrapper;
|
mysql2_result_wrapper * wrapper;
|
||||||
MYSQL_ROW row;
|
MYSQL_ROW row;
|
||||||
MYSQL_FIELD * fields = NULL;
|
MYSQL_FIELD * fields = NULL;
|
||||||
unsigned int i = 0, symbolizeKeys = 0;
|
unsigned int i = 0;
|
||||||
unsigned long * fieldLengths;
|
unsigned long * fieldLengths;
|
||||||
void * ptr;
|
void * ptr;
|
||||||
#ifdef HAVE_RUBY_ENCODING_H
|
#ifdef HAVE_RUBY_ENCODING_H
|
||||||
@ -100,20 +101,17 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|||||||
|
|
||||||
GetMysql2Result(self, wrapper);
|
GetMysql2Result(self, wrapper);
|
||||||
|
|
||||||
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
|
|
||||||
Check_Type(opts, T_HASH);
|
|
||||||
if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
|
|
||||||
symbolizeKeys = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr = wrapper->result;
|
ptr = wrapper->result;
|
||||||
row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
|
row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
|
||||||
if (row == NULL) {
|
if (row == NULL) {
|
||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
|
||||||
rowHash = rb_hash_new();
|
if (asArray) {
|
||||||
|
rowVal = rb_ary_new2(wrapper->numberOfFields);
|
||||||
|
} else {
|
||||||
|
rowVal = rb_hash_new();
|
||||||
|
}
|
||||||
fields = mysql_fetch_fields(wrapper->result);
|
fields = mysql_fetch_fields(wrapper->result);
|
||||||
fieldLengths = mysql_fetch_lengths(wrapper->result);
|
fieldLengths = mysql_fetch_lengths(wrapper->result);
|
||||||
if (wrapper->fields == Qnil) {
|
if (wrapper->fields == Qnil) {
|
||||||
@ -151,7 +149,7 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|||||||
case MYSQL_TYPE_TIME: { // TIME field
|
case MYSQL_TYPE_TIME: { // TIME field
|
||||||
int hour, min, sec, tokens;
|
int hour, min, sec, tokens;
|
||||||
tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
|
tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
|
||||||
val = rb_funcall(rb_cTime, intern_utc, 6, INT2NUM(0), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
|
val = rb_funcall(rb_cTime, timezone, 6, INT2NUM(2000), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
|
case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
|
||||||
@ -165,7 +163,7 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|||||||
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
|
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
|
||||||
val = Qnil;
|
val = Qnil;
|
||||||
} else {
|
} else {
|
||||||
val = rb_funcall(rb_cTime, intern_utc, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
|
val = rb_funcall(rb_cTime, timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -220,12 +218,20 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
rb_hash_aset(rowHash, field, val);
|
if (asArray) {
|
||||||
|
rb_ary_push(rowVal, val);
|
||||||
} else {
|
} else {
|
||||||
rb_hash_aset(rowHash, field, Qnil);
|
rb_hash_aset(rowVal, field, val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (asArray) {
|
||||||
|
rb_ary_push(rowVal, Qnil);
|
||||||
|
} else {
|
||||||
|
rb_hash_aset(rowVal, field, Qnil);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rowHash;
|
}
|
||||||
|
return rowVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE rb_mysql_result_fetch_fields(VALUE self) {
|
static VALUE rb_mysql_result_fetch_fields(VALUE self) {
|
||||||
@ -249,18 +255,44 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
||||||
VALUE opts, block;
|
VALUE defaults, opts, block, timezoneVal;
|
||||||
|
ID timezone;
|
||||||
mysql2_result_wrapper * wrapper;
|
mysql2_result_wrapper * wrapper;
|
||||||
unsigned long i;
|
unsigned long i;
|
||||||
|
int symbolizeKeys = 0, asArray = 0;
|
||||||
|
|
||||||
GetMysql2Result(self, wrapper);
|
GetMysql2Result(self, wrapper);
|
||||||
|
|
||||||
rb_scan_args(argc, argv, "01&", &opts, &block);
|
defaults = rb_iv_get(self, "@query_options");
|
||||||
|
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
|
||||||
|
opts = rb_funcall(defaults, intern_merge, 1, opts);
|
||||||
|
} else {
|
||||||
|
opts = defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
|
||||||
|
symbolizeKeys = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rb_hash_aref(opts, sym_as) == sym_array) {
|
||||||
|
asArray = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
timezoneVal = rb_hash_aref(opts, sym_timezone);
|
||||||
|
if (timezoneVal == sym_local) {
|
||||||
|
timezone = intern_local;
|
||||||
|
} else if (timezoneVal == sym_utc) {
|
||||||
|
timezone = intern_utc;
|
||||||
|
} else {
|
||||||
|
rb_warn(":timezone config option must be :utc or :local - defaulting to :local");
|
||||||
|
timezone = intern_local;
|
||||||
|
}
|
||||||
|
|
||||||
if (wrapper->lastRowProcessed == 0) {
|
if (wrapper->lastRowProcessed == 0) {
|
||||||
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
|
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
|
||||||
if (wrapper->numberOfRows == 0) {
|
if (wrapper->numberOfRows == 0) {
|
||||||
return Qnil;
|
wrapper->rows = rb_ary_new();
|
||||||
|
return wrapper->rows;
|
||||||
}
|
}
|
||||||
wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
|
wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
|
||||||
}
|
}
|
||||||
@ -279,7 +311,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|||||||
if (i < rowsProcessed) {
|
if (i < rowsProcessed) {
|
||||||
row = rb_ary_entry(wrapper->rows, i);
|
row = rb_ary_entry(wrapper->rows, i);
|
||||||
} else {
|
} else {
|
||||||
row = rb_mysql_result_fetch_row(argc, argv, self);
|
row = rb_mysql_result_fetch_row(self, timezone, symbolizeKeys, asArray);
|
||||||
rb_ary_store(wrapper->rows, i, row);
|
rb_ary_store(wrapper->rows, i, row);
|
||||||
wrapper->lastRowProcessed++;
|
wrapper->lastRowProcessed++;
|
||||||
}
|
}
|
||||||
@ -319,8 +351,7 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_mysql2_result()
|
void init_mysql2_result() {
|
||||||
{
|
|
||||||
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
||||||
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
||||||
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
||||||
@ -329,9 +360,9 @@ void init_mysql2_result()
|
|||||||
rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
|
rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
|
||||||
rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
|
rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
|
||||||
|
|
||||||
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
|
||||||
intern_new = rb_intern("new");
|
intern_new = rb_intern("new");
|
||||||
intern_utc = rb_intern("utc");
|
intern_utc = rb_intern("utc");
|
||||||
|
intern_local = rb_intern("local");
|
||||||
intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
|
intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
|
||||||
|
|
||||||
#ifdef HAVE_RUBY_ENCODING_H
|
#ifdef HAVE_RUBY_ENCODING_H
|
||||||
|
@ -131,7 +131,7 @@ module ActiveRecord
|
|||||||
cattr_accessor :emulate_booleans
|
cattr_accessor :emulate_booleans
|
||||||
self.emulate_booleans = true
|
self.emulate_booleans = true
|
||||||
|
|
||||||
ADAPTER_NAME = 'MySQL2'
|
ADAPTER_NAME = 'Mysql2'
|
||||||
PRIMARY = "PRIMARY"
|
PRIMARY = "PRIMARY"
|
||||||
|
|
||||||
LOST_CONNECTION_ERROR_MESSAGES = [
|
LOST_CONNECTION_ERROR_MESSAGES = [
|
||||||
@ -161,6 +161,7 @@ module ActiveRecord
|
|||||||
super(connection, logger)
|
super(connection, logger)
|
||||||
@connection_options, @config = connection_options, config
|
@connection_options, @config = connection_options, config
|
||||||
@quoted_column_names, @quoted_table_names = {}, {}
|
@quoted_column_names, @quoted_table_names = {}, {}
|
||||||
|
configure_connection
|
||||||
end
|
end
|
||||||
|
|
||||||
def adapter_name
|
def adapter_name
|
||||||
@ -263,15 +264,45 @@ module ActiveRecord
|
|||||||
|
|
||||||
# DATABASE STATEMENTS ======================================
|
# DATABASE STATEMENTS ======================================
|
||||||
|
|
||||||
def select_values(sql, name = nil)
|
# FIXME: re-enable the following once a "better" query_cache solution is in core
|
||||||
select(sql, name).map { |row| row.values.first }
|
#
|
||||||
end
|
# The overrides below perform much better than the originals in AbstractAdapter
|
||||||
|
# because we're able to take advantage of mysql2's lazy-loading capabilities
|
||||||
|
#
|
||||||
|
# # Returns a record hash with the column names as keys and column values
|
||||||
|
# # as values.
|
||||||
|
# def select_one(sql, name = nil)
|
||||||
|
# result = execute(sql, name)
|
||||||
|
# result.each(:as => :hash) do |r|
|
||||||
|
# return r
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # Returns a single value from a record
|
||||||
|
# def select_value(sql, name = nil)
|
||||||
|
# result = execute(sql, name)
|
||||||
|
# if first = result.first
|
||||||
|
# first.first
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # Returns an array of the values of the first column in a select:
|
||||||
|
# # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
||||||
|
# def select_values(sql, name = nil)
|
||||||
|
# execute(sql, name).map { |row| row.first }
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Returns an array of arrays containing the field values.
|
||||||
|
# Order is the same as that returned by +columns+.
|
||||||
def select_rows(sql, name = nil)
|
def select_rows(sql, name = nil)
|
||||||
select(sql, name).map { |row| row.values }
|
execute(sql, name).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Executes the SQL statement in the context of this connection.
|
||||||
def execute(sql, name = nil)
|
def execute(sql, name = nil)
|
||||||
|
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
||||||
|
# made since we established the connection
|
||||||
|
@connection.query_options[:timezone] = ActiveRecord::Base.default_timezone
|
||||||
if name == :skip_logging
|
if name == :skip_logging
|
||||||
@connection.query(sql)
|
@connection.query(sql)
|
||||||
else
|
else
|
||||||
@ -286,7 +317,7 @@ module ActiveRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||||
super sql, name
|
super
|
||||||
id_value || @connection.last_id
|
id_value || @connection.last_id
|
||||||
end
|
end
|
||||||
alias :create :insert_sql
|
alias :create :insert_sql
|
||||||
@ -393,8 +424,8 @@ module ActiveRecord
|
|||||||
|
|
||||||
def tables(name = nil)
|
def tables(name = nil)
|
||||||
tables = []
|
tables = []
|
||||||
execute("SHOW TABLES", name).each(:symbolize_keys => true) do |field|
|
execute("SHOW TABLES", name).each do |field|
|
||||||
tables << field.values.first
|
tables << field.first
|
||||||
end
|
end
|
||||||
tables
|
tables
|
||||||
end
|
end
|
||||||
@ -407,7 +438,7 @@ module ActiveRecord
|
|||||||
indexes = []
|
indexes = []
|
||||||
current_index = nil
|
current_index = nil
|
||||||
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
|
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
|
||||||
result.each(:symbolize_keys => true) do |row|
|
result.each(:symbolize_keys => true, :as => :hash) do |row|
|
||||||
if current_index != row[:Key_name]
|
if current_index != row[:Key_name]
|
||||||
next if row[:Key_name] == PRIMARY # skip the primary key
|
next if row[:Key_name] == PRIMARY # skip the primary key
|
||||||
current_index = row[:Key_name]
|
current_index = row[:Key_name]
|
||||||
@ -423,7 +454,7 @@ module ActiveRecord
|
|||||||
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
||||||
columns = []
|
columns = []
|
||||||
result = execute(sql, :skip_logging)
|
result = execute(sql, :skip_logging)
|
||||||
result.each(:symbolize_keys => true) { |field|
|
result.each(:symbolize_keys => true, :as => :hash) { |field|
|
||||||
columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
|
columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
|
||||||
}
|
}
|
||||||
columns
|
columns
|
||||||
@ -520,7 +551,7 @@ module ActiveRecord
|
|||||||
def pk_and_sequence_for(table)
|
def pk_and_sequence_for(table)
|
||||||
keys = []
|
keys = []
|
||||||
result = execute("describe #{quote_table_name(table)}")
|
result = execute("describe #{quote_table_name(table)}")
|
||||||
result.each(:symbolize_keys => true) do |row|
|
result.each(:symbolize_keys => true, :as => :hash) do |row|
|
||||||
keys << row[:Field] if row[:Key] == "PRI"
|
keys << row[:Field] if row[:Key] == "PRI"
|
||||||
end
|
end
|
||||||
keys.length == 1 ? [keys.first, nil] : nil
|
keys.length == 1 ? [keys.first, nil] : nil
|
||||||
@ -574,6 +605,7 @@ module ActiveRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def configure_connection
|
def configure_connection
|
||||||
|
@connection.query_options.merge!(:as => :array)
|
||||||
encoding = @config[:encoding]
|
encoding = @config[:encoding]
|
||||||
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
|
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
|
||||||
|
|
||||||
@ -582,8 +614,10 @@ module ActiveRecord
|
|||||||
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
|
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns an array of record hashes with the column names as keys and
|
||||||
|
# column values as values.
|
||||||
def select(sql, name = nil)
|
def select(sql, name = nil)
|
||||||
execute(sql, name).to_a
|
execute(sql, name).each(:as => :hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
def supports_views?
|
def supports_views?
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
module Mysql2
|
module Mysql2
|
||||||
class Client
|
class Client
|
||||||
def initialize opts = {}
|
attr_reader :query_options
|
||||||
|
@@default_query_options = {
|
||||||
|
:symbolize_keys => false,
|
||||||
|
:async => false,
|
||||||
|
:as => :hash,
|
||||||
|
:timezone => :local
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize(opts = {})
|
||||||
|
@query_options = @@default_query_options.dup
|
||||||
|
|
||||||
init_connection
|
init_connection
|
||||||
|
|
||||||
[:reconnect, :connect_timeout].each do |key|
|
[:reconnect, :connect_timeout].each do |key|
|
||||||
@ -23,6 +33,10 @@ module Mysql2
|
|||||||
connect user, pass, host, port, database, socket
|
connect user, pass, host, port, database, socket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.default_query_options
|
||||||
|
@@default_query_options
|
||||||
|
end
|
||||||
|
|
||||||
# NOTE: from ruby-mysql
|
# NOTE: from ruby-mysql
|
||||||
if defined? Encoding
|
if defined? Encoding
|
||||||
CHARSET_MAP = {
|
CHARSET_MAP = {
|
||||||
|
@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
s.authors = ["Brian Lopez"]
|
s.authors = ["Brian Lopez"]
|
||||||
s.date = %q{2010-07-17}
|
s.date = %q{2010-08-01}
|
||||||
s.email = %q{seniorlopez@gmail.com}
|
s.email = %q{seniorlopez@gmail.com}
|
||||||
s.extensions = ["ext/mysql2/extconf.rb"]
|
s.extensions = ["ext/mysql2/extconf.rb"]
|
||||||
s.extra_rdoc_files = [
|
s.extra_rdoc_files = [
|
||||||
@ -29,6 +29,8 @@ Gem::Specification.new do |s|
|
|||||||
"benchmark/sequel.rb",
|
"benchmark/sequel.rb",
|
||||||
"benchmark/setup_db.rb",
|
"benchmark/setup_db.rb",
|
||||||
"examples/eventmachine.rb",
|
"examples/eventmachine.rb",
|
||||||
|
"ext/mysql2/client.c",
|
||||||
|
"ext/mysql2/client.h",
|
||||||
"ext/mysql2/extconf.rb",
|
"ext/mysql2/extconf.rb",
|
||||||
"ext/mysql2/mysql2_ext.c",
|
"ext/mysql2/mysql2_ext.c",
|
||||||
"ext/mysql2/mysql2_ext.h",
|
"ext/mysql2/mysql2_ext.h",
|
||||||
|
@ -21,14 +21,14 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should be able to execute a raw query" do
|
it "should be able to execute a raw query" do
|
||||||
@connection.execute("SELECT 1 as one").first['one'].should eql(1)
|
@connection.execute("SELECT 1 as one").first.first.should eql(1)
|
||||||
@connection.execute("SELECT NOW() as n").first['n'].class.should eql(Time)
|
@connection.execute("SELECT NOW() as n").first.first.class.should eql(Time)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "columns" do
|
context "columns" do
|
||||||
before(:all) do
|
before(:all) do
|
||||||
ActiveRecord::Base.default_timezone = 'Pacific Time (US & Canada)'
|
ActiveRecord::Base.default_timezone = :local
|
||||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||||
ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'test')
|
ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'test')
|
||||||
Mysql2Test2.connection.execute %[
|
Mysql2Test2.connection.execute %[
|
||||||
@ -72,7 +72,7 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
|
|||||||
end
|
end
|
||||||
|
|
||||||
after(:all) do
|
after(:all) do
|
||||||
Mysql2Test2.connection.execute("DELETE FROM mysql2_test WHERE id=#{@test_result['id']}")
|
Mysql2Test2.connection.execute("DELETE FROM mysql2_test WHERE id=#{@test_result.first}")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "default value should be cast to the expected type of the field" do
|
it "default value should be cast to the expected type of the field" do
|
||||||
@ -89,9 +89,9 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
|
|||||||
test.double_test.should eql('1.0000'.to_f)
|
test.double_test.should eql('1.0000'.to_f)
|
||||||
test.decimal_test.should eql(BigDecimal.new('1.0000'))
|
test.decimal_test.should eql(BigDecimal.new('1.0000'))
|
||||||
test.date_test.should eql(Date.parse('2010-01-01'))
|
test.date_test.should eql(Date.parse('2010-01-01'))
|
||||||
test.date_time_test.should eql(DateTime.parse('2010-01-01 00:00:00'))
|
test.date_time_test.should eql(Time.local(2010,1,1,0,0,0))
|
||||||
test.timestamp_test.should be_nil
|
test.timestamp_test.should be_nil
|
||||||
test.time_test.class.should eql(DateTime)
|
test.time_test.class.should eql(Time)
|
||||||
test.year_test.should eql(2010)
|
test.year_test.should eql(2010)
|
||||||
test.char_test.should eql('abcdefghij')
|
test.char_test.should eql('abcdefghij')
|
||||||
test.varchar_test.should eql('abcdefghij')
|
test.varchar_test.should eql('abcdefghij')
|
||||||
@ -125,8 +125,9 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
|
|||||||
test.double_test.should eql('1.0000'.to_f)
|
test.double_test.should eql('1.0000'.to_f)
|
||||||
test.decimal_test.should eql(BigDecimal.new('1.0000'))
|
test.decimal_test.should eql(BigDecimal.new('1.0000'))
|
||||||
test.date_test.should eql(Date.parse('2010-01-01'))
|
test.date_test.should eql(Date.parse('2010-01-01'))
|
||||||
test.date_time_test.should eql(Time.utc(2010,1,1,0,0,0))
|
test.date_time_test.should eql(Time.local(2010,1,1,0,0,0))
|
||||||
test.timestamp_test.class.should eql(ActiveSupport::TimeWithZone)
|
test.timestamp_test.class.should eql(ActiveSupport::TimeWithZone) if RUBY_VERSION >= "1.9"
|
||||||
|
test.timestamp_test.class.should eql(Time) if RUBY_VERSION < "1.9"
|
||||||
test.time_test.class.should eql(Time)
|
test.time_test.class.should eql(Time)
|
||||||
test.year_test.should eql(2010)
|
test.year_test.should eql(2010)
|
||||||
test.char_test.should eql('abcdefghij')
|
test.char_test.should eql('abcdefghij')
|
||||||
|
@ -14,6 +14,10 @@ describe Mysql2::Client do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should have a global default_query_options hash" do
|
||||||
|
Mysql2::Client.should respond_to(:default_query_options)
|
||||||
|
end
|
||||||
|
|
||||||
it "should be able to connect via SSL options" do
|
it "should be able to connect via SSL options" do
|
||||||
pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.")
|
pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.")
|
||||||
ssl_client = nil
|
ssl_client = nil
|
||||||
@ -49,6 +53,26 @@ describe Mysql2::Client do
|
|||||||
@client.should respond_to(:query)
|
@client.should respond_to(:query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "#query" do
|
||||||
|
it "should accept an options hash that inherits from Mysql2::Client.default_query_options" do
|
||||||
|
@client.query "SELECT 1", :something => :else
|
||||||
|
@client.query_options.should eql(@client.query_options.merge(:something => :else))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return results as a hash by default" do
|
||||||
|
@client.query("SELECT 1").first.class.should eql(Hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be able to return results as an array" do
|
||||||
|
@client.query("SELECT 1", :as => :array).first.class.should eql(Array)
|
||||||
|
@client.query("SELECT 1").each(:as => :array)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be able to return results with symbolized keys" do
|
||||||
|
@client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "should respond to #escape" do
|
it "should respond to #escape" do
|
||||||
@client.should respond_to(:escape)
|
@client.should respond_to(:escape)
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Mysql2::Result do
|
describe Mysql2::Result do
|
||||||
before(:all) do
|
before(:each) do
|
||||||
@client = Mysql2::Client.new :host => "localhost", :username => "root"
|
@client = Mysql2::Client.new :host => "localhost", :username => "root"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -37,18 +37,23 @@ describe Mysql2::Result do
|
|||||||
|
|
||||||
it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do
|
it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do
|
||||||
@result.each(:symbolize_keys => true) do |row|
|
@result.each(:symbolize_keys => true) do |row|
|
||||||
row.class.should eql(Hash)
|
|
||||||
row.keys.first.class.should eql(Symbol)
|
row.keys.first.class.should eql(Symbol)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should be able to return results as an array" do
|
||||||
|
@result.each(:as => :array) do |row|
|
||||||
|
row.class.should eql(Array)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "should cache previously yielded results" do
|
it "should cache previously yielded results" do
|
||||||
@result.first.should eql(@result.first)
|
@result.first.should eql(@result.first)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "#fields" do
|
context "#fields" do
|
||||||
before(:all) do
|
before(:each) do
|
||||||
@client.query "USE test"
|
@client.query "USE test"
|
||||||
@test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1")
|
@test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1")
|
||||||
end
|
end
|
||||||
@ -64,7 +69,7 @@ describe Mysql2::Result do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "row data type mapping" do
|
context "row data type mapping" do
|
||||||
before(:all) do
|
before(:each) do
|
||||||
@client.query "USE test"
|
@client.query "USE test"
|
||||||
@client.query %[
|
@client.query %[
|
||||||
CREATE TABLE IF NOT EXISTS mysql2_test (
|
CREATE TABLE IF NOT EXISTS mysql2_test (
|
||||||
@ -192,12 +197,8 @@ describe Mysql2::Result do
|
|||||||
|
|
||||||
it "should return Time for a TIME value" do
|
it "should return Time for a TIME value" do
|
||||||
@test_result['time_test'].class.should eql(Time)
|
@test_result['time_test'].class.should eql(Time)
|
||||||
if RUBY_VERSION >= "1.9.2"
|
|
||||||
@test_result['time_test'].strftime("%F %T").should eql('0000-01-01 11:44:00')
|
|
||||||
else
|
|
||||||
@test_result['time_test'].strftime("%F %T").should eql('2000-01-01 11:44:00')
|
@test_result['time_test'].strftime("%F %T").should eql('2000-01-01 11:44:00')
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Date for a DATE value" do
|
it "should return Date for a DATE value" do
|
||||||
@test_result['date_test'].class.should eql(Date)
|
@test_result['date_test'].class.should eql(Date)
|
||||||
|
Loading…
Reference in New Issue
Block a user