mysql2 0.3.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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