mysql2 0.3.8 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -220
  3. data/LICENSE +21 -0
  4. data/README.md +370 -79
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +1017 -305
  8. data/ext/mysql2/client.h +35 -11
  9. data/ext/mysql2/extconf.rb +222 -34
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +1 -0
  13. data/ext/mysql2/mysql2_ext.h +12 -14
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +249 -0
  16. data/ext/mysql2/result.c +664 -166
  17. data/ext/mysql2/result.h +16 -6
  18. data/ext/mysql2/statement.c +595 -0
  19. data/ext/mysql2/statement.h +19 -0
  20. data/lib/mysql2/client.rb +118 -211
  21. data/lib/mysql2/console.rb +5 -0
  22. data/lib/mysql2/em.rb +23 -5
  23. data/lib/mysql2/error.rb +62 -6
  24. data/lib/mysql2/field.rb +3 -0
  25. data/lib/mysql2/statement.rb +17 -0
  26. data/lib/mysql2/version.rb +1 -1
  27. data/lib/mysql2.rb +66 -3
  28. data/spec/configuration.yml.example +11 -0
  29. data/spec/em/em_spec.rb +96 -10
  30. data/spec/my.cnf.example +9 -0
  31. data/spec/mysql2/client_spec.rb +779 -205
  32. data/spec/mysql2/error_spec.rb +58 -45
  33. data/spec/mysql2/result_spec.rb +316 -159
  34. data/spec/mysql2/statement_spec.rb +776 -0
  35. data/spec/spec_helper.rb +97 -56
  36. data/spec/ssl/ca-cert.pem +17 -0
  37. data/spec/ssl/ca-key.pem +27 -0
  38. data/spec/ssl/ca.cnf +22 -0
  39. data/spec/ssl/cert.cnf +22 -0
  40. data/spec/ssl/client-cert.pem +17 -0
  41. data/spec/ssl/client-key.pem +27 -0
  42. data/spec/ssl/client-req.pem +15 -0
  43. data/spec/ssl/gen_certs.sh +48 -0
  44. data/spec/ssl/pkcs8-client-key.pem +28 -0
  45. data/spec/ssl/pkcs8-server-key.pem +28 -0
  46. data/spec/ssl/server-cert.pem +17 -0
  47. data/spec/ssl/server-key.pem +27 -0
  48. data/spec/ssl/server-req.pem +15 -0
  49. data/spec/test_data +1 -0
  50. data/support/5072E1F5.asc +432 -0
  51. data/support/libmysql.def +219 -0
  52. data/support/mysql_enc_to_ruby.rb +81 -0
  53. data/support/ruby_enc_to_mysql.rb +61 -0
  54. metadata +77 -196
  55. data/.gitignore +0 -12
  56. data/.rspec +0 -3
  57. data/.rvmrc +0 -1
  58. data/.travis.yml +0 -7
  59. data/Gemfile +0 -3
  60. data/MIT-LICENSE +0 -20
  61. data/Rakefile +0 -5
  62. data/benchmark/active_record.rb +0 -51
  63. data/benchmark/active_record_threaded.rb +0 -42
  64. data/benchmark/allocations.rb +0 -33
  65. data/benchmark/escape.rb +0 -36
  66. data/benchmark/query_with_mysql_casting.rb +0 -80
  67. data/benchmark/query_without_mysql_casting.rb +0 -56
  68. data/benchmark/sequel.rb +0 -37
  69. data/benchmark/setup_db.rb +0 -119
  70. data/benchmark/threaded.rb +0 -44
  71. data/mysql2.gemspec +0 -29
  72. data/tasks/benchmarks.rake +0 -20
  73. data/tasks/compile.rake +0 -71
  74. data/tasks/rspec.rake +0 -16
  75. data/tasks/vendor_mysql.rake +0 -40
data/lib/mysql2/client.rb CHANGED
@@ -1,242 +1,149 @@
1
1
  module Mysql2
2
2
  class Client
