mysql2 0.3.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -151
  3. data/LICENSE +21 -0
  4. data/README.md +634 -0
  5. data/examples/eventmachine.rb +1 -3
  6. data/examples/threaded.rb +5 -9
  7. data/ext/mysql2/client.c +1154 -342
  8. data/ext/mysql2/client.h +20 -33
  9. data/ext/mysql2/extconf.rb +229 -37
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +3 -1
  13. data/ext/mysql2/mysql2_ext.h +18 -16
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +259 -0
  16. data/ext/mysql2/result.c +708 -191
  17. data/ext/mysql2/result.h +15 -6
  18. data/ext/mysql2/statement.c +602 -0
  19. data/ext/mysql2/statement.h +17 -0
  20. data/ext/mysql2/wait_for_single_fd.h +37 -0
  21. data/lib/mysql2.rb +69 -7
  22. data/lib/mysql2/client.rb +126 -211
  23. data/lib/mysql2/console.rb +5 -0
  24. data/lib/mysql2/em.rb +24 -8
  25. data/lib/mysql2/error.rb +93 -8
  26. data/lib/mysql2/field.rb +3 -0
  27. data/lib/mysql2/result.rb +2 -0
  28. data/lib/mysql2/statement.rb +11 -0
  29. data/lib/mysql2/version.rb +2 -2
  30. data/spec/configuration.yml.example +11 -0
  31. data/spec/em/em_spec.rb +101 -15
  32. data/spec/my.cnf.example +9 -0
  33. data/spec/mysql2/client_spec.rb +874 -232
  34. data/spec/mysql2/error_spec.rb +55 -46
  35. data/spec/mysql2/result_spec.rb +306 -154
  36. data/spec/mysql2/statement_spec.rb +712 -0
  37. data/spec/spec_helper.rb +103 -57
  38. data/spec/ssl/ca-cert.pem +17 -0
  39. data/spec/ssl/ca-key.pem +27 -0
  40. data/spec/ssl/ca.cnf +22 -0
  41. data/spec/ssl/cert.cnf +22 -0
  42. data/spec/ssl/client-cert.pem +17 -0
  43. data/spec/ssl/client-key.pem +27 -0
  44. data/spec/ssl/client-req.pem +15 -0
  45. data/spec/ssl/gen_certs.sh +48 -0
  46. data/spec/ssl/pkcs8-client-key.pem +28 -0
  47. data/spec/ssl/pkcs8-server-key.pem +28 -0
  48. data/spec/ssl/server-cert.pem +17 -0
  49. data/spec/ssl/server-key.pem +27 -0
  50. data/spec/ssl/server-req.pem +15 -0
  51. data/spec/test_data +1 -0
  52. data/support/5072E1F5.asc +432 -0
  53. data/support/libmysql.def +219 -0
  54. data/support/mysql_enc_to_ruby.rb +81 -0
  55. data/support/ruby_enc_to_mysql.rb +61 -0
  56. metadata +82 -188
  57. data/.gitignore +0 -12
  58. data/.rspec +0 -2
  59. data/.rvmrc +0 -1
  60. data/Gemfile +0 -3
  61. data/MIT-LICENSE +0 -20
  62. data/README.rdoc +0 -257
  63. data/Rakefile +0 -5
  64. data/benchmark/active_record.rb +0 -51
  65. data/benchmark/active_record_threaded.rb +0 -42
  66. data/benchmark/allocations.rb +0 -33
  67. data/benchmark/escape.rb +0 -36
  68. data/benchmark/query_with_mysql_casting.rb +0 -80
  69. data/benchmark/query_without_mysql_casting.rb +0 -47
  70. data/benchmark/sequel.rb +0 -37
  71. data/benchmark/setup_db.rb +0 -119
  72. data/benchmark/threaded.rb +0 -44
  73. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +0 -64
  74. data/lib/active_record/fiber_patches.rb +0 -104
  75. data/lib/mysql2/em_fiber.rb +0 -31
  76. data/mysql2.gemspec +0 -32
  77. data/spec/em/em_fiber_spec.rb +0 -22
  78. data/tasks/benchmarks.rake +0 -20
  79. data/tasks/compile.rake +0 -71
  80. data/tasks/rspec.rake +0 -16
  81. data/tasks/vendor_mysql.rake +0 -40
