mysql2 0.3.20 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mysql2/client.rb CHANGED
@@ -1,24 +1,28 @@
1
1
  module Mysql2
2
2
  class Client
3
3
  attr_reader :query_options, :read_timeout
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
- :default_file => nil,
15
- :default_group => nil
16
- }
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
17
20
 
18
21
  def initialize(opts = {})
19
- opts = Mysql2::Util.key_hash_as_symbols( opts )
22
+ raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
23
+ opts = Mysql2::Util.key_hash_as_symbols(opts)
20
24
  @read_timeout = nil
21
- @query_options = @@default_query_options.dup
25
+ @query_options = self.class.default_query_options.dup
22
26
  @query_options.merge! opts
23
27
 
24
28
  initialize_ext
@@ -26,13 +30,14 @@ module Mysql2
26
30
  # Set default connect_timeout to avoid unlimited retries from signal interruption
27
31
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
28
32
 
29
- [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key|
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|
30
35
  next unless opts.key?(key)
31
36
  case key
32
- when :reconnect, :local_infile, :secure_auth
33
- send(:"#{key}=", !!opts[key])
37
+ when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
38
+ send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
34
39
  when :connect_timeout, :read_timeout, :write_timeout
35
- send(:"#{key}=", opts[key].to_i)
40
+ send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
36
41
  else
37
42
  send(:"#{key}=", opts[key])
38
43
  end
@@ -42,11 +47,26 @@ module Mysql2
42
47
  self.charset_name = opts[:encoding] || 'utf8'
43
48
 
44
49
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
45
- ssl_set(*ssl_options) if ssl_options.any?
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]
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]
46
66
 
47
- if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) }
67
+ if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
48
68
  warn "============= WARNING FROM mysql2 ============="
49
- warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
69
+ warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
50
70
  warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
51
71
  warn "============= END WARNING FROM mysql2 ========="
52
72
  end
@@ -57,7 +77,6 @@ module Mysql2
57
77
  port = opts[:port]
58
78
  database = opts[:database] || opts[:dbname] || opts[:db]
59
79
  socket = opts[:socket] || opts[:sock]
60
- flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
61
80
 
62
81
  # Correct the data types before passing these values down to the C level
63
82
  user = user.to_s unless user.nil?
@@ -66,12 +85,51 @@ module Mysql2
66
85
  port = port.to_i unless port.nil?
67
86
  database = database.to_s unless database.nil?
68
87
  socket = socket.to_s unless socket.nil?
88
+ conn_attrs = parse_connect_attrs(opts[:connect_attrs])
69
89
 
70
- connect user, pass, host, port, database, socket, flags
90
+ connect user, pass, host, port, database, socket, flags, conn_attrs
71
91
  end
72
92
 
73
- def self.default_query_options
74
- @@default_query_options
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)
100
+ end
101
+ warn "Unknown MySQL ssl_mode flag: #{mode}"
102
+ end
103
+
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)
111
+ else
112
+ warn "Unknown MySQL connection flag: '#{f}'"
113
+ memo
114
+ end
115
+ end
116
+ end
117
+
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
75
133
  end
76
134
 
77
135
  def query_info
@@ -86,9 +144,12 @@ module Mysql2
86
144
  self.class.info
87
145
  end
88
146
 
89
- private
90
- def self.local_offset
147
+ class << self
148
+ private
149
+
150
+ def local_offset
91
151
  ::Time.local(2010).utc_offset.to_r / 86400
92
152
  end
153
+ end
93
154
  end
94
155
  end
@@ -1,5 +1,5 @@
1
1
  # Loaded by script/console. Land helpers here.
2
2
 
3
- Pry.config.prompt = lambda do |context, nesting, pry|
3
+ Pry.config.prompt = lambda do |context, *|
4
4
  "[mysql2] #{context}> "
5
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
 
@@ -17,7 +15,7 @@ module Mysql2
17
15
  detach
18
16
  begin
19
17
  result = @client.async_result
20
- rescue Exception => e
18
+ rescue StandardError => e
21
19
  @deferable.fail(e)
22
20
  else
23
21
  @deferable.succeed(result)
@@ -34,17 +32,16 @@ module Mysql2
34
32
  end
35
33
 
36
34
  def close(*args)
37
- if @watch
38
- @watch.detach if @watch.watching?
39
- end
35
+ @watch.detach if @watch && @watch.watching?
36
+
40
37
  super(*args)
41
38
  end
42
39
 
43
- def query(sql, opts={})
40
+ def query(sql, opts = {})
44
41
  if ::EM.reactor_running?
45
- super(sql, opts.merge(:async => true))
42
+ super(sql, opts.merge(async: true))
46
43
  deferable = ::EM::DefaultDeferrable.new
47
- @watch = ::EM.watch(self.socket, Watcher, self, deferable)
44
+ @watch = ::EM.watch(socket, Watcher, self, deferable)
48
45
  @watch.notify_readable = true
49
46
  deferable
50
47
  else
data/lib/mysql2/error.rb CHANGED
@@ -1,26 +1,65 @@
1
- # encoding: UTF-8
2
-
3
1
  module Mysql2
4
2
  class Error < StandardError
5
- REPLACEMENT_CHAR = '?'
6
- ENCODE_OPTS = {:undef => :replace, :invalid => :replace, :replace => REPLACEMENT_CHAR}
3
+ ENCODE_OPTS = {
4
+ undef: :replace,
5
+ invalid: :replace,
6
+ replace: '?'.freeze,
7
+ }.freeze
8
+
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
7
27
 
