mysql2 0.3.10 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -230
  3. data/LICENSE +21 -0
  4. data/README.md +405 -80
  5. data/ext/mysql2/client.c +1110 -335
  6. data/ext/mysql2/client.h +18 -32
  7. data/ext/mysql2/extconf.rb +228 -37
  8. data/ext/mysql2/infile.c +122 -0
  9. data/ext/mysql2/infile.h +1 -0
  10. data/ext/mysql2/mysql2_ext.c +3 -1
  11. data/ext/mysql2/mysql2_ext.h +18 -16
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +172 -0
  13. data/ext/mysql2/mysql_enc_to_ruby.h +310 -0
  14. data/ext/mysql2/result.c +671 -226
  15. data/ext/mysql2/result.h +15 -6
  16. data/ext/mysql2/statement.c +604 -0
  17. data/ext/mysql2/statement.h +17 -0
  18. data/ext/mysql2/wait_for_single_fd.h +2 -1
  19. data/lib/mysql2/client.rb +125 -212
  20. data/lib/mysql2/console.rb +5 -0
  21. data/lib/mysql2/em.rb +24 -8
  22. data/lib/mysql2/error.rb +93 -8
  23. data/lib/mysql2/field.rb +3 -0
  24. data/lib/mysql2/result.rb +2 -0
  25. data/lib/mysql2/statement.rb +11 -0
  26. data/lib/mysql2/version.rb +1 -1
  27. data/lib/mysql2.rb +70 -5
  28. data/support/5072E1F5.asc +432 -0
  29. data/support/libmysql.def +219 -0
  30. data/support/mysql_enc_to_ruby.rb +86 -0
  31. data/support/ruby_enc_to_mysql.rb +63 -0
  32. metadata +45 -214
  33. data/.gitignore +0 -12
  34. data/.rspec +0 -3
  35. data/.rvmrc +0 -1
  36. data/.travis.yml +0 -7
  37. data/Gemfile +0 -3
  38. data/MIT-LICENSE +0 -20
  39. data/Rakefile +0 -5
  40. data/benchmark/active_record.rb +0 -51
  41. data/benchmark/active_record_threaded.rb +0 -42
  42. data/benchmark/allocations.rb +0 -33
  43. data/benchmark/escape.rb +0 -36
  44. data/benchmark/query_with_mysql_casting.rb +0 -80
  45. data/benchmark/query_without_mysql_casting.rb +0 -56
  46. data/benchmark/sequel.rb +0 -37
  47. data/benchmark/setup_db.rb +0 -119
  48. data/benchmark/threaded.rb +0 -44
  49. data/examples/eventmachine.rb +0 -21
  50. data/examples/threaded.rb +0 -20
  51. data/mysql2.gemspec +0 -29
  52. data/spec/em/em_spec.rb +0 -50
  53. data/spec/mysql2/client_spec.rb +0 -465
  54. data/spec/mysql2/error_spec.rb +0 -69
  55. data/spec/mysql2/result_spec.rb +0 -388
  56. data/spec/rcov.opts +0 -3
  57. data/spec/spec_helper.rb +0 -67
  58. data/tasks/benchmarks.rake +0 -20
  59. data/tasks/compile.rake +0 -71
  60. data/tasks/rspec.rake +0 -16
  61. data/tasks/vendor_mysql.rake +0 -40
data/lib/mysql2/client.rb CHANGED
@@ -1,242 +1,155 @@
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 | CONNECT_ATTRS,
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
+ raise 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
+ %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].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
+ flags = case opts[:flags]
54
+ when Array
55
+ parse_flags_array(opts[:flags], @query_options[:connect_flags])
56
+ when String
57
+ parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
58
+ when Integer
59
+ @query_options[:connect_flags] | opts[:flags]
60
+ else
61
+ @query_options[:connect_flags]
32
62
  end
33
63
 
34
- ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher))
64
+ # SSL verify is a connection flag rather than a mysql_ssl_set option
65
+ flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
35
66
 
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]
67
+ if %i[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 ========="
72
+ end
43
73
 
44
- connect user, pass, host, port, database, socket, flags
45
- end
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]
46
80
 
47
- def self.default_query_options
48
- @@default_query_options
49
- end
50
-
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
- }
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?
88
+ conn_attrs = parse_connect_attrs(opts[:connect_attrs])
91
89
 
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
- }
90
+ connect user, pass, host, port, database, socket, flags, conn_attrs
91
+ end
223
92
 