@@ -1,80 +0,0 @@
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
@@ -1,47 +0,0 @@
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
data/benchmark/sequel.rb DELETED
@@ -1,37 +0,0 @@
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
@@ -1,119 +0,0 @@
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"
@@ -1,44 +0,0 @@
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
@@ -1,64 +0,0 @@
1
- # encoding: utf-8
2
-
3
- # AR adapter for using a fibered mysql2 connection with EM
4
- # This adapter should be used within Thin or Unicorn with the rack-fiber_pool middleware.
5
- # Just update your database.yml's adapter to be 'em_mysql2'
6
-
7
- module ActiveRecord
8
- class Base
9
- def self.em_mysql2_connection(config)
10
- client = ::Mysql2::Fibered::Client.new(config.symbolize_keys)
11
- options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
12
- ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
13
- end
14
- end
15
- end
16
-
17
- require 'fiber'
18
- require 'eventmachine'
19
- require 'mysql2'
20
- require 'active_record/connection_adapters/mysql2_adapter'
21
- require 'active_record/fiber_patches'
22
-
23
- module Mysql2
24
- module Fibered
25
- class Client < ::Mysql2::Client
26
- module Watcher
27
- def initialize(client, deferable)
28
- @client = client
29
- @deferable = deferable
30
- end
31
-
32
- def notify_readable
33
- begin
34
- detach
35
- results = @client.async_result
36
- @deferable.succeed(results)
37
- rescue Exception => e
38
- @deferable.fail(e)
39
- end
40
- end
41
- end
42
-
43
- def query(sql, opts={})
44
- if ::EM.reactor_running?
45
- super(sql, opts.merge(:async => true))
46
- deferrable = ::EM::DefaultDeferrable.new
47
- ::EM.watch(self.socket, Watcher, self, deferrable).notify_readable = true
48
- fiber = Fiber.current
49
- deferrable.callback do |result|
50
- fiber.resume(result)
51
- end
52
- deferrable.errback do |err|
53
- fiber.resume(err)
54
- end
55
- Fiber.yield.tap do |result|
56
- raise result if result.is_a?(Exception)
57
- end
58
- else
59
- super(sql, opts)
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,104 +0,0 @@
1
- # Necessary monkeypatching to make AR fiber-friendly.
2
-
3
- module ActiveRecord
4
- module ConnectionAdapters
5
-
6
- def self.fiber_pools
7
- @fiber_pools ||= []
8
- end
9
- def self.register_fiber_pool(fp)
10
- fiber_pools << fp
11
- end
12
-
13
- class FiberedMonitor
14
- class Queue
15
- def initialize
16
- @queue = []
17
- end
18
-
19
- def wait(timeout)
20
- t = timeout || 5
21
- fiber = Fiber.current
22
- x = EM::Timer.new(t) do
23
- @queue.delete(fiber)
24
- fiber.resume(false)
25
- end
26
- @queue << fiber
27
- Fiber.yield.tap do
28
- x.cancel
29
- end
30
- end
31
-
32
- def signal
33
- fiber = @queue.pop
34
- fiber.resume(true) if fiber
35
- end
36
- end
37
-
38
- def synchronize
39
- yield
40
- end
41
-
42
- def new_cond
43
- Queue.new
44
- end
45
- end
46
-
47
- # ActiveRecord's connection pool is based on threads. Since we are working
48
- # with EM and a single thread, multiple fiber design, we need to provide
49
- # our own connection pool that keys off of Fiber.current so that different
50
- # fibers running in the same thread don't try to use the same connection.
51
- class ConnectionPool
52
- def initialize(spec)
53
- @spec = spec
54
-
55
- # The cache of reserved connections mapped to threads
56
- @reserved_connections = {}
57
-
58
- # The mutex used to synchronize pool access
59
- @connection_mutex = FiberedMonitor.new
60
- @queue = @connection_mutex.new_cond
61
-
62
- # default 5 second timeout unless on ruby 1.9
63
- @timeout = spec.config[:wait_timeout] || 5
64
-
65
- # default max pool size to 5
66
- @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
67
-
68
- @connections = []
69
- @checked_out = []
70
- end
71
-
72
- def clear_stale_cached_connections!
73
- cache = @reserved_connections
74
- keys = Set.new(cache.keys)
75
-
76
- ActiveRecord::ConnectionAdapters.fiber_pools.each do |pool|
77
- pool.busy_fibers.each_pair do |object_id, fiber|
78
- keys.delete(object_id)
79
- end
80
- end
81
-
82
- keys.each do |key|
83
- next unless cache.has_key?(key)
84
- checkin cache[key]
85
- cache.delete(key)
86
- end
87
- end
88
-
89
- private
90
-
91
- def current_connection_id #:nodoc:
92
- Fiber.current.object_id
93
- end
94
-
95
- def checkout_and_verify(c)
96
- @checked_out << c
97
- c.run_callbacks :checkout
98
- c.verify!
99
- c
100
- end
101
- end
102
-
103
- end
104
- end