3
- attr_reader :query_options
4
- @@default_query_options = {
5
- :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
6
- :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
7
- :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
8
- :symbolize_keys => false, # return field names as symbols instead of strings
9
- :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
10
- :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
11
- :cache_rows => true, # tells Mysql2 to use it's internal row cache for results
12
- :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
13
- :cast => true
14
- }
3
+ attr_reader :query_options, :read_timeout
4
+
5
+ def self.default_query_options
6
+ @default_query_options ||= {
7
+ :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
8
+ :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
9
+ :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
10
+ :symbolize_keys => false, # return field names as symbols instead of strings
11
+ :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
12
+ :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
13
+ :cache_rows => true, # tells Mysql2 to use its internal row cache for results
14
+ :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
15
+ :cast => true,
16
+ :default_file => nil,
17
+ :default_group => nil,
18
+ }
19
+ end
15
20
 
16
21
  def initialize(opts = {})
17
- @query_options = @@default_query_options.dup
22
+ fail Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
23
+ opts = Mysql2::Util.key_hash_as_symbols(opts)
24
+ @read_timeout = nil
25
+ @query_options = self.class.default_query_options.dup
18
26
  @query_options.merge! opts
19
27
 
20
- init_connection
28
+ initialize_ext
21
29
 
22
- [:reconnect, :connect_timeout].each do |key|
30
+ # Set default connect_timeout to avoid unlimited retries from signal interruption
31
+ opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
32
+
33
+ # TODO: stricter validation rather than silent massaging
34
+ [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close, :enable_cleartext_plugin].each do |key|
23
35
  next unless opts.key?(key)
24
- send(:"#{key}=", opts[key])
36
+ case key
37
+ when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
38
+ send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
39
+ when :connect_timeout, :read_timeout, :write_timeout
40
+ send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
41
+ else
42
+ send(:"#{key}=", opts[key])
43
+ end
25
44
  end
45
+
26
46
  # force the encoding to utf8
27
47
  self.charset_name = opts[:encoding] || 'utf8'
28
48
 
29
- @read_timeout = opts[:read_timeout]
30
- if @read_timeout and @read_timeout < 0
31
- raise Mysql2::Error, "read_timeout must be a positive integer, you passed #{@read_timeout}"
49
+ ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
50
+ ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
51
+ self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
52
+
53
+ case opts[:flags]
54
+ when Array
55
+ flags = parse_flags_array(opts[:flags], @query_options[:connect_flags])
56
+ when String
57
+ flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
58
+ when Integer
59
+ flags = @query_options[:connect_flags] | opts[:flags]
60
+ else
61
+ flags = @query_options[:connect_flags]
62
+ end
63
+
64
+ # SSL verify is a connection flag rather than a mysql_ssl_set option
65
+ flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
66
+
67
+ if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
68
+ warn "============= WARNING FROM mysql2 ============="
69
+ warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
70
+ warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
71
+ warn "============= END WARNING FROM mysql2 ========="
32
72
  end
33
73
 
34
- ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher))
74
+ user = opts[:username] || opts[:user]
75
+ pass = opts[:password] || opts[:pass]
76
+ host = opts[:host] || opts[:hostname]
77
+ port = opts[:port]
78
+ database = opts[:database] || opts[:dbname] || opts[:db]
79
+ socket = opts[:socket] || opts[:sock]
35
80
 
36
- user = opts[:username]
37
- pass = opts[:password]
38
- host = opts[:host] || 'localhost'
39
- port = opts[:port] || 3306
40
- database = opts[:database]
41
- socket = opts[:socket]
42
- flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
81
+ # Correct the data types before passing these values down to the C level
82
+ user = user.to_s unless user.nil?
83
+ pass = pass.to_s unless pass.nil?
84
+ host = host.to_s unless host.nil?
85
+ port = port.to_i unless port.nil?
86
+ database = database.to_s unless database.nil?
87
+ socket = socket.to_s unless socket.nil?
43
88
 
44
89
  connect user, pass, host, port, database, socket, flags