224
- def self.encoding_from_charset(charset)
225
- CHARSET_MAP[charset.to_s.downcase]
93
+ def parse_ssl_mode(mode)
94
+ m = mode.to_s.upcase
95
+ if m.start_with?('SSL_MODE_')
96
+ return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
97
+ else
98
+ x = 'SSL_MODE_' + m
99
+ return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
226
100
  end
101
+ warn "Unknown MySQL ssl_mode flag: #{mode}"
102
+ end
227
103
 
228
- def self.encoding_from_charset_code(code)
229
- if mapping = MYSQL_CHARSET_MAP[code]
230
- encoding_from_charset(mapping[:name])
104
+ def parse_flags_array(flags, initial = 0)
105
+ flags.reduce(initial) do |memo, f|
106
+ fneg = f.start_with?('-') ? f[1..-1] : nil
107
+ if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg)
108
+ memo & ~ Mysql2::Client.const_get(fneg)
109
+ elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f)
110
+ memo | Mysql2::Client.const_get(f)
231
111
  else
232
- nil
112
+ warn "Unknown MySQL connection flag: '#{f}'"
113
+ memo
233
114
  end
234
115
  end
235
116
  end
236
117
 
237
- private
238
- def self.local_offset
118
+ # Set default program_name in performance_schema.session_connect_attrs
119
+ # and performance_schema.session_account_connect_attrs
120
+ def parse_connect_attrs(conn_attrs)
121
+ return {} if Mysql2::Client::CONNECT_ATTRS.zero?
122
+ conn_attrs ||= {}
123
+ conn_attrs[:program_name] ||= $PROGRAM_NAME
124
+ conn_attrs.each_with_object({}) do |(key, value), hash|
125
+ hash[key.to_s] = value.to_s
126
+ end
127
+ end
128
+
129
+ def query(sql, options = {})
130
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
131
+ _query(sql, @query_options.merge(options))
132
+ end
133
+ end
134
+
135
+ def query_info
136
+ info = query_info_string
137
+ return {} unless info
138
+ info_hash = {}
139
+ info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i }
140
+ info_hash
141
+ end
142
+
143
+ def info
144
+ self.class.info
145
+ end
146
+
147
+ class << self
148
+ private
149
+
150
+ def local_offset
239
151
  ::Time.local(2010).utc_offset.to_r / 86400
240
152
  end
153
+ end
241
154
  end
242
155
  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
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'eventmachine'
4
2
  require 'mysql2'
5
3
 
@@ -10,23 +8,41 @@ module Mysql2
10
8
  def initialize(client, deferable)
11
9
  @client = client
12
10
  @deferable = deferable
11
+ @is_watching = true
13
12
  end
14
13
 
15
14
  def notify_readable
16
15
  detach
17
16
  begin
18
- @deferable.succeed(@client.async_result)
19
- rescue Exception => e
17
+ result = @client.async_result
18
+ rescue StandardError => e
20
19
  @deferable.fail(e)
20
+ else
21
+ @deferable.succeed(result)
21
22
  end
22
23
  end
24
+
25
+ def watching?
26
+ @is_watching
27
+ end
28
+
29
+ def unbind
30
+ @is_watching = false
31
+ end
32
+ end
33
+
34
+ def close(*args)
35
+ @watch.detach if @watch && @watch.watching?
36
+
37
+ super(*args)
23
38
  end
24
39
 
25
- def query(sql, opts={})
40
+ def query(sql, opts = {})
26
41
  if ::EM.reactor_running?
27
- super(sql, opts.merge(:async => true))
42
+ super(sql, opts.merge(async: true))
28
43
  deferable = ::EM::DefaultDeferrable.new
29
- ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
44
+ @watch = ::EM.watch(socket, Watcher, self, deferable)
45
+ @watch.notify_readable = true
30
46
  deferable
31
47
  else
32
48
  super(sql, opts)
@@ -34,4 +50,4 @@ module Mysql2
34
50
  end
35
51
  end
36
52
  end
37
- end
53
+ end
data/lib/mysql2/error.rb CHANGED
@@ -1,15 +1,100 @@
1
1
  module Mysql2
2
2
  class Error < StandardError
3
- attr_accessor :error_number, :sql_state
3
+ ENCODE_OPTS = {
4
+ undef: :replace,
5
+ invalid: :replace,
6
+ replace: '?'.freeze,
7
+ }.freeze
4
8
 
