ruby-mysql 3.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37cc4956fc000b612b24fe104acf96abf27e0a7016cbd05c2c5e7b4ae5e0bade
4
- data.tar.gz: b2c4ded73325a5c4aadce1d81af507e2a7d4144764150fde183f0e541e25e8b8
3
+ metadata.gz: fd37c2f40ef7277952c0be41848abc338e29d3a4e9da3f3eed1d1d8f61407c76
4
+ data.tar.gz: 754994f1cb1f5e4cb007d5f1a3d81238e626ce8d224aef23c58c8af3d5fd351f
5
5
  SHA512:
6
- metadata.gz: c81163849e312cb8cb861a1c5add93fba0973d1fbf01b9296513914931134230dbf7d2d6437e72097c8426d24daab316c2756621d3302d5ee91efb72ab951cc3
7
- data.tar.gz: c4e13cea550e4659db986772663b04364c8c18f51abc9442516058cbe5ebc420dd19a95e324e149ae0966edefa1259fff9274677295849834029f827667547c2
6
+ metadata.gz: c31c8170682621713905f820a2816377a045b3ba489adc39d3575543483475411bdd7ecead30f806494533dd91819e8e53dad1bd3c090164b80d7600666ea1a4
7
+ data.tar.gz: 6f59d7638d0f09bd3c21cd34e32cae9e995f26219efc5594b3982b94562a0c3726a1258ebfff5e3b0afbe53764135d96eff0250d57d6d75289f65e3b9957e720
data/CHANGELOG.md CHANGED
@@ -1,3 +1,60 @@
1
+ ## [4.1.0] - 2023-10-08
2
+
3
+ - Support geometry column <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/1>
4
+ - FIX: If the server is localhost and disconnect the connection, Errno::EPIPE is raised <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/3> <https://gitlab.com/tmtms/ruby-mysql/-/issues/1>
5
+ - Allow for existing socket when connecting <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/2> <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/5>
6
+ - add `io` keyword parameter
7
+
8
+ ## [4.0.0] - 2022-11-09
9
+
10
+ ### Incompatible changes
11
+
12
+ - unsupport MySQL 5.5 and Ruby 2.5
13
+ - `Mysql::Result#fetch` returns converted values unless `cast` is false
14
+ - `Mysql::Result#each` always returns records from the beginning
15
+ - Retrieving a DECIMAL type with a prepared statement returns a `BigDecimal` object instead of `String`
16
+ - Retrieving a DATE type value with a prepared statement returns a `Date` object instead of `Time`
17
+ - delete `Mysql#more_results`. use `#more_results?` instead.
18
+ - remove `Mysql::Result#fetch_field`, `#field_tell`, `#field_seek`, `#fetch_field_direct`, `#fetch_lengths`, `#num_fields`.
19
+ - error 'command out of sync' is `Mysql::ClientError::CommandOutOfSync` instead of `RuntimeError`.
20
+ - error 'Authentication requires secure connection' is `Mysql::ClientError::AuthPluginErr` instead of `RuntimeError`.
21
+
22
+ ### Features
23
+
24
+ - `Mysql.default_options` is global options.
25
+ - `Mysql#connect` option `ssl_mode`: support `SSL_MODE_VERIFY_CA`, `SSL_MODE_VERIFY_IDENTITY`.
26
+ - `Mysql#connect` option `ssl_context_params`: see `OpenSSL::SSL::SSLContext#set_params`.
27
+ - `Mysql#connect` option `connect_attrs`.
28
+ - `Mysql::Stmt#more_results?`, `#next_result`, `#info`.
29
+ - `Mysql#close` and `Mysql::Stmt#close` read pending packets.
30
+ - `Mysql#query` and `Mysql::Stmt#execute` option: `return_result` and `yield_null_result`.
31
+ - support session tracking. See https://dev.mysql.com/doc/refman/8.0/en/session-state-tracking.html
32
+ - thread safe.
33
+ - `Mysql#query`, `Mysql::Stmt#execute` option: `auto_store_result`.
34
+ - add `Mysql::Result#server_status`
35
+
36
+ ### Fixes
37
+
38
+ - When using connection that disconnected from client. error 'MySQL client is not connected' is occured instead of 'MySQL server has gone away'.
39
+ - When SSL error, `MySQL::ClientError::ServerLost` or `ServerGoneError` is occured instead of `OpenSSL::SSL::SSLError`.
40
+ - `Mysql#server_version` don't require connection.
41
+ - use `connect_timeout` instead of `read/write_timeout` on initial negotiation.
42
+ - enable to changing `local_infile` for established connection.
43
+ - `Mysql.connect` with host nil ignores parameters
44
+ - raises `IOError` after `Mysql#close`
45
+ - Fractional seconds of time types were ignored when retrieving values using prepared statements
46
+ - `Mysql::Stmt#execute` allows true or false values.
47
+
48
+ ### Others
49
+
50
+ - Mysql#prpare raises Mysql::ClientError if the connection is not connected.
51
+ - using rubocop
52
+ - migrate from GitHub to GitLab
53
+ - ignore Gemfile.lock
54
+ - use rspec instead of test-unit
55
+ - using connection parameter from spec/config.rb for testing
56
+ - split files
57
+
1
58
  ## [3.0.1] - 2022-06-18