45
90
  end
46
91
 
47
- def self.default_query_options
48
- @@default_query_options
92
+ def parse_ssl_mode(mode)
93
+ m = mode.to_s.upcase
94
+ if m.start_with?('SSL_MODE_')
95
+ return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
96
+ else
97
+ x = 'SSL_MODE_' + m
98
+ return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
99
+ end
100
+ warn "Unknown MySQL ssl_mode flag: #{mode}"
49
101
  end
50
102
 
51
- # NOTE: from ruby-mysql
52
- if defined? Encoding
53
- CHARSET_MAP = {
54
- "armscii8" => nil,
55
- "ascii" => Encoding::US_ASCII,
56
- "big5" => Encoding::Big5,
57
- "binary" => Encoding::ASCII_8BIT,
58
- "cp1250" => Encoding::Windows_1250,
59
- "cp1251" => Encoding::Windows_1251,
60
- "cp1256" => Encoding::Windows_1256,
61
- "cp1257" => Encoding::Windows_1257,
62
- "cp850" => Encoding::CP850,
63
- "cp852" => Encoding::CP852,
64
- "cp866" => Encoding::IBM866,
65
- "cp932" => Encoding::Windows_31J,
66
- "dec8" => nil,
67
- "eucjpms" => Encoding::EucJP_ms,
68
- "euckr" => Encoding::EUC_KR,
69
- "gb2312" => Encoding::EUC_CN,
70
- "gbk" => Encoding::GBK,
71
- "geostd8" => nil,
72
- "greek" => Encoding::ISO_8859_7,
73
- "hebrew" => Encoding::ISO_8859_8,
74
- "hp8" => nil,
75
- "keybcs2" => nil,
76
- "koi8r" => Encoding::KOI8_R,
77
- "koi8u" => Encoding::KOI8_U,
78
- "latin1" => Encoding::ISO_8859_1,
79
- "latin2" => Encoding::ISO_8859_2,
80
- "latin5" => Encoding::ISO_8859_9,
81
- "latin7" => Encoding::ISO_8859_13,
82
- "macce" => Encoding::MacCentEuro,
83
- "macroman" => Encoding::MacRoman,
84
- "sjis" => Encoding::SHIFT_JIS,
85
- "swe7" => nil,
86
- "tis620" => Encoding::TIS_620,
87
- "ucs2" => Encoding::UTF_16BE,
88
- "ujis" => Encoding::EucJP_ms,
89
- "utf8" => Encoding::UTF_8,
90
- }
91
-
92
- MYSQL_CHARSET_MAP = {
93
- 1 => {:name => "big5", :collation => "big5_chinese_ci"},
94
- 2 => {:name => "latin2", :collation => "latin2_czech_cs"},
95
- 3 => {:name => "dec8", :collation => "dec8_swedish_ci"},
96
- 4 => {:name => "cp850", :collation => "cp850_general_ci"},
97
- 5 => {:name => "latin1", :collation => "latin1_german1_ci"},
98
- 6 => {:name => "hp8", :collation => "hp8_english_ci"},
99
- 7 => {:name => "koi8r", :collation => "koi8r_general_ci"},
100
- 8 => {:name => "latin1", :collation => "latin1_swedish_ci"},
101
- 9 => {:name => "latin2", :collation => "latin2_general_ci"},
102
- 10 => {:name => "swe7", :collation => "swe7_swedish_ci"},
103
- 11 => {:name => "ascii", :collation => "ascii_general_ci"},
104
- 12 => {:name => "ujis", :collation => "ujis_japanese_ci"},
105
- 13 => {:name => "sjis", :collation => "sjis_japanese_ci"},
106
- 14 => {:name => "cp1251", :collation => "cp1251_bulgarian_ci"},
107
- 15 => {:name => "latin1", :collation => "latin1_danish_ci"},
108
- 16 => {:name => "hebrew", :collation => "hebrew_general_ci"},
109
- 17 => {:name => "filename", :collation => "filename"},
110
- 18 => {:name => "tis620", :collation => "tis620_thai_ci"},
111
- 19 => {:name => "euckr", :collation => "euckr_korean_ci"},
112
- 20 => {:name => "latin7", :collation => "latin7_estonian_cs"},
113
- 21 => {:name => "latin2", :collation => "latin2_hungarian_ci"},
114
- 22 => {:name => "koi8u", :collation => "koi8u_general_ci"},
115
- 23 => {:name => "cp1251", :collation => "cp1251_ukrainian_ci"},
116
- 24 => {:name => "gb2312", :collation => "gb2312_chinese_ci"},
117
- 25 => {:name => "greek", :collation => "greek_general_ci"},
118
- 26 => {:name => "cp1250", :collation => "cp1250_general_ci"},
119
- 27 => {:name => "latin2", :collation => "latin2_croatian_ci"},
120
- 28 => {:name => "gbk", :collation => "gbk_chinese_ci"},
121
- 29 => {:name => "cp1257", :collation => "cp1257_lithuanian_ci"},
122
- 30 => {:name => "latin5", :collation => "latin5_turkish_ci"},
123
- 31 => {:name => "latin1", :collation => "latin1_german2_ci"},
124
- 32 => {:name => "armscii8", :collation => "armscii8_general_ci"},
125
- 33 => {:name => "utf8", :collation => "utf8_general_ci"},
126
- 34 => {:name => "cp1250", :collation => "cp1250_czech_cs"},
127
- 35 => {:name => "ucs2", :collation => "ucs2_general_ci"},
128
- 36 => {:name => "cp866", :collation => "cp866_general_ci"},
129
- 37 => {:name => "keybcs2", :collation => "keybcs2_general_ci"},
130
- 38 => {:name => "macce", :collation => "macce_general_ci"},
131
- 39 => {:name => "macroman", :collation => "macroman_general_ci"},
132
- 40 => {:name => "cp852", :collation => "cp852_general_ci"},
133
- 41 => {:name => "latin7", :collation => "latin7_general_ci"},
134
- 42 => {:name => "latin7", :collation => "latin7_general_cs"},
135
- 43 => {:name => "macce", :collation => "macce_bin"},
136
- 44 => {:name => "cp1250", :collation => "cp1250_croatian_ci"},
137
- 47 => {:name => "latin1", :collation => "latin1_bin"},
138
- 48 => {:name => "latin1", :collation => "latin1_general_ci"},
139
- 49 => {:name => "latin1", :collation => "latin1_general_cs"},
140
- 50 => {:name => "cp1251", :collation => "cp1251_bin"},
141
- 51 => {:name => "cp1251", :collation => "cp1251_general_ci"},
142
- 52 => {:name => "cp1251", :collation => "cp1251_general_cs"},
143
- 53 => {:name => "macroman", :collation => "macroman_bin"},
144
- 57 => {:name => "cp1256", :collation => "cp1256_general_ci"},
145
- 58 => {:name => "cp1257", :collation => "cp1257_bin"},
146
- 59 => {:name => "cp1257", :collation => "cp1257_general_ci"},
147
- 63 => {:name => "binary", :collation => "binary"},
148
- 64 => {:name => "armscii8", :collation => "armscii8_bin"},
149
- 65 => {:name => "ascii", :collation => "ascii_bin"},
150
- 66 => {:name => "cp1250", :collation => "cp1250_bin"},
151
- 67 => {:name => "cp1256", :collation => "cp1256_bin"},
152
- 68 => {:name => "cp866", :collation => "cp866_bin"},
153
- 69 => {:name => "dec8", :collation => "dec8_bin"},
154
- 70 => {:name => "greek", :collation => "greek_bin"},
155
- 71 => {:name => "hebrew", :collation => "hebrew_bin"},
156
- 72 => {:name => "hp8", :collation => "hp8_bin"},
157
- 73 => {:name => "keybcs2", :collation => "keybcs2_bin"},
158
- 74 => {:name => "koi8r", :collation => "koi8r_bin"},
159
- 75 => {:name => "koi8u", :collation => "koi8u_bin"},
160
- 77 => {:name => "latin2", :collation => "latin2_bin"},
161
- 78 => {:name => "latin5", :collation => "latin5_bin"},
162
- 79 => {:name => "latin7", :collation => "latin7_bin"},
163
- 80 => {:name => "cp850", :collation => "cp850_bin"},
164
- 81 => {:name => "cp852", :collation => "cp852_bin"},
165
- 82 => {:name => "swe7", :collation => "swe7_bin"},
166
- 83 => {:name => "utf8", :collation => "utf8_bin"},
167
- 84 => {:name => "big5", :collation => "big5_bin"},
168
- 85 => {:name => "euckr", :collation => "euckr_bin"},
169
- 86 => {:name => "gb2312", :collation => "gb2312_bin"},
170
- 87 => {:name => "gbk", :collation => "gbk_bin"},
171
- 88 => {:name => "sjis", :collation => "sjis_bin"},
172
- 89 => {:name => "tis620", :collation => "tis620_bin"},
173
- 90 => {:name => "ucs2", :collation => "ucs2_bin"},
174
- 91 => {:name => "ujis", :collation => "ujis_bin"},
175
- 92 => {:name => "geostd8", :collation => "geostd8_general_ci"},
176
- 93 => {:name => "geostd8", :collation => "geostd8_bin"},
177
- 94 => {:name => "latin1", :collation => "latin1_spanish_ci"},
178
- 95 => {:name => "cp932", :collation => "cp932_japanese_ci"},
179
- 96 => {:name => "cp932", :collation => "cp932_bin"},
180
- 97 => {:name => "eucjpms", :collation => "eucjpms_japanese_ci"},
181
- 98 => {:name => "eucjpms", :collation => "eucjpms_bin"},
182
- 99 => {:name => "cp1250", :collation => "cp1250_polish_ci"},
183
- 128 => {:name => "ucs2", :collation => "ucs2_unicode_ci"},
184
- 129 => {:name => "ucs2", :collation => "ucs2_icelandic_ci"},
185
- 130 => {:name => "ucs2", :collation => "ucs2_latvian_ci"},
186
- 131 => {:name => "ucs2", :collation => "ucs2_romanian_ci"},
187
- 132 => {:name => "ucs2", :collation => "ucs2_slovenian_ci"},
188
- 133 => {:name => "ucs2", :collation => "ucs2_polish_ci"},
189
- 134 => {:name => "ucs2", :collation => "ucs2_estonian_ci"},
190
- 135 => {:name => "ucs2", :collation => "ucs2_spanish_ci"},
191
- 136 => {:name => "ucs2", :collation => "ucs2_swedish_ci"},
192
- 137 => {:name => "ucs2", :collation => "ucs2_turkish_ci"},
193
- 138 => {:name => "ucs2", :collation => "ucs2_czech_ci"},
194
- 139 => {:name => "ucs2", :collation => "ucs2_danish_ci"},
195
- 140 => {:name => "ucs2", :collation => "ucs2_lithuanian_ci"},
196
- 141 => {:name => "ucs2", :collation => "ucs2_slovak_ci"},
197
- 142 => {:name => "ucs2", :collation => "ucs2_spanish2_ci"},
198
- 143 => {:name => "ucs2", :collation => "ucs2_roman_ci"},
199
- 144 => {:name => "ucs2", :collation => "ucs2_persian_ci"},
200
- 145 => {:name => "ucs2", :collation => "ucs2_esperanto_ci"},
201
- 146 => {:name => "ucs2", :collation => "ucs2_hungarian_ci"},
202
- 192 => {:name => "utf8", :collation => "utf8_unicode_ci"},
203
- 193 => {:name => "utf8", :collation => "utf8_icelandic_ci"},
204
- 194 => {:name => "utf8", :collation => "utf8_latvian_ci"},
205
- 195 => {:name => "utf8", :collation => "utf8_romanian_ci"},
206
- 196 => {:name => "utf8", :collation => "utf8_slovenian_ci"},
207
- 197 => {:name => "utf8", :collation => "utf8_polish_ci"},
208
- 198 => {:name => "utf8", :collation => "utf8_estonian_ci"},
209
- 199 => {:name => "utf8", :collation => "utf8_spanish_ci"},
210
- 200 => {:name => "utf8", :collation => "utf8_swedish_ci"},
211
- 201 => {:name => "utf8", :collation => "utf8_turkish_ci"},
212
- 202 => {:name => "utf8", :collation => "utf8_czech_ci"},
213
- 203 => {:name => "utf8", :collation => "utf8_danish_ci"},
214
- 204 => {:name => "utf8", :collation => "utf8_lithuanian_ci"},
215
- 205 => {:name => "utf8", :collation => "utf8_slovak_ci"},
216
- 206 => {:name => "utf8", :collation => "utf8_spanish2_ci"},
217
- 207 => {:name => "utf8", :collation => "utf8_roman_ci"},
218
- 208 => {:name => "utf8", :collation => "utf8_persian_ci"},
219
- 209 => {:name => "utf8", :collation => "utf8_esperanto_ci"},
220
- 210 => {:name => "utf8", :collation => "utf8_hungarian_ci"},
221
- 254 => {:name => "utf8", :collation => "utf8_general_cs"}
222
- }
223
-
224
- def self.encoding_from_charset(charset)
225
- CHARSET_MAP[charset.to_s.downcase]
103
+ def parse_flags_array(flags, initial = 0)
104
+ flags.reduce(initial) do |memo, f|
105
+ fneg = f.start_with?('-') ? f[1..-1] : nil
106
+ if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg)
107
+ memo & ~ Mysql2::Client.const_get(fneg)
108
+ elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f)
109
+ memo | Mysql2::Client.const_get(f)
110
+ else
111
+ warn "Unknown MySQL connection flag: '#{f}'"
112
+ memo
113
+ end
226
114
  end