8
- attr_accessor :error_number
9
- attr_reader :sql_state
10
- attr_writer :server_version
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
11
47
 
12
48
  # Mysql gem compatibility
13
- alias_method :errno, :error_number
14
- alias_method :error, :message
49
+ alias errno error_number
50
+ alias error message
15
51
 
16
- def initialize(msg, server_version=nil)
17
- self.server_version = server_version
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
18
56
 
19
57
  super(clean_message(msg))
20
58
  end
21
59
 
22
- def sql_state=(state)
23
- @sql_state = ''.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state
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)
24
63
  end
25
64
 
26
65
  private
@@ -49,31 +88,12 @@ module Mysql2
49
88
  # encoding, we'll assume UTF-8 and clean the string of anything that's not a
50
89
  # valid UTF-8 character.
51
90
  #
52
- # Except for if we're on 1.8, where we'll do nothing ;)
53
- #
54
- # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8
91
+ # Returns a valid UTF-8 string.
55
92
  def clean_message(message)
56
- return message if !message.respond_to?(:encoding)
57
-
58
93
  if @server_version && @server_version > 50500
59
- message.encode(ENCODE_OPTS)
94
+ message.encode(**ENCODE_OPTS)
60
95
  else
61
- if message.respond_to? :scrub
62
- message.scrub(REPLACEMENT_CHAR).encode(ENCODE_OPTS)
63
- else
64
- # This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string
65
- # and retain it's valid UTF-8 characters, that I know of.
66
-
67
- new_message = "".force_encoding(Encoding::UTF_8)
68
- message.chars.each do |char|
69
- if char.valid_encoding?
70
- new_message << char
71
- else
72
- new_message << REPLACEMENT_CHAR
73
- end
74
- end
75
- new_message.encode(ENCODE_OPTS)
76
- end
96
+ message.encode(Encoding::UTF_8, **ENCODE_OPTS)
77
97
  end
78
98
  end
79
99
  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.20"
2
+ VERSION = "0.5.3".freeze
3
3
  end
data/lib/mysql2.rb CHANGED
@@ -1,28 +1,33 @@
1
- # encoding: UTF-8
2
1
  require 'date'
3
2
  require 'bigdecimal'
4
- require 'rational' unless RUBY_VERSION >= '1.9.2'
5
3
 
6
4
  # Load libmysql.dll before requiring mysql2/mysql2.so
7
5
  # This gives a chance to be flexible about the load path
8
6
  # Or to bomb out with a clear error message instead of a linker crash
9
7
  if RUBY_PLATFORM =~ /mswin|mingw/
10
8
  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'].dup
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__)).gsub('/', '\\')
17
- else
18
- # This will use default / system library paths
19
- 'libmysql.dll'
20
- end
9
+ # If this environment variable is set, it overrides any other paths
10
+ # The user is advised to use backslashes not forward slashes
11
+ ENV['RUBY_MYSQL2_LIBMYSQL_DLL']
12
+ elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)))
13
+ # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary
14
+ File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))
15
+ elsif defined?(RubyInstaller)
16
+ # RubyInstaller-2.4+ native build doesn't need DLL preloading
17
+ else
18
+ # This will use default / system library paths
19
+ 'libmysql.dll'
20
+ end
21
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}"
22
+ if dll_path
23
+ require 'fiddle'
24
+ kernel32 = Fiddle.dlopen 'kernel32'
25
+ load_library = Fiddle::Function.new(
26
+ kernel32['LoadLibraryW'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT,
27
+ )
28
+ if load_library.call(dll_path.encode('utf-16le')).zero?
29
+ abort "Failed to load libmysql.dll from #{dll_path}"
30
+ end
26
31
  end
27
32
  end
28
33
 
@@ -31,6 +36,8 @@ require 'mysql2/error'
31
36
  require 'mysql2/mysql2'
32
37
  require 'mysql2/result'
33
38
  require 'mysql2/client'
39
+ require 'mysql2/field'
40
+ require 'mysql2/statement'
34
41
 
35
42
  # = Mysql2
36
43
  #
@@ -51,14 +58,29 @@ if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3
51
58
  end
52
59
 
53
60
  # For holding utility methods
54
- module Mysql2::Util
61
+ module Mysql2
62
+ module Util
63
+ #
64
+ # Rekey a string-keyed hash with equivalent symbols.
65
+ #
66
+ def self.key_hash_as_symbols(hash)
67
+ return nil unless hash
68
+ Hash[hash.map { |k, v| [k.to_sym, v] }]
69
+ end
55
70
 
56
- #
57
- # Rekey a string-keyed hash with equivalent symbols.
58
- #
59
- def self.key_hash_as_symbols(hash)
60
- return nil unless hash
61
- Hash[hash.map { |k,v| [k.to_sym, v] }]
71
+ #
72
+ # In Mysql2::Client#query and Mysql2::Statement#execute,
73
+ # Thread#handle_interrupt is used to prevent Timeout#timeout
74
+ # from interrupting query execution.
75
+ #
76
+ # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8,
77
+ # but is present in earlier 2.1.x and 2.2.x, so we provide a shim.
78
+ #
79
+ require 'timeout'
80
+ TIMEOUT_ERROR_CLASS = if defined?(::Timeout::ExitException)
81
+ ::Timeout::ExitException
82
+ else
83
+ ::Timeout::Error
84
+ end
62
85
  end
63
-
64
86
  end