mysql2 0.3.20 → 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.
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