115
+ end
227
116
 
228
- def self.encoding_from_charset_code(code)
229
- if mapping = MYSQL_CHARSET_MAP[code]
230
- encoding_from_charset(mapping[:name])
231
- else
232
- nil
117
+ if Thread.respond_to?(:handle_interrupt)
118
+ def query(sql, options = {})
119
+ Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
120
+ _query(sql, @query_options.merge(options))
233
121
  end
234
122
  end
123
+ else
124
+ def query(sql, options = {})
125
+ _query(sql, @query_options.merge(options))
126
+ end
127
+ end
128
+
129
+ def query_info
130
+ info = query_info_string
131
+ return {} unless info
132
+ info_hash = {}
133
+ info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i }
134
+ info_hash
235
135
  end
236
136
 
237
- private
238
- def self.local_offset
137
+ def info
138
+ self.class.info
139
+ end
140
+
141
+ class << self
142
+ private
143
+
144
+ def local_offset
239
145
  ::Time.local(2010).utc_offset.to_r / 86400
240
146
  end
147
+ end
241
148
  end
242
149
  end
@@ -0,0 +1,5 @@
1
+ # Loaded by script/console. Land helpers here.
2
+
3
+ Pry.config.prompt = lambda do |context, *|
4
+ "[mysql2] #{context}> "
5
+ end
data/lib/mysql2/em.rb CHANGED
@@ -10,23 +10,41 @@ module Mysql2
10
10
  def initialize(client, deferable)
