mysql2 0.2.6-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +2 -0
  3. data/CHANGELOG.md +117 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +240 -0
  6. data/Rakefile +5 -0
  7. data/VERSION +1 -0
  8. data/benchmark/active_record.rb +53 -0
  9. data/benchmark/allocations.rb +33 -0
  10. data/benchmark/escape.rb +39 -0
  11. data/benchmark/query_with_mysql_casting.rb +83 -0
  12. data/benchmark/query_without_mysql_casting.rb +50 -0
  13. data/benchmark/sequel.rb +39 -0
  14. data/benchmark/setup_db.rb +115 -0
  15. data/examples/eventmachine.rb +21 -0
  16. data/examples/threaded.rb +20 -0
  17. data/ext/mysql2/client.c +659 -0
  18. data/ext/mysql2/client.h +41 -0
  19. data/ext/mysql2/extconf.rb +65 -0
  20. data/ext/mysql2/mysql2_ext.c +12 -0
  21. data/ext/mysql2/mysql2_ext.h +32 -0
  22. data/ext/mysql2/result.c +475 -0
  23. data/ext/mysql2/result.h +20 -0
  24. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +63 -0
  25. data/lib/active_record/connection_adapters/mysql2_adapter.rb +654 -0
  26. data/lib/active_record/fiber_patches.rb +104 -0
  27. data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +11 -0
  28. data/lib/mysql2.rb +16 -0
  29. data/lib/mysql2/client.rb +235 -0
  30. data/lib/mysql2/em.rb +33 -0
  31. data/lib/mysql2/error.rb +15 -0
  32. data/lib/mysql2/result.rb +5 -0
  33. data/mysql2.gemspec +89 -0
  34. data/spec/em/em_spec.rb +49 -0
  35. data/spec/mysql2/client_spec.rb +348 -0
  36. data/spec/mysql2/error_spec.rb +25 -0
  37. data/spec/mysql2/result_spec.rb +318 -0
  38. data/spec/rcov.opts +3 -0
  39. data/spec/spec_helper.rb +67 -0
  40. data/tasks/benchmarks.rake +8 -0
  41. data/tasks/compile.rake +54 -0
  42. data/tasks/jeweler.rake +17 -0
  43. data/tasks/rspec.rake +16 -0
  44. data/tasks/vendor_mysql.rake +41 -0
  45. metadata +120 -0
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ raise Mysql2::Mysql2Error.new("GC allocation benchmarks only supported on Ruby 1.9!") unless RUBY_VERSION =~ /1\.9/
5
+
6
+ require 'rubygems'
7
+ require 'benchmark'
8
+ require 'active_record'
9
+
10
+ ActiveRecord::Base.default_timezone = :local
11
+ ActiveRecord::Base.time_zone_aware_attributes = true
12
+
13
+ class Mysql2Model < ActiveRecord::Base
14
+ set_table_name :mysql2_test
15
+ end
16
+
17
+ def bench_allocations(feature, iterations = 10, &blk)
18
+ puts "GC overhead for #{feature}"
19
+ Mysql2Model.establish_connection(:adapter => 'mysql2', :database => 'test')
20
+ GC::Profiler.clear
21
+ GC::Profiler.enable
22
+ iterations.times{ blk.call }
23
+ GC::Profiler.report(STDOUT)
24
+ GC::Profiler.disable
25
+ end
26
+
27
+ bench_allocations('coercion') do
28
+ Mysql2Model.all(:limit => 1000).each{ |r|
29
+ r.attributes.keys.each{ |k|
30
+ r.send(k.to_sym)
31
+ }
32
+ }
33
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'mysql'
7
+ require 'mysql2'
8
+ require 'do_mysql'
9
+
10
+ def run_escape_benchmarks(str, number_of = 1000)
11
+ Benchmark.bmbm do |x|
12
+ mysql = Mysql.new("localhost", "root")
13
+ x.report do
14
+ puts "Mysql #{str.inspect}"
15
+ number_of.times do
16
+ mysql.quote str
17
+ end
18
+ end
19
+
20
+ mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
21
+ x.report do
22
+ puts "Mysql2 #{str.inspect}"
23
+ number_of.times do
24
+ mysql2.escape str
25
+ end
26
+ end
27
+
28
+ do_mysql = DataObjects::Connection.new("mysql://localhost/test")
29
+ x.report do
30
+ puts "do_mysql #{str.inspect}"
31
+ number_of.times do
32
+ do_mysql.quote_string str
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ run_escape_benchmarks "abc'def\"ghi\0jkl%mno"
39
+ run_escape_benchmarks "clean string"
@@ -0,0 +1,83 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'mysql'
7
+ require 'mysql2'
8
+ require 'do_mysql'
9
+
10
+ number_of = 100
11
+ database = 'test'
12
+ sql = "SELECT * FROM mysql2_test LIMIT 100"
13
+
14
+ class Mysql
15
+ include Enumerable
16
+ end
17
+
18
+ def mysql_cast(type, value)
19
+ case type
20
+ when Mysql::Field::TYPE_NULL
21
+ nil
22
+ when Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG,
23
+ Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR
24
+ value.to_i
25
+ when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL
26
+ BigDecimal.new(value)
27
+ when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT
28
+ value.to_f
29
+ when Mysql::Field::TYPE_DATE
30
+ Date.parse(value)
31
+ when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP
32
+ Time.parse(value)
33
+ when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING,
34
+ Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET
35
+ Mysql::Field::TYPE_ENUM
36
+ value
37
+ else
38
+ value
39
+ end
40
+ end
41
+
42
+ Benchmark.bmbm do |x|
43
+ mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
44
+ mysql2.query "USE #{database}"
45
+ x.report do
46
+ puts "Mysql2"
47
+ number_of.times do
48
+ mysql2_result = mysql2.query sql, :symbolize_keys => true
49
+ mysql2_result.each do |res|
50
+ # puts res.inspect
51
+ end
52
+ end
53
+ end
54
+
55
+ mysql = Mysql.new("localhost", "root")
56
+ mysql.query "USE #{database}"
57
+ x.report do
58
+ puts "Mysql"
59
+ number_of.times do
60
+ mysql_result = mysql.query sql
61
+ fields = mysql_result.fetch_fields
62
+ mysql_result.each do |row|
63
+ row_hash = {}
64
+ row.each_with_index do |f, j|
65
+ row_hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j])
66
+ end
67
+ # puts row_hash.inspect
68
+ end
69
+ end
70
+ end
71
+
72
+ do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
73
+ command = do_mysql.create_command sql
74
+ x.report do
75
+ puts "do_mysql"
76
+ number_of.times do
77
+ do_result = command.execute_reader
78
+ do_result.each do |res|
79
+ # puts res.inspect
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'mysql'
7
+ require 'mysql2'
8
+ require 'do_mysql'
9
+
10
+ number_of = 100
11
+ database = 'test'
12
+ sql = "SELECT * FROM mysql2_test LIMIT 100"
13
+
14
+ Benchmark.bmbm do |x|
15
+ mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
16
+ mysql2.query "USE #{database}"
17
+ x.report do
18
+ puts "Mysql2"
19
+ number_of.times do
20
+ mysql2_result = mysql2.query sql, :symbolize_keys => true
21
+ mysql2_result.each do |res|
22
+ # puts res.inspect
23
+ end
24
+ end
25
+ end
26
+
27
+ mysql = Mysql.new("localhost", "root")
28
+ mysql.query "USE #{database}"
29
+ x.report do
30
+ puts "Mysql"
31
+ number_of.times do
32
+ mysql_result = mysql.query sql
33
+ mysql_result.each_hash do |res|
34
+ # puts res.inspect
35
+ end
36
+ end
37
+ end
38
+
39
+ do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
40
+ command = DataObjects::Mysql::Command.new do_mysql, sql
41
+ x.report do
42
+ puts "do_mysql"
43
+ number_of.times do
44
+ do_result = command.execute_reader
45
+ do_result.each do |res|
46
+ # puts res.inspect
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'sequel'
7
+ require 'sequel/adapters/do'
8
+
9
+ number_of = 10
10
+ mysql2_opts = "mysql2://localhost/test"
11
+ mysql_opts = "mysql://localhost/test"
12
+ do_mysql_opts = "do:mysql://localhost/test"
13
+
14
+ class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); end
15
+ class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end
16
+ class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end
17
+
18
+ Benchmark.bmbm do |x|
19
+ x.report do
20
+ puts "Mysql2"
21
+ number_of.times do
22
+ Mysql2Model.limit(1000).all
23
+ end
24
+ end
25
+
26
+ x.report do
27
+ puts "do:mysql"
28
+ number_of.times do
29
+ DOMysqlModel.limit(1000).all
30
+ end
31
+ end
32
+
33
+ x.report do
34
+ puts "Mysql"
35
+ number_of.times do
36
+ MysqlModel.limit(1000).all
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,115 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ # This script is for generating psudo-random data into a single table consisting of nearly every
5
+ # data type MySQL 5.1 supports.
6
+ #
7
+ # It's meant to be used with the query.rb benchmark script (or others in the future)
8
+
9
+ require 'mysql2'
10
+ require 'rubygems'
11
+ require 'faker'
12
+
13
+ num = ENV['NUM'] && ENV['NUM'].to_i || 10_000
14
+
15
+ create_table_sql = %[
16
+ CREATE TABLE IF NOT EXISTS mysql2_test (
17
+ null_test VARCHAR(10),
18
+ bit_test BIT,
19
+ tiny_int_test TINYINT,
20
+ small_int_test SMALLINT,
21
+ medium_int_test MEDIUMINT,
22
+ int_test INT,
23
+ big_int_test BIGINT,
24
+ float_test FLOAT(10,3),
25
+ float_zero_test FLOAT(10,3),
26
+ double_test DOUBLE(10,3),
27
+ decimal_test DECIMAL(10,3),
28
+ decimal_zero_test DECIMAL(10,3),
29
+ date_test DATE,
30
+ date_time_test DATETIME,
31
+ timestamp_test TIMESTAMP,
32
+ time_test TIME,
33
+ year_test YEAR(4),
34
+ char_test CHAR(10),
35
+ varchar_test VARCHAR(10),
36
+ binary_test BINARY(10),
37
+ varbinary_test VARBINARY(10),
38
+ tiny_blob_test TINYBLOB,
39
+ tiny_text_test TINYTEXT,
40
+ blob_test BLOB,
41
+ text_test TEXT,
42
+ medium_blob_test MEDIUMBLOB,
43
+ medium_text_test MEDIUMTEXT,
44
+ long_blob_test LONGBLOB,
45
+ long_text_test LONGTEXT,
46
+ enum_test ENUM('val1', 'val2'),
47
+ set_test SET('val1', 'val2')
48
+ ) DEFAULT CHARSET=utf8
49
+ ]
50
+
51
+ # connect to localhost by default, pass options as needed
52
+ @client = Mysql2::Client.new :host => "localhost", :username => "root", :database => "test"
53
+
54
+ @client.query create_table_sql
55
+
56
+ def insert_record(args)
57
+ insert_sql = "
58
+ INSERT INTO mysql2_test (
59
+ null_test, bit_test, tiny_int_test, small_int_test, medium_int_test, int_test, big_int_test,
60
+ float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test,
61
+ year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test,
62
+ tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test,
63
+ long_blob_test, long_text_test, enum_test, set_test
64
+ )
65
+
66
+ VALUES (
67
+ NULL, #{args[:bit_test]}, #{args[:tiny_int_test]}, #{args[:small_int_test]}, #{args[:medium_int_test]}, #{args[:int_test]}, #{args[:big_int_test]},
68
+ #{args[:float_test]}, #{args[:float_zero_test]}, #{args[:double_test]}, #{args[:decimal_test]}, #{args[:decimal_zero_test]}, '#{args[:date_test]}', '#{args[:date_time_test]}', '#{args[:timestamp_test]}', '#{args[:time_test]}',
69
+ #{args[:year_test]}, '#{args[:char_test]}', '#{args[:varchar_test]}', '#{args[:binary_test]}', '#{args[:varbinary_test]}', '#{args[:tiny_blob_test]}',
70
+ '#{args[:tiny_text_test]}', '#{args[:blob_test]}', '#{args[:text_test]}', '#{args[:medium_blob_test]}', '#{args[:medium_text_test]}',
71
+ '#{args[:long_blob_test]}', '#{args[:long_text_test]}', '#{args[:enum_test]}', '#{args[:set_test]}'
72
+ )
73
+ "
74
+ @client.query insert_sql
75
+ end
76
+
77
+ puts "Creating #{num} records"
78
+ num.times do |n|
79
+ insert_record(
80
+ :bit_test => 1,
81
+ :tiny_int_test => rand(128),
82
+ :small_int_test => rand(32767),
83
+ :medium_int_test => rand(8388607),
84
+ :int_test => rand(2147483647),
85
+ :big_int_test => rand(9223372036854775807),
86
+ :float_test => rand(32767)/1.87,
87
+ :float_zero_test => 0.0,
88
+ :double_test => rand(8388607)/1.87,
89
+ :decimal_test => rand(8388607)/1.87,
90
+ :decimal_zero_test => 0,
91
+ :date_test => '2010-4-4',
92
+ :date_time_test => '2010-4-4 11:44:00',
93
+ :timestamp_test => '2010-4-4 11:44:00',
94
+ :time_test => '11:44:00',
95
+ :year_test => Time.now.year,
96
+ :char_test => Faker::Lorem.words(rand(5)),
97
+ :varchar_test => Faker::Lorem.words(rand(5)),
98
+ :binary_test => Faker::Lorem.words(rand(5)),
99
+ :varbinary_test => Faker::Lorem.words(rand(5)),
100
+ :tiny_blob_test => Faker::Lorem.words(rand(5)),
101
+ :tiny_text_test => Faker::Lorem.paragraph(rand(5)),
102
+ :blob_test => Faker::Lorem.paragraphs(rand(25)),
103
+ :text_test => Faker::Lorem.paragraphs(rand(25)),
104
+ :medium_blob_test => Faker::Lorem.paragraphs(rand(25)),
105
+ :medium_text_test => Faker::Lorem.paragraphs(rand(25)),
106
+ :long_blob_test => Faker::Lorem.paragraphs(rand(25)),
107
+ :long_text_test => Faker::Lorem.paragraphs(rand(25)),
108
+ :enum_test => ['val1', 'val2'].rand,
109
+ :set_test => ['val1', 'val2', 'val1,val2'].rand
110
+ )
111
+ $stdout.putc '.'
112
+ $stdout.flush
113
+ end
114
+ puts
115
+ puts "Done"
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH.unshift 'lib'
4
+
5
+ require 'rubygems'
6
+ require 'eventmachine'
7
+ require 'mysql2/em'
8
+
9
+ EM.run do
10
+ client1 = Mysql2::EM::Client.new
11
+ defer1 = client1.query "SELECT sleep(3) as first_query"
12
+ defer1.callback do |result|
13
+ puts "Result: #{result.to_a.inspect}"
14
+ end
15
+
16
+ client2 = Mysql2::EM::Client.new
17
+ defer2 = client2.query "SELECT sleep(1) second_query"
18
+ defer2.callback do |result|
19
+ puts "Result: #{result.to_a.inspect}"
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH.unshift 'lib'
4
+ require 'mysql2'
5
+ require 'timeout'
6
+
7
+ threads = []
8
+ # Should never exceed worst case 3.5 secs across all 20 threads
9
+ Timeout.timeout(3.5) do
10
+ 20.times do
11
+ threads << Thread.new do
12
+ overhead = rand(3)
13
+ puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead"
14
+ # 3 second overhead per query
15
+ Mysql2::Client.new(:host => "localhost", :username => "root").query("SELECT sleep(#{overhead}) as result")
16
+ puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead"
17
+ end
18
+ end
19
+ threads.each{|t| t.join }
20
+ end
@@ -0,0 +1,659 @@
1
+ #include <mysql2_ext.h>
2
+ #include <client.h>
3
+ #include <errno.h>
4
+
5
+ VALUE cMysql2Client;
6
+ extern VALUE mMysql2, cMysql2Error;
7
+ static VALUE intern_encoding_from_charset;
8
+ static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
9
+ static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
10
+
11
+ #define REQUIRE_OPEN_DB(wrapper) \
12
+ if(wrapper->closed) { \
13
+ rb_raise(cMysql2Error, "closed MySQL connection"); \
14
+ return Qnil; \
15
+ }
16
+
17
+ #define MARK_CONN_INACTIVE(conn) \
18
+ wrapper->active = 0
19
+
20
+ #define GET_CLIENT(self) \
21
+ mysql_client_wrapper *wrapper; \
22
+ Data_Get_Struct(self, mysql_client_wrapper, wrapper)
23
+
24
+ /*
25
+ * used to pass all arguments to mysql_real_connect while inside
26
+ * rb_thread_blocking_region
27
+ */
28
+ struct nogvl_connect_args {
29
+ MYSQL *mysql;
30
+ const char *host;
31
+ const char *user;
32
+ const char *passwd;
33
+ const char *db;
34
+ unsigned int port;
35
+ const char *unix_socket;
36
+ unsigned long client_flag;
37
+ };
38
+
39
+ /*
40
+ * used to pass all arguments to mysql_send_query while inside
41
+ * rb_thread_blocking_region
42
+ */
43
+ struct nogvl_send_query_args {
44
+ MYSQL *mysql;
45
+ VALUE sql;
46
+ };
47
+
48
+ /*
49
+ * non-blocking mysql_*() functions that we won't be wrapping since
50
+ * they do not appear to hit the network nor issue any interruptible
51
+ * or blocking system calls.
52
+ *
53
+ * - mysql_affected_rows()
54
+ * - mysql_error()
55
+ * - mysql_fetch_fields()
56
+ * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths
57
+ * - mysql_field_count()
58
+ * - mysql_get_client_info()
59
+ * - mysql_get_client_version()
60
+ * - mysql_get_server_info()
61
+ * - mysql_get_server_version()
62
+ * - mysql_insert_id()
63
+ * - mysql_num_fields()
64
+ * - mysql_num_rows()
65
+ * - mysql_options()
66
+ * - mysql_real_escape_string()
67
+ * - mysql_ssl_set()
68
+ */
69
+
70
+ static void rb_mysql_client_mark(void * wrapper) {
71
+ mysql_client_wrapper * w = wrapper;
72
+ if (w) {
73
+ rb_gc_mark(w->encoding);
74
+ }
75
+ }
76
+
77
+ static VALUE rb_raise_mysql2_error(MYSQL *client) {
78
+ VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client));
79
+ rb_funcall(e, intern_error_number_eql, 1, INT2NUM(mysql_errno(client)));
80
+ rb_funcall(e, intern_sql_state_eql, 1, rb_tainted_str_new2(mysql_sqlstate(client)));
81
+ rb_exc_raise(e);
82
+ return Qnil;
83
+ }
84
+
85
+ static VALUE nogvl_init(void *ptr) {
86
+ MYSQL *client;
87
+
88
+ /* may initialize embedded server and read /etc/services off disk */
89
+ client = mysql_init((MYSQL *)ptr);
90
+ return client ? Qtrue : Qfalse;
91
+ }
92
+
93
+ static VALUE nogvl_connect(void *ptr) {
94
+ struct nogvl_connect_args *args = ptr;
95
+ MYSQL *client;
96
+
97
+ do {
98
+ client = mysql_real_connect(args->mysql, args->host,
99
+ args->user, args->passwd,
100
+ args->db, args->port, args->unix_socket,
101
+ args->client_flag);
102
+ } while (! client && errno == EINTR && (errno = 0) == 0);
103
+
104
+ return client ? Qtrue : Qfalse;
105
+ }
106
+
107
+ static VALUE nogvl_close(void *ptr) {
108
+ mysql_client_wrapper *wrapper = ptr;
109
+ if (!wrapper->closed) {
110
+ wrapper->closed = 1;
111
+
112
+ /*
113
+ * we'll send a QUIT message to the server, but that message is more of a
114
+ * formality than a hard requirement since the socket is getting shutdown
115
+ * anyways, so ensure the socket write does not block our interpreter
116
+ *
117
+ *
118
+ * if the socket is dead we have no chance of blocking,
119
+ * so ignore any potential fcntl errors since they don't matter
120
+ */
121
+ #ifndef _WIN32
122
+ int flags = fcntl(wrapper->client->net.fd, F_GETFL);
123
+ if (flags > 0 && !(flags & O_NONBLOCK))
124
+ fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
125
+ #else
126
+ u_long iMode = 1;
127
+ ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
128
+ #endif
129
+
130
+ mysql_close(wrapper->client);
131
+ free(wrapper->client);
132
+ }
133
+
134
+ return Qnil;
135
+ }
136
+
137
+ static void rb_mysql_client_free(void * ptr) {
138
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
139
+
140
+ nogvl_close(wrapper);
141
+
142
+ xfree(ptr);
143
+ }
144
+
145
+ static VALUE allocate(VALUE klass) {
146
+ VALUE obj;
147
+ mysql_client_wrapper * wrapper;
148
+ obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
149
+ wrapper->encoding = Qnil;
150
+ wrapper->active = 0;
151
+ wrapper->closed = 1;
152
+ wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
153
+ return obj;
154
+ }
155
+
156
+ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
157
+ struct nogvl_connect_args args;
158
+ GET_CLIENT(self);
159
+
160
+ args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
161
+ args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
162
+ args.port = NIL_P(port) ? 3306 : NUM2INT(port);
163
+ args.user = NIL_P(user) ? NULL : StringValuePtr(user);
164
+ args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
165
+ args.db = NIL_P(database) ? NULL : StringValuePtr(database);
166
+ args.mysql = wrapper->client;
167
+ args.client_flag = NUM2ULONG(flags);
168
+
169
+ if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
170
+ // unable to connect
171
+ return rb_raise_mysql2_error(wrapper->client);
172
+ }
173
+
174
+ return self;
175
+ }
176
+
177
+ /*
178
+ * Immediately disconnect from the server, normally the garbage collector
179
+ * will disconnect automatically when a connection is no longer needed.
180
+ * Explicitly closing this will free up server resources sooner than waiting
181
+ * for the garbage collector.
182
+ */
183
+ static VALUE rb_mysql_client_close(VALUE self) {
184
+ GET_CLIENT(self);
185
+
186
+ if (!wrapper->closed) {
187
+ rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
188
+ }
189
+
190
+ return Qnil;
191
+ }
192
+
193
+ /*
194
+ * mysql_send_query is unlikely to block since most queries are small
195
+ * enough to fit in a socket buffer, but sometimes large UPDATE and
196
+ * INSERTs will cause the process to block
197
+ */
198
+ static VALUE nogvl_send_query(void *ptr) {
199
+ struct nogvl_send_query_args *args = ptr;
200
+ int rv;
201
+ const char *sql = StringValuePtr(args->sql);
202
+ long sql_len = RSTRING_LEN(args->sql);
203
+
204
+ rv = mysql_send_query(args->mysql, sql, sql_len);
205
+
206
+ return rv == 0 ? Qtrue : Qfalse;
207
+ }
208
+
209
+ /*
210
+ * even though we did rb_thread_select before calling this, a large
211
+ * response can overflow the socket buffers and cause us to eventually
212
+ * block while calling mysql_read_query_result
213
+ */
214
+ static VALUE nogvl_read_query_result(void *ptr) {
215
+ MYSQL * client = ptr;
216
+ my_bool res = mysql_read_query_result(client);
217
+
218
+ return res == 0 ? Qtrue : Qfalse;
219
+ }
220
+
221
+ /* mysql_store_result may (unlikely) read rows off the socket */
222
+ static VALUE nogvl_store_result(void *ptr) {
223
+ MYSQL * client = ptr;
224
+ return (VALUE)mysql_store_result(client);
225
+ }
226
+
227
+ static VALUE rb_mysql_client_async_result(VALUE self) {
228
+ MYSQL_RES * result;
229
+ GET_CLIENT(self);
230
+
231
+ REQUIRE_OPEN_DB(wrapper);
232
+ if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
233
+ // an error occurred, mark this connection inactive
234
+ MARK_CONN_INACTIVE(self);
235
+ return rb_raise_mysql2_error(wrapper->client);
236
+ }
237
+
238
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
239
+
240
+ // we have our result, mark this connection inactive
241
+ MARK_CONN_INACTIVE(self);
242
+
243
+ if (result == NULL) {
244
+ if (mysql_field_count(wrapper->client) != 0) {
245
+ rb_raise_mysql2_error(wrapper->client);
246
+ }
247
+ return Qnil;
248
+ }
249
+
250
+ VALUE resultObj = rb_mysql_result_to_obj(result);
251
+ // pass-through query options for result construction later
252
+ rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
253
+
254
+ #ifdef HAVE_RUBY_ENCODING_H
255
+ mysql2_result_wrapper * result_wrapper;
256
+ GetMysql2Result(resultObj, result_wrapper);
257
+ result_wrapper->encoding = wrapper->encoding;
258
+ #endif
259
+ return resultObj;
260
+ }
261
+
262
+ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
263
+ struct nogvl_send_query_args args;
264
+ fd_set fdset;
265
+ int fd, retval;
266
+ int async = 0;
267
+ VALUE opts, defaults;
268
+ GET_CLIENT(self);
269
+
270
+ REQUIRE_OPEN_DB(wrapper);
271
+ args.mysql = wrapper->client;
272
+
273
+ // see if this connection is still waiting on a result from a previous query
274
+ if (wrapper->active == 0) {
275
+ // mark this connection active
276
+ wrapper->active = 1;
277
+ } else {
278
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
279
+ }
280
+
281
+ defaults = rb_iv_get(self, "@query_options");
282
+ if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
283
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
284
+ rb_iv_set(self, "@query_options", opts);
285
+
286
+ if (rb_hash_aref(opts, sym_async) == Qtrue) {
287
+ async = 1;
288
+ }
289
+ } else {
290
+ opts = defaults;
291
+ }
292
+
293
+ #ifdef HAVE_RUBY_ENCODING_H
294
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
295
+ // ensure the string is in the encoding the connection is expecting
296
+ args.sql = rb_str_export_to_enc(args.sql, conn_enc);
297
+ #endif
298
+
299
+ if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
300
+ // an error occurred, we're not active anymore
301
+ MARK_CONN_INACTIVE(self);
302
+ return rb_raise_mysql2_error(wrapper->client);
303
+ }
304
+
305
+ if (!async) {
306
+ // the below code is largely from do_mysql
307
+ // http://github.com/datamapper/do
308
+ fd = wrapper->client->net.fd;
309
+ for(;;) {
310
+ FD_ZERO(&fdset);
311
+ FD_SET(fd, &fdset);
312
+
313
+ retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL);
314
+
315
+ if (retval < 0) {
316
+ rb_sys_fail(0);
317
+ }
318
+
319
+ if (retval > 0) {
320
+ break;
321
+ }
322
+ }
323
+
324
+ VALUE result = rb_mysql_client_async_result(self);
325
+
326
+ return result;
327
+ } else {
328
+ return Qnil;
329
+ }
330
+ }
331
+
332
+ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
333
+ VALUE newStr;
334
+ unsigned long newLen, oldLen;
335
+ GET_CLIENT(self);
336
+
337
+ REQUIRE_OPEN_DB(wrapper);
338
+ Check_Type(str, T_STRING);
339
+ #ifdef HAVE_RUBY_ENCODING_H
340
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
341
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
342
+ // ensure the string is in the encoding the connection is expecting
343
+ str = rb_str_export_to_enc(str, conn_enc);
344
+ #endif
345
+
346
+ oldLen = RSTRING_LEN(str);
347
+ newStr = rb_str_new(0, oldLen*2+1);
348
+
349
+ newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
350
+ if (newLen == oldLen) {
351
+ // no need to return a new ruby string if nothing changed
352
+ return str;
353
+ } else {
354
+ rb_str_resize(newStr, newLen);
355
+ #ifdef HAVE_RUBY_ENCODING_H
356
+ rb_enc_associate(newStr, conn_enc);
357
+ if (default_internal_enc) {
358
+ newStr = rb_str_export_to_enc(newStr, default_internal_enc);
359
+ }
360
+ #endif
361
+ return newStr;
362
+ }
363
+ }
364
+
365
+ static VALUE rb_mysql_client_info(VALUE self) {
366
+ VALUE version = rb_hash_new(), client_info;
367
+ GET_CLIENT(self);
368
+
369
+ #ifdef HAVE_RUBY_ENCODING_H
370
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
371
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
372
+ #endif
373
+
374
+ rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
375
+ client_info = rb_str_new2(mysql_get_client_info());
376
+ #ifdef HAVE_RUBY_ENCODING_H
377
+ rb_enc_associate(client_info, conn_enc);
378
+ if (default_internal_enc) {
379
+ client_info = rb_str_export_to_enc(client_info, default_internal_enc);
380
+ }
381
+ #endif
382
+ rb_hash_aset(version, sym_version, client_info);
383
+ return version;
384
+ }
385
+
386
+ static VALUE rb_mysql_client_server_info(VALUE self) {
387
+ VALUE version, server_info;
388
+ GET_CLIENT(self);
389
+
390
+ REQUIRE_OPEN_DB(wrapper);
391
+ #ifdef HAVE_RUBY_ENCODING_H
392
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
393
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
394
+ #endif
395
+
396
+ version = rb_hash_new();
397
+ rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
398
+ server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
399
+ #ifdef HAVE_RUBY_ENCODING_H
400
+ rb_enc_associate(server_info, conn_enc);
401
+ if (default_internal_enc) {
402
+ server_info = rb_str_export_to_enc(server_info, default_internal_enc);
403
+ }
404
+ #endif
405
+ rb_hash_aset(version, sym_version, server_info);
406
+ return version;
407
+ }
408
+
409
+ static VALUE rb_mysql_client_socket(VALUE self) {
410
+ GET_CLIENT(self);
411
+ REQUIRE_OPEN_DB(wrapper);
412
+ return INT2NUM(wrapper->client->net.fd);
413
+ }
414
+
415
+ static VALUE rb_mysql_client_last_id(VALUE self) {
416
+ GET_CLIENT(self);
417
+ REQUIRE_OPEN_DB(wrapper);
418
+ return ULL2NUM(mysql_insert_id(wrapper->client));
419
+ }
420
+
421
+ static VALUE rb_mysql_client_affected_rows(VALUE self) {
422
+ GET_CLIENT(self);
423
+ my_ulonglong retVal;
424
+
425
+ REQUIRE_OPEN_DB(wrapper);
426
+ retVal = mysql_affected_rows(wrapper->client);
427
+ if (retVal == (my_ulonglong)-1) {
428
+ rb_raise_mysql2_error(wrapper->client);
429
+ }
430
+ return ULL2NUM(retVal);
431
+ }
432
+
433
+ static VALUE set_reconnect(VALUE self, VALUE value) {
434
+ my_bool reconnect;
435
+ GET_CLIENT(self);
436
+
437
+ if(!NIL_P(value)) {
438
+ reconnect = value == Qfalse ? 0 : 1;
439
+
440
+ /* set default reconnect behavior */
441
+ if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
442
+ /* TODO: warning - unable to set reconnect behavior */
443
+ rb_warn("%s\n", mysql_error(wrapper->client));
444
+ }
445
+ }
446
+ return value;
447
+ }
448
+
449
+ static VALUE set_connect_timeout(VALUE self, VALUE value) {
450
+ unsigned int connect_timeout = 0;
451
+ GET_CLIENT(self);
452
+
453
+ if(!NIL_P(value)) {
454
+ connect_timeout = NUM2INT(value);
455
+ if(0 == connect_timeout) return value;
456
+
457
+ /* set default connection timeout behavior */
458
+ if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
459
+ /* TODO: warning - unable to set connection timeout */
460
+ rb_warn("%s\n", mysql_error(wrapper->client));
461
+ }
462
+ }
463
+ return value;
464
+ }
465
+
466
+ static VALUE set_charset_name(VALUE self, VALUE value) {
467
+ char * charset_name;
468
+ GET_CLIENT(self);
469
+
470
+ #ifdef HAVE_RUBY_ENCODING_H
471
+ VALUE new_encoding;
472
+ new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
473
+ if (new_encoding == Qnil) {
474
+ rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(value));
475
+ } else {
476
+ if (wrapper->encoding == Qnil) {
477
+ wrapper->encoding = new_encoding;
478
+ }
479
+ }
480
+ #endif
481
+
482
+ charset_name = StringValuePtr(value);
483
+
484
+ if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
485
+ /* TODO: warning - unable to set charset */
486
+ rb_warn("%s\n", mysql_error(wrapper->client));
487
+ }
488
+
489
+ return value;
490
+ }
491
+
492
+ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
493
+ GET_CLIENT(self);
494
+
495
+ if(!NIL_P(ca) || !NIL_P(key)) {
496
+ mysql_ssl_set(wrapper->client,
497
+ NIL_P(key) ? NULL : StringValuePtr(key),
498
+ NIL_P(cert) ? NULL : StringValuePtr(cert),
499
+ NIL_P(ca) ? NULL : StringValuePtr(ca),
500
+ NIL_P(capath) ? NULL : StringValuePtr(capath),
501
+ NIL_P(cipher) ? NULL : StringValuePtr(cipher));
502
+ }
503
+
504
+ return self;
505
+ }
506
+
507
+ static VALUE init_connection(VALUE self) {
508
+ GET_CLIENT(self);
509
+
510
+ if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
511
+ /* TODO: warning - not enough memory? */
512
+ return rb_raise_mysql2_error(wrapper->client);
513
+ }
514
+
515
+ wrapper->closed = 0;
516
+ return self;
517
+ }
518
+
519
+ void init_mysql2_client() {
520
+ cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
521
+
522
+ rb_define_alloc_func(cMysql2Client, allocate);
523
+
524
+ rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
525
+ rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
526
+ rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
527
+ rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
528
+ rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
529
+ rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
530
+ rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
531
+ rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
532
+ rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
533
+
534
+ rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
535
+ rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
536
+ rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
537
+ rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
538
+ rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
539
+ rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
540
+
541
+ intern_encoding_from_charset = rb_intern("encoding_from_charset");
542
+
543
+ sym_id = ID2SYM(rb_intern("id"));
544
+ sym_version = ID2SYM(rb_intern("version"));
545
+ sym_async = ID2SYM(rb_intern("async"));
546
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
547
+ sym_as = ID2SYM(rb_intern("as"));
548
+ sym_array = ID2SYM(rb_intern("array"));
549
+
550
+ intern_merge = rb_intern("merge");
551
+ intern_error_number_eql = rb_intern("error_number=");
552
+ intern_sql_state_eql = rb_intern("sql_state=");
553
+
554
+ #ifdef CLIENT_LONG_PASSWORD
555
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
556
+ INT2NUM(CLIENT_LONG_PASSWORD));
557
+ #endif
558
+
559
+ #ifdef CLIENT_FOUND_ROWS
560
+ rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"),
561
+ INT2NUM(CLIENT_FOUND_ROWS));
562
+ #endif
563
+
564
+ #ifdef CLIENT_LONG_FLAG
565
+ rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"),
566
+ INT2NUM(CLIENT_LONG_FLAG));
567
+ #endif
568
+
569
+ #ifdef CLIENT_CONNECT_WITH_DB
570
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"),
571
+ INT2NUM(CLIENT_CONNECT_WITH_DB));
572
+ #endif
573
+
574
+ #ifdef CLIENT_NO_SCHEMA
575
+ rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"),
576
+ INT2NUM(CLIENT_NO_SCHEMA));
577
+ #endif
578
+
579
+ #ifdef CLIENT_COMPRESS
580
+ rb_const_set(cMysql2Client, rb_intern("COMPRESS"), INT2NUM(CLIENT_COMPRESS));
581
+ #endif
582
+
583
+ #ifdef CLIENT_ODBC
584
+ rb_const_set(cMysql2Client, rb_intern("ODBC"), INT2NUM(CLIENT_ODBC));
585
+ #endif
586
+
587
+ #ifdef CLIENT_LOCAL_FILES
588
+ rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"),
589
+ INT2NUM(CLIENT_LOCAL_FILES));
590
+ #endif
591
+
592
+ #ifdef CLIENT_IGNORE_SPACE
593
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"),
594
+ INT2NUM(CLIENT_IGNORE_SPACE));
595
+ #endif
596
+
597
+ #ifdef CLIENT_PROTOCOL_41
598
+ rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"),
599
+ INT2NUM(CLIENT_PROTOCOL_41));
600
+ #endif
601
+
602
+ #ifdef CLIENT_INTERACTIVE
603
+ rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"),
604
+ INT2NUM(CLIENT_INTERACTIVE));
605
+ #endif
606
+
607
+ #ifdef CLIENT_SSL
608
+ rb_const_set(cMysql2Client, rb_intern("SSL"), INT2NUM(CLIENT_SSL));
609
+ #endif
610
+
611
+ #ifdef CLIENT_IGNORE_SIGPIPE
612
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"),
613
+ INT2NUM(CLIENT_IGNORE_SIGPIPE));
614
+ #endif
615
+
616
+ #ifdef CLIENT_TRANSACTIONS
617
+ rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"),
618
+ INT2NUM(CLIENT_TRANSACTIONS));
619
+ #endif
620
+
621
+ #ifdef CLIENT_RESERVED
622
+ rb_const_set(cMysql2Client, rb_intern("RESERVED"), INT2NUM(CLIENT_RESERVED));
623
+ #endif
624
+
625
+ #ifdef CLIENT_SECURE_CONNECTION
626
+ rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"),
627
+ INT2NUM(CLIENT_SECURE_CONNECTION));
628
+ #endif
629
+
630
+ #ifdef CLIENT_MULTI_STATEMENTS
631
+ rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
632
+ INT2NUM(CLIENT_MULTI_STATEMENTS));
633
+ #endif
634
+
635
+ #ifdef CLIENT_PS_MULTI_RESULTS
636
+ rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"),
637
+ INT2NUM(CLIENT_PS_MULTI_RESULTS));
638
+ #endif
639
+
640
+ #ifdef CLIENT_SSL_VERIFY_SERVER_CERT
641
+ rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"),
642
+ INT2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
643
+ #endif
644
+
645
+ #ifdef CLIENT_REMEMBER_OPTIONS
646
+ rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"),
647
+ INT2NUM(CLIENT_REMEMBER_OPTIONS));
648
+ #endif
649
+
650
+ #ifdef CLIENT_ALL_FLAGS
651
+ rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"),
652
+ INT2NUM(CLIENT_ALL_FLAGS));
653
+ #endif
654
+
655
+ #ifdef CLIENT_BASIC_FLAGS
656
+ rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
657
+ INT2NUM(CLIENT_BASIC_FLAGS));
658
+ #endif
659
+ }