mysql2 0.3.10 → 0.5.3

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 (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