11
11
  @client = client
12
12
  @deferable = deferable
13
+ @is_watching = true
13
14
  end
14
15
 
15
16
  def notify_readable
16
17
  detach
17
18
  begin
18
- @deferable.succeed(@client.async_result)
19
- rescue Exception => e
19
+ result = @client.async_result
20
+ rescue => e
20
21
  @deferable.fail(e)
22
+ else
23
+ @deferable.succeed(result)
21
24
  end
22
25
  end
26
+
27
+ def watching?
28
+ @is_watching
29
+ end
30
+
31
+ def unbind
32
+ @is_watching = false
33
+ end
34
+ end
35
+
36
+ def close(*args)
37
+ @watch.detach if @watch && @watch.watching?
38
+
39
+ super(*args)
23
40
  end
24
41
 
25
- def query(sql, opts={})
42
+ def query(sql, opts = {})
26
43
  if ::EM.reactor_running?
27
44
  super(sql, opts.merge(:async => true))
28
45
  deferable = ::EM::DefaultDeferrable.new
29
- ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
46
+ @watch = ::EM.watch(socket, Watcher, self, deferable)
47
+ @watch.notify_readable = true
30
48
  deferable
31
49
  else
32
50
  super(sql, opts)
@@ -34,4 +52,4 @@ module Mysql2
34
52
  end