5
- def initialize msg
6
- super
7
- @error_number = nil
8
- @sql_state = nil
9
- end
9
+ ConnectionError = Class.new(Error)
10
+ TimeoutError = Class.new(Error)
11
+
12
+ CODES = {
13
+ 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
14
+
15
+ 1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR
16
+ 1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR
17
+ 1152 => ConnectionError, # ER_ABORTING_CONNECTION
18
+ 1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE
19
+ 1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE
20
+ 1155 => ConnectionError, # ER_NET_FCNTL_ERROR
21
+ 1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER
22
+ 1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR
23
+ 1158 => ConnectionError, # ER_NET_READ_ERROR
24
+ 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
25
+ 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
26
+ 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
27
+
28
+ 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
29
+ 2002 => ConnectionError, # CR_CONNECTION_ERROR
30
+ 2003 => ConnectionError, # CR_CONN_HOST_ERROR
31
+ 2004 => ConnectionError, # CR_IPSOCK_ERROR
32
+ 2005 => ConnectionError, # CR_UNKNOWN_HOST
33
+ 2006 => ConnectionError, # CR_SERVER_GONE_ERROR
34
+ 2007 => ConnectionError, # CR_VERSION_ERROR
35
+ 2009 => ConnectionError, # CR_WRONG_HOST_INFO
36
+ 2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR
37
+ 2013 => ConnectionError, # CR_SERVER_LOST
38
+ 2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE
39
+ 2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR
40
+ 2027 => ConnectionError, # CR_MALFORMED_PACKET
41
+ 2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL
42
+ 2048 => ConnectionError, # CR_INVALID_CONN_HANDLE
43
+ 2049 => ConnectionError, # CR_UNUSED_1
44
+ }.freeze
45
+
46
+ attr_reader :error_number, :sql_state
10
47
 
11
48
  # Mysql gem compatibility
12
- alias_method :errno, :error_number
13
- alias_method :error, :message
49
+ alias errno error_number
50
+ alias error message
51
+
52
+ def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
53
+ @server_version = server_version
54
+ @error_number = error_number
55
+ @sql_state = sql_state ? sql_state.encode(**ENCODE_OPTS) : nil
56
+
57
+ super(clean_message(msg))
58
+ end
59
+
60
+ def self.new_with_args(msg, server_version, error_number, sql_state)
61
+ error_class = CODES.fetch(error_number, self)
62
+ error_class.new(msg, server_version, error_number, sql_state)
63
+ end
64
+
65
+ private
66
+
67
+ # In MySQL 5.5+ error messages are always constructed server-side as UTF-8
68
+ # then returned in the encoding set by the `character_set_results` system
69
+ # variable.
70
+ #
71
+ # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for
72
+ # more context.
73
+ #
74
+ # Before MySQL 5.5 error message template strings are in whatever encoding
75
+ # is associated with the error message language.
76
+ # See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html
77
+ # for more information.
78
+ #
79
+ # The issue is that the user-data inserted in the message could potentially
80
+ # be in any encoding MySQL supports and is insert into the latin1, euckr or
81
+ # koi8r string raw. Meaning there's a high probability the string will be
82
+ # corrupt encoding-wise.
83
+ #
84
+ # See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for
85
+ # more information.
86
+ #
87
+ # So in an attempt to make sure the error message string is always in a valid
88
+ # encoding, we'll assume UTF-8 and clean the string of anything that's not a
89
+ # valid UTF-8 character.
90
+ #
91
+ # Returns a valid UTF-8 string.
92
+ def clean_message(message)
93
+ if @server_version && @server_version > 50500
94
+ message.encode(**ENCODE_OPTS)
95
+ else
96
+ message.encode(Encoding::UTF_8, **ENCODE_OPTS)
97
+ end
98
+ end
14
99
  end
15
100
  end
@@ -0,0 +1,3 @@
1
+ module Mysql2
2
+ Field = Struct.new(:name, :type)
3
+ end
data/lib/mysql2/result.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Mysql2
2
2
  class Result
3
+ attr_reader :server_flags
4
+
3
5
  include Enumerable
4
6
  end
5
7
  end
@@ -0,0 +1,11 @@
1
+ module Mysql2
2
+ class Statement
3
+ include Enumerable
4
+
5
+ def execute(*args, **kwargs)
6
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
7
+ _execute(*args, **kwargs)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.3.10"
2
+ VERSION = "0.5.3".freeze
3
3
  end