2
59
 
3
60
  - LICENSE: correct author
data/README.md CHANGED
@@ -2,17 +2,21 @@
2
2
 
3
3
  ## Description
4
4
 
5
- MySQL connector for Ruby.
5
+ ruby-mysql is a MySQL client library.
6
+ It is written entirely in Ruby.
7
+ Therefore libmysqlclient is not required and no compilation is required during installation.
6
8
 
7
9
  ## Installation
8
10
 
9
- ```
11
+ ```ruby
10
12
  gem install ruby-mysql
11
13
  ```
12
14
 
13
15
  ## Synopsis
14
16
 
15
17
  ```ruby
18
+ require 'mysql'
19
+
16
20
  my = Mysql.connect('mysql://username:password@hostname:port/dbname?charset=utf8mb4')
17
21
  my.query("select col1, col2 from tblname").each do |col1, col2|
18
22
  p col1, col2
@@ -21,6 +25,95 @@ stmt = my.prepare('insert into tblname (col1,col2) values (?,?)')
21
25
  stmt.execute 123, 'abc'
22
26
  ```
23
27
 
28
+ ## Major incompatibility with 3.0
29
+
30
+ ### Result values are now converted by default
31
+
32
+ | MySQL type | Ruby class |
33
+ |---------------------|--------------------|
34
+ | NULL | NilClass |
35
+ | INT | Integer |
36
+ | DECIMAL | BigDecimal |
37
+ | FLOAT, DOUBLE | Float |
38
+ | DATE | Date |
39
+ | DATETIME, TIMESTAMP | Time |
40
+ | TIME | Float (as seconds) |
41
+ | YEAR | Integer |
42
+ | CHAR, VARCHAR | String |
43
+ | BIT | String |
44
+ | TEXT, BLOB, JSON | String |
45
+
46
+ 3.0:
47
+ ```ruby
48
+ pp my.query('select 123,123.45,now(),cast(now() as date)').fetch.map{[_1, _1.class]}
49
+ #=> [["123", String],
50
+ # ["123.45", String],
51
+ # ["2022-11-15 00:17:11", String],
52
+ # ["2022-11-15", String]]
53
+ ```
54
+
55
+ 4.0:
56
+ ```ruby
57
+ pp my.query('select 123,123.45,now(),cast(now() as date)').fetch.map{[_1, _1.class]}
58
+ #=> [[123, Integer],
59
+ # [0.12345e3, BigDecimal],
60
+ # [2022-11-15 00:17:17 +0900, Time],
61
+ # [#<Date: 2022-11-15 ((2459899j,0s,0n),+0s,2299161j)>, Date]]
62
+ ```
63
+
64
+ To specify `cast: false`, you get the same behavior as in 3.0.
65
+ ```ruby
66
+ my.query('select 123,123.45,now(),cast(now() as date)', cast: false).fetch.map{[_1, _1.class]}
67
+ #=> [["123", String],
68
+ # ["123.45", String],
69
+ # ["2022-11-15 00:19:18", String],
70
+ # ["2022-11-15", String]]
71
+ ```
72
+
73
+ It can also be specified during Mysql.new and Mysql.connect.
74
+
75
+ ```ruby
76
+ my = Mysql.connect('mysql://user:pass@localhost/', cast: false)
77
+ ```
78
+
79
+ Changing mysql.default_options will affect the behavior of subsequently created instances.
80
+
81
+ ```ruby
82
+ my1 = Mysql.connect('mysql://user:pass@localhost/')
83
+ Mysql.default_options[:cast] = false
84
+ my2 = Mysql.connect('mysql://user:pass@localhost/')
85
+ pp my1.query('select 123,123.45,now(),cast(now() as date)').fetch.map{[_1, _1.class]}
86
+ #=> [[123, Integer],
87
+ # [0.12345e3, BigDecimal],
88
+ # [2022-11-15 00:26:09 +0900, Time],
89
+ # [#<Date: 2022-11-15 ((2459899j,0s,0n),+0s,2299161j)>, Date]]
90
+ pp my2.query('select 123,123.45,now(),cast(now() as date)').fetch.map{[_1, _1.class]}
91
+ #=> [["123", String],
92
+ # ["123.45", String],
93
+ # ["2022-11-15 00:26:09", String],
94
+ # ["2022-11-15", String]]
95
+ ```
96
+
97
+ ### Mysql::Result#each now always return records from the beginning
98
+
99
+ 3.0:
100
+ ```ruby
101
+ res = my.query('select 123 union select 456')
102
+ res.entries
103
+ #=> [["123"], ["456"]]
104
+ res.entries
105
+ #=> []
106
+ ```
107
+
108
+ 4.0:
109
+ ```ruby
110
+ res = my.query('select 123 union select 456')
111
+ res.entries
112
+ #=> [[123], [456]]
113
+ res.entries
114
+ #=> [[123], [456]]
115
+ ```
116
+
24
117
  ## Copyright
25
118
 
26
119
  * Author: TOMITA Masahiro <tommy@tmtm.org>
@@ -2,6 +2,7 @@ require 'digest/sha2'
2
2
 
3
3
  class Mysql
4
4
  class Authenticator
5
+ # caching_sha2_password
5
6
  class CachingSha2Password
6
7
  # @param protocol [Mysql::Protocol]
7
8
  def initialize(protocol)
@@ -29,7 +30,7 @@ class Mysql
29
30
  if @protocol.client_flags & CLIENT_SSL != 0
30
31
  @protocol.write passwd+"\0"
31
32
  elsif !@protocol.get_server_public_key
32
- raise 'Authentication requires secure connection'
33
+ raise ClientError::AuthPluginErr, 'Authentication requires secure connection'
33
34
  else
34
35
  @protocol.write "\2" # request public key
35
36
  pkt = @protocol.read
@@ -40,7 +41,7 @@ class Mysql
40
41
  @protocol.write enc
41
42
  end
42
43
  else
43
- raise "invalid auth reply packet: #{data.inspect}"
44
+ raise ClientError, "invalid auth reply packet: #{data.inspect}"
44
45
  end
45
46
  pkt = @protocol.read
46
47
  end
@@ -2,6 +2,7 @@ require 'digest/sha1'
2
2
 
3
3
  class Mysql
4
4
  class Authenticator
5
+ # mysql_native_password
5
6
  class MysqlNativePassword
6
7
  # @param protocol [Mysql::Protocol]
7
8
  def initialize(protocol)
@@ -2,6 +2,7 @@ require 'openssl'
2
2
 
3
3
  class Mysql
4
4
  class Authenticator
5
+ # sha256_password
5
6
  class Sha256Password
6
7
  # @param protocol [Mysql::Protocol]
7
8
  def initialize(protocol)
@@ -1,4 +1,5 @@
1
1
  class Mysql
2
+ # authenticator
2
3
  class Authenticator
3
4
  @plugins = {}
4
5
 
@@ -33,10 +34,10 @@ class Mysql
33
34
  get(plugin) or raise ClientError, "unknown plugin: #{plugin}"
34
35
  end
35
36
 
36
- def authenticate(user, passwd, db, scramble, plugin_name)
37
+ def authenticate(user, passwd, db, scramble, plugin_name, connect_attrs)
37
38
  plugin = (get(plugin_name) || DummyPlugin).new(@protocol)
38
39
  pkt = plugin.authenticate(passwd, scramble) do |hashed|
39
- @protocol.write Protocol::AuthenticationPacket.serialize(@protocol.client_flags, 1024**3, @protocol.charset.number, user, hashed, db, plugin.name)
40
+ @protocol.write Protocol::AuthenticationPacket.serialize(@protocol.client_flags, 1024**3, @protocol.charset.number, user, hashed, db, plugin.name, connect_attrs)
40
41
  end
41
42
  while true
42
43
  res = Protocol::AuthenticationResultPacket.parse(pkt)
@@ -55,11 +56,12 @@ class Mysql
55
56
  end
56
57
  end
57
58
  else
58
- raise ClientError, "invalid packet: #{pkt.to_s}"
59
+ raise ClientError, "invalid packet: #{pkt}"
59
60
  end
60
61
  end
61
62
  end
62
63
 
64
+ # dummy plugin
63
65
  class DummyPlugin
64
66
  # @param protocol [Mysql::Protocol]
65
67
  def initialize(protocol)
@@ -75,7 +77,7 @@ class Mysql
75
77
  # @param scramble [String]
76
78
  # @yield [String] hashed password
77
79
  # @return [Mysql::Packet]
78
- def authenticate(passwd, scramble)
80
+ def authenticate(passwd, scramble) # rubocop:disable Lint/UnusedMethodArgument
79
81
  yield ''
80
82
  @protocol.read
81
83
  end
data/lib/mysql/charset.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: ascii-8bit
2
+
2
3
  # Copyright (C) 2008-2012 TOMITA Masahiro
3
4
  # mailto:tommy@tmtm.org
4
5
 
5
- #
6
6
  class Mysql
7
7
  # @!attribute [r] number
8
8
  # @private
@@ -327,14 +327,14 @@ class Mysql
327
327
  [307, "utf8mb4", "utf8mb4_ru_0900_as_cs", false],
328
328
  [308, "utf8mb4", "utf8mb4_zh_0900_as_cs", false],
329
329
  [309, "utf8mb4", "utf8mb4_0900_bin", false],
330
- ]
330
+ ].freeze
331
331
 
332
332
  # @private
333
- NUMBER_TO_CHARSET = {}
333
+ NUMBER_TO_CHARSET = {} # rubocop:disable Style/MutableConstant
334
334
  # @private
335
- COLLATION_TO_CHARSET = {}
335
+ COLLATION_TO_CHARSET = {} # rubocop:disable Style/MutableConstant
336
336
  # @private
337
- CHARSET_DEFAULT = {}
337
+ CHARSET_DEFAULT = {} # rubocop:disable Style/MutableConstant
338
338
  CHARSETS.each do |number, csname, clname, default|
339
339
  cs = Charset.new number, csname, clname
340
340
  NUMBER_TO_CHARSET[number] = cs
@@ -407,7 +407,7 @@ class Mysql
407
407
  "utf8" => Encoding::UTF_8,
408
408
  "utf8mb3" => Encoding::UTF_8,
409
409
  "utf8mb4" => Encoding::UTF_8,
410
- }
410
+ }.freeze
411
411
 
412
412
  # @private
413
413
  # @param [String] value
@@ -1,4 +1,5 @@
1
1
  # coding: ascii-8bit
2
+
2
3
  # Copyright (C) 2003 TOMITA Masahiro
3
4
  # mailto:tommy@tmtm.org
4
5
 
@@ -168,6 +169,13 @@ class Mysql
168
169
  REFRESH_OPTIMIZER_COSTS = 1 << 21
169
170
  REFRESH_PERSIST = 1 << 22
170
171
 
172
+ SESSION_TRACK_SYSTEM_VARIABLES = 0 # Session system variables
173
+ SESSION_TRACK_SCHEMA = 1 # Current schema
174
+ SESSION_TRACK_STATE_CHANGE = 2 # track session state changes
175
+ SESSION_TRACK_GTIDS = 3 # See also: session_track_gtids
176
+ SESSION_TRACK_TRANSACTION_CHARACTERISTICS = 4 # Transaction chistics
177
+ SESSION_TRACK_TRANSACTION_STATE = 5 # Transaction state
178
+
171
179
  class Field
172
180
  # Field type
173
181
  TYPE_DECIMAL = 0
data/lib/mysql/error.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  # coding: ascii-8bit
2
+
2
3
  # Copyright (C) 2003-2010 TOMITA Masahiro
3
4
  # mailto:tommy@tmtm.org
4
5
 
6
+ # Mysql
5
7
  class Mysql
8
+ # Mysql::Error
6
9
  class Error < StandardError
7
10
  ERRNO = 0
8
11
 
@@ -11,7 +14,7 @@ class Mysql
11
14
  errname = errname.to_s
12
15
  next unless errname =~ prefix_re
13
16
  errno = self.const_get errname
14
- excname = errname.sub(prefix_re,'').gsub(/(\A.|_.)([A-Z]+)/){$1+$2.downcase}.gsub(/_/,'')
17
+ excname = errname.sub(prefix_re, '').gsub(/(\A.|_.)([A-Z]+)/){$1+$2.downcase}.gsub(/_/, '')
15
18
  klass = Class.new self
16
19
  klass.const_set 'ERRNO', errno
17
20
  self.const_set excname, klass
@@ -32,7 +35,7 @@ class Mysql
32
35
 
33
36
  # server side error
34
37
  class ServerError < Error
35
- ERROR_MAP = {}
38
+ ERROR_MAP = {} # rubocop:disable Style/MutableConstant
36
39
 
37
40
  ER_ERROR_FIRST = 1000
38
41
  ER_HASHCHK = 1000
@@ -905,11 +908,11 @@ class Mysql
905
908
  end
906
909
 
907
910
  ServerError.define_error_class(/\AER_/)
908
- ServerError::ERROR_MAP.values.each{|v| Mysql.const_set v.name.split(/::/).last, v} # for compatibility
911
+ ServerError::ERROR_MAP.each_value{|v| Mysql.const_set v.name.split(/::/).last, v} # for compatibility
909
912
 
910
913
  # client side error
911
914
  class ClientError < Error
912
- ERROR_MAP = {}
915
+ ERROR_MAP = {} # rubocop:disable Style/MutableConstant
913
916
 
914
917
  CR_ERROR_FIRST = 2000
915
918
  CR_UNKNOWN_ERROR = 2000
@@ -991,5 +994,4 @@ class Mysql
991
994
  # protocol error
992
995
  class ProtocolError < ClientError
993
996
  end
994
-
995
997
  end
@@ -0,0 +1,95 @@
1
+ class Mysql
2
+ # @!visibility public
3
+ # Field class
4
+ class Field
5
+ # @return [String] database name
6
+ attr_reader :db
7
+ # @return [String] table name
8
+ attr_reader :table
9
+ # @return [String] original table name
10
+ attr_reader :org_table
11
+ # @return [String] field name
12
+ attr_reader :name
13
+ # @return [String] original field name
14
+ attr_reader :org_name
15
+ # @return [Integer] charset id number
16
+ attr_reader :charsetnr
17
+ # @return [Integer] field length
18
+ attr_reader :length
19
+ # @return [Integer] field type
20
+ attr_reader :type
21
+ # @return [Integer] flag
22
+ attr_reader :flags
23
+ # @return [Integer] number of decimals
24
+ attr_reader :decimals
25
+ # @return [String] defualt value
26
+ attr_reader :default
27
+ alias def default
28
+
29
+ # @private
30
+ attr_accessor :result
31
+
32
+ # @attr [Protocol::FieldPacket] packet
33
+ def initialize(packet)
34
+ @db, @table, @org_table, @name, @org_name, @charsetnr, @length, @type, @flags, @decimals, @default =
35
+ packet.db, packet.table, packet.org_table, packet.name, packet.org_name, packet.charsetnr, packet.length, packet.type, packet.flags, packet.decimals, packet.default
36
+ @db.force_encoding('utf-8')
37
+ @table.force_encoding('utf-8')
38
+ @org_table.force_encoding('utf-8')
39
+ @name.force_encoding('utf-8')
40
+ @org_name.force_encoding('utf-8')
41
+ @flags |= NUM_FLAG if is_num_type?
42
+ @max_length = nil
43
+ end
44
+
45
+ # @return [Hash] field information
46
+ def to_hash
47
+ {
48
+ "name" => @name,
49
+ "table" => @table,
50
+ "def" => @default,
51
+ "type" => @type,
52
+ "length" => @length,
53
+ "max_length" => max_length,
54
+ "flags" => @flags,
55
+ "decimals" => @decimals,
56
+ }
57
+ end
58
+
59
+ # @private
60
+ def inspect
61
+ "#<Mysql::Field:#{@name}>"
62
+ end
63
+
64
+ # @return [Boolean] true if numeric field.
65
+ def is_num?
66
+ @flags & NUM_FLAG != 0
67
+ end
68
+
69
+ # @return [Boolean] true if not null field.
70
+ def is_not_null?
71
+ @flags & NOT_NULL_FLAG != 0
72
+ end
73
+
74
+ # @return [Boolean] true if primary key field.
75
+ def is_pri_key?
76
+ @flags & PRI_KEY_FLAG != 0
77
+ end
78
+
79
+ # @return [Integer] maximum width of the field for the result set
80
+ def max_length
81
+ return @max_length if @max_length
82
+ @max_length = 0
83
+ @result&.calculate_field_max_length
84
+ @max_length
85
+ end
86
+
87
+ attr_writer :max_length
88
+
89
+ private
90
+
91
+ def is_num_type?
92
+ [TYPE_DECIMAL, TYPE_TINY, TYPE_SHORT, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_LONGLONG, TYPE_INT24].include?(@type) || (@type == TYPE_TIMESTAMP && (@length == 14 || @length == 8))
93
+ end
94
+ end
95
+ end
data/lib/mysql/packet.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # coding: ascii-8bit
2
+
2
3
  class Mysql
4
+ # Mysql::Packet
3
5
  class Packet
4
6
  # convert Numeric to LengthCodedBinary
5
7
  def self.lcb(num)
@@ -49,21 +51,21 @@ class Mysql
49
51
  end
50
52
 
51
53
  def string
52
- str = @data.unpack('Z*').first
54
+ str = @data.unpack1('Z*')
53
55
  @data.slice!(0, str.length+1)
54
56
  str
55
57
  end
56
58
 
57
59
  def utiny
58
- @data.slice!(0, 1).unpack('C').first
60
+ @data.slice!(0, 1).unpack1('C')
59
61
  end
60
62
 
61
63
  def ushort
62
- @data.slice!(0, 2).unpack('v').first
64
+ @data.slice!(0, 2).unpack1('v')
63
65
  end
64
66
 
65
67
  def ulong
66
- @data.slice!(0, 4).unpack('V').first
68
+ @data.slice!(0, 4).unpack1('V')
67
69
  end
68
70
 
69
71
  def eof?
@@ -73,6 +75,5 @@ class Mysql
73
75
  def to_s
74
76
  @data
75
77
  end
76
-
77
78
  end
78
79
  end