35
53
  end
36
54
  end
37
- end
55
+ end
data/lib/mysql2/error.rb CHANGED
@@ -1,15 +1,71 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Mysql2
2
4
  class Error < StandardError
3
- attr_accessor :error_number, :sql_state
5
+ ENCODE_OPTS = {
6
+ :undef => :replace,
7
+ :invalid => :replace,
8
+ :replace => '?'.freeze,
9
+ }.freeze
4
10
 
5
- def initialize msg
6
- super
7
- @error_number = nil
8
- @sql_state = nil
9
- end
11
+ attr_reader :error_number, :sql_state
10
12
 
11
13
  # Mysql gem compatibility
12
14
  alias_method :errno, :error_number
13
15
  alias_method :error, :message
16
+
17
+ def initialize(msg)
18
+ @server_version ||= nil
19
+
20
+ super(clean_message(msg))
21
+ end
22
+
23
+ def self.new_with_args(msg, server_version, error_number, sql_state)
24
+ err = allocate
25
+ err.instance_variable_set('@server_version', server_version)
26
+ err.instance_variable_set('@error_number', error_number)
27
+ err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state)
28
+ err.send(:initialize, msg)
29
+ err
30
+ end
31
+
32
+ private
33
+
34
+ # In MySQL 5.5+ error messages are always constructed server-side as UTF-8
35
+ # then returned in the encoding set by the `character_set_results` system
36
+ # variable.
37
+ #
38
+ # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for
39
+ # more context.
40
+ #
41
+ # Before MySQL 5.5 error message template strings are in whatever encoding
42
+ # is associated with the error message language.
43
+ # See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html
44
+ # for more information.
45
+ #
46
+ # The issue is that the user-data inserted in the message could potentially
47
+ # be in any encoding MySQL supports and is insert into the latin1, euckr or
48
+ # koi8r string raw. Meaning there's a high probability the string will be
49
+ # corrupt encoding-wise.
50
+ #
51
+ # See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for
52
+ # more information.
53
+ #
54
+ # So in an attempt to make sure the error message string is always in a valid
55
+ # encoding, we'll assume UTF-8 and clean the string of anything that's not a
56
+ # valid UTF-8 character.
57
+ #
58
+ # Except for if we're on 1.8, where we'll do nothing ;)
59
+ #
60
+ # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8
61
+ def clean_message(message)
62
+ return message unless message.respond_to?(:encode)
63
+
64
+ if @server_version && @server_version > 50500
65
+ message.encode(ENCODE_OPTS)
66
+ else
67
+ message.encode(Encoding::UTF_8, ENCODE_OPTS)
68
+ end
69
+ end
14
70
  end
