mysql2 0.2.24 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/CHANGELOG.md +148 -0
  5. data/Gemfile +3 -0
  6. data/README.rdoc +257 -0
  7. data/Rakefile +5 -0
  8. data/benchmark/active_record.rb +51 -0
  9. data/benchmark/active_record_threaded.rb +42 -0
  10. data/benchmark/allocations.rb +33 -0
  11. data/benchmark/escape.rb +36 -0
  12. data/benchmark/query_with_mysql_casting.rb +80 -0
  13. data/benchmark/query_without_mysql_casting.rb +47 -0
  14. data/benchmark/sequel.rb +37 -0
  15. data/benchmark/setup_db.rb +119 -0
  16. data/benchmark/threaded.rb +44 -0
  17. data/ext/mysql2/client.c +272 -849
  18. data/ext/mysql2/client.h +12 -27
  19. data/ext/mysql2/extconf.rb +14 -72
  20. data/ext/mysql2/mysql2_ext.h +4 -7
  21. data/ext/mysql2/result.c +123 -319
  22. data/ext/mysql2/result.h +1 -4
  23. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +64 -0
  24. data/lib/active_record/fiber_patches.rb +104 -0
  25. data/lib/mysql2.rb +5 -20
  26. data/lib/mysql2/client.rb +200 -50
  27. data/lib/mysql2/em.rb +3 -13
  28. data/lib/mysql2/em_fiber.rb +31 -0
  29. data/lib/mysql2/error.rb +6 -71
  30. data/lib/mysql2/version.rb +2 -2
  31. data/mysql2.gemspec +32 -0
  32. data/spec/em/em_fiber_spec.rb +22 -0
  33. data/spec/em/em_spec.rb +9 -74
  34. data/spec/mysql2/client_spec.rb +126 -593
  35. data/spec/mysql2/error_spec.rb +44 -58
  36. data/spec/mysql2/result_spec.rb +85 -257
  37. data/spec/spec_helper.rb +3 -24
  38. data/tasks/benchmarks.rake +20 -0
  39. data/tasks/compile.rake +71 -0
  40. data/tasks/rspec.rake +16 -0
  41. data/tasks/vendor_mysql.rake +40 -0
  42. metadata +179 -92
  43. checksums.yaml +0 -7
  44. data/README.md +0 -524
  45. data/ext/mysql2/infile.c +0 -122
  46. data/ext/mysql2/infile.h +0 -1
  47. data/ext/mysql2/mysql_enc_name_to_ruby.h +0 -168
  48. data/ext/mysql2/mysql_enc_to_ruby.h +0 -246
  49. data/ext/mysql2/wait_for_single_fd.h +0 -36
  50. data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -635
  51. data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +0 -11
  52. data/lib/mysql2/console.rb +0 -5
  53. data/spec/configuration.yml.example +0 -17
  54. data/spec/my.cnf.example +0 -9
  55. data/spec/test_data +0 -1
  56. data/support/mysql_enc_to_ruby.rb +0 -82
  57. data/support/ruby_enc_to_mysql.rb +0 -61
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'active_record'
7
+
8
+ times = 25
9
+
10
+
11
+ # mysql2
12
+ mysql2_opts = {
13
+ :adapter => 'mysql2',
14
+ :database => 'test',
15
+ :pool => times
16
+ }
17
+ ActiveRecord::Base.establish_connection(mysql2_opts)
18
+ x = Benchmark.realtime do
19
+ threads = []
20
+ times.times do
21
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
22
+ end
23
+ threads.each {|t| t.join }
24
+ end
25
+ puts "mysql2: #{x} seconds"
26
+
27
+
28
+ # mysql
29
+ mysql2_opts = {
30
+ :adapter => 'mysql',
31
+ :database => 'test',
32
+ :pool => times
33
+ }
34
+ ActiveRecord::Base.establish_connection(mysql2_opts)
35
+ x = Benchmark.realtime do
36
+ threads = []
37
+ times.times do
38
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
39
+ end
40
+ threads.each {|t| t.join }
41
+ end
42
+ puts "mysql: #{x} seconds"
@@ -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,36 @@
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 "Mysql #{str.inspect}" do
14
+ number_of.times do
15
+ mysql.quote str
16
+ end
17
+ end
18
+
19
+ mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
20
+ x.report "Mysql2 #{str.inspect}" do
21
+ number_of.times do
22
+ mysql2.escape str
23
+ end
24
+ end
25
+
26
+ do_mysql = DataObjects::Connection.new("mysql://localhost/test")
27
+ x.report "do_mysql #{str.inspect}" do
28
+ number_of.times do
29
+ do_mysql.quote_string str
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ run_escape_benchmarks "abc'def\"ghi\0jkl%mno"
36
+ run_escape_benchmarks "clean string"
@@ -0,0 +1,80 @@
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 "Mysql2" do
46
+ number_of.times do
47
+ mysql2_result = mysql2.query sql, :symbolize_keys => true
48
+ mysql2_result.each do |res|
49
+ # puts res.inspect
50
+ end
51
+ end
52
+ end
53
+
54
+ mysql = Mysql.new("localhost", "root")
55
+ mysql.query "USE #{database}"
56
+ x.report "Mysql" do
57
+ number_of.times do
58
+ mysql_result = mysql.query sql
59
+ fields = mysql_result.fetch_fields
60
+ mysql_result.each do |row|
61
+ row_hash = {}
62
+ row.each_with_index do |f, j|
63
+ row_hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j])
64
+ end
65
+ # puts row_hash.inspect
66
+ end
67
+ end
68
+ end
69
+
70
+ do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
71
+ command = do_mysql.create_command sql
72
+ x.report "do_mysql" do
73
+ number_of.times do
74
+ do_result = command.execute_reader
75
+ do_result.each do |res|
76
+ # puts res.inspect
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,47 @@
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 "Mysql2" do
18
+ number_of.times do
19
+ mysql2_result = mysql2.query sql, :symbolize_keys => true
20
+ mysql2_result.each do |res|
21
+ # puts res.inspect
22
+ end
23
+ end
24
+ end
25
+
26
+ mysql = Mysql.new("localhost", "root")
27
+ mysql.query "USE #{database}"
28
+ x.report "Mysql" do
29
+ number_of.times do
30
+ mysql_result = mysql.query sql
31
+ mysql_result.each_hash do |res|
32
+ # puts res.inspect
33
+ end
34
+ end
35
+ end
36
+
37
+ do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
38
+ command = DataObjects::Mysql::Command.new do_mysql, sql
39
+ x.report "do_mysql" do
40
+ number_of.times do
41
+ do_result = command.execute_reader
42
+ do_result.each do |res|
43
+ # puts res.inspect
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'mysql2'
7
+ require 'sequel'
8
+ require 'sequel/adapters/do'
9
+
10
+ number_of = 10
11
+ mysql2_opts = "mysql2://localhost/test"
12
+ mysql_opts = "mysql://localhost/test"
13
+ do_mysql_opts = "do:mysql://localhost/test"
14
+
15
+ class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); end
16
+ class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end
17
+ class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end
18
+
19
+ Benchmark.bmbm do |x|
20
+ x.report "Mysql2" do
21
+ number_of.times do
22
+ Mysql2Model.limit(1000).all
23
+ end
24
+ end
25
+
26
+ x.report "do:mysql" do
27
+ number_of.times do
28
+ DOMysqlModel.limit(1000).all
29
+ end
30
+ end
31
+
32
+ x.report "Mysql" do
33
+ number_of.times do
34
+ MysqlModel.limit(1000).all
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,119 @@
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
+ five_words = Faker::Lorem.words(rand(5))
80
+ twenty5_paragraphs = Faker::Lorem.paragraphs(rand(25))
81
+ insert_record(
82
+ :bit_test => 1,
83
+ :tiny_int_test => rand(128),
84
+ :small_int_test => rand(32767),
85
+ :medium_int_test => rand(8388607),
86
+ :int_test => rand(2147483647),
87
+ :big_int_test => rand(9223372036854775807),
88
+ :float_test => rand(32767)/1.87,
89
+ :float_zero_test => 0.0,
90
+ :double_test => rand(8388607)/1.87,
91
+ :decimal_test => rand(8388607)/1.87,
92
+ :decimal_zero_test => 0,
93
+ :date_test => '2010-4-4',
94
+ :date_time_test => '2010-4-4 11:44:00',
95
+ :timestamp_test => '2010-4-4 11:44:00',
96
+ :time_test => '11:44:00',
97
+ :year_test => Time.now.year,
98
+ :char_test => five_words,
99
+ :varchar_test => five_words,
100
+ :binary_test => five_words,
101
+ :varbinary_test => five_words,
102
+ :tiny_blob_test => five_words,
103
+ :tiny_text_test => Faker::Lorem.paragraph(rand(5)),
104
+ :blob_test => twenty5_paragraphs,
105
+ :text_test => twenty5_paragraphs,
106
+ :medium_blob_test => twenty5_paragraphs,
107
+ :medium_text_test => twenty5_paragraphs,
108
+ :long_blob_test => twenty5_paragraphs,
109
+ :long_text_test => twenty5_paragraphs,
110
+ :enum_test => ['val1', 'val2'].rand,
111
+ :set_test => ['val1', 'val2', 'val1,val2'].rand
112
+ )
113
+ if n % 100 == 0
114
+ $stdout.putc '.'
115
+ $stdout.flush
116
+ end
117
+ end
118
+ puts
119
+ puts "Done"
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'active_record'
7
+
8
+ mysql2_opts = {
9
+ :adapter => 'mysql2',
10
+ :database => 'test',
11
+ :pool => 25
12
+ }
13
+ ActiveRecord::Base.establish_connection(mysql2_opts)
14
+ x = Benchmark.realtime do
15
+ threads = []
16
+ 25.times do
17
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
18
+ end
19
+ threads.each {|t| t.join }
20
+ end
21
+ puts x
22
+
23
+ mysql2_opts = {
24
+ :adapter => 'mysql',
25
+ :database => 'test',
26
+ :pool => 25
27
+ }
28
+ ActiveRecord::Base.establish_connection(mysql2_opts)
29
+ x = Benchmark.realtime do
30
+ threads = []
31
+ 25.times do
32
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
33
+ end
34
+ threads.each {|t| t.join }
35
+ end
36
+ puts x
37
+
38
+ # these results are similar on 1.8.7, 1.9.2 and rbx-head
39
+ #
40
+ # $ bundle exec ruby benchmarks/threaded.rb
41
+ # 1.0774750709533691
42
+ #
43
+ # and using the mysql gem
44
+ # 25.099437952041626