15
71
  end
@@ -0,0 +1,3 @@
1
+ module Mysql2
2
+ Field = Struct.new(:name, :type)
3
+ end
@@ -0,0 +1,17 @@
1
+ module Mysql2
2
+ class Statement
3
+ include Enumerable
4
+
5
+ if Thread.respond_to?(:handle_interrupt)
6
+ def execute(*args)
7
+ Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
8
+ _execute(*args)
9
+ end
10
+ end
11
+ else
12
+ def execute(*args)
13
+ _execute(*args)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.3.8"
2
+ VERSION = "0.4.10"
3
3
  end
data/lib/mysql2.rb CHANGED
@@ -3,11 +3,36 @@ require 'date'
3
3
  require 'bigdecimal'
4
4
  require 'rational' unless RUBY_VERSION >= '1.9.2'
5
5
 
6
+ # Load libmysql.dll before requiring mysql2/mysql2.so
7
+ # This gives a chance to be flexible about the load path
8
+ # Or to bomb out with a clear error message instead of a linker crash
9
+ if RUBY_PLATFORM =~ /mswin|mingw/
10
+ dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL']
11
+ # If this environment variable is set, it overrides any other paths
12
+ # The user is advised to use backslashes not forward slashes
13
+ ENV['RUBY_MYSQL2_LIBMYSQL_DLL']
14
+ elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)))
15
+ # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary
16
+ File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).tr('/', '\\')
17
+ else
18
+ # This will use default / system library paths
19
+ 'libmysql.dll'
20
+ end
21
+
22
+ require 'Win32API'
23
+ LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I')
24
+ if 0 == LoadLibrary.call(dll_path)
25
+ abort "Failed to load libmysql.dll from #{dll_path}"
26
+ end
27
+ end
28
+
6
29
  require 'mysql2/version' unless defined? Mysql2::VERSION
7
30
  require 'mysql2/error'
8
- require 'mysql2/result'
9
31
  require 'mysql2/mysql2'
32
+ require 'mysql2/result'
10
33
  require 'mysql2/client'
34
+ require 'mysql2/field'
35
+ require 'mysql2/statement'
11
36
 
12
37
  # = Mysql2
13
38
  #
@@ -16,6 +41,44 @@ module Mysql2
16
41
  end
17
42
 
18
43
  if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3.1"
19
- puts "WARNING: This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter bundled anymore as it's now part of Rails 3.1"
20
- puts "WARNING: Please use the 0.2.x releases if you plan on using it in Rails <= 3.0.x"
44
+ begin
45
+ require 'active_record/connection_adapters/mysql2_adapter'
46
+ rescue LoadError
47
+ warn "============= WARNING FROM mysql2 ============="
48
+ warn "This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter."
49
+ warn "In Rails version 3.1.0 and up, the mysql2 ActiveRecord adapter is included with rails."
50
+ warn "If you want to use the mysql2 gem with Rails <= 3.0.x, please use the latest mysql2 in the 0.2.x series."
51
+ warn "============= END WARNING FROM mysql2 ============="
52
+ end
53
+ end
54
+
55
+ # For holding utility methods
56
+ module Mysql2
57
+ module Util
58
+ #
59
+ # Rekey a string-keyed hash with equivalent symbols.
60
+ #
61
+ def self.key_hash_as_symbols(hash)
62
+ return nil unless hash
63
+ Hash[hash.map { |k, v| [k.to_sym, v] }]
64
+ end
65
+
66
+ #
67
+ # In Mysql2::Client#query and Mysql2::Statement#execute,
68
+ # Thread#handle_interrupt is used to prevent Timeout#timeout
69
+ # from interrupting query execution.
70
+ #
71
+ # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8,
72
+ # but is present in earlier 2.1.x and 2.2.x, so we provide a shim.
73
+ #
74
+ if Thread.respond_to?(:handle_interrupt)
75
+ require 'timeout'
76
+ # rubocop:disable Style/ConstantName
77
+ TimeoutError = if defined?(::Timeout::ExitException)
78
+ ::Timeout::ExitException
79
+ else
80
+ ::Timeout::Error
81
+ end
82
+ end
83
+ end
21
84
  end
@@ -0,0 +1,11 @@
1
+ root:
2
+ host: localhost
3
+ username: root
4
+ password:
5
+ database: test
6
+
7
+ user:
8
+ host: localhost
9
+ username: LOCALUSERNAME
10
+ password:
11
+ database: mysql2_test