ruby-mysql 4.0.0 → 4.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13656e5ef8a79113c2dce1fac6f1a47e510c2cd6bfb9958518a966b349bdbb13
4
- data.tar.gz: 55a2f5e70d73add2e6d71eb0bb02e19d0d498cacd46c78f0415f8f31620c76ab
3
+ metadata.gz: b0fae682668684b92f92ebf353cc0bcfaaeffc5afc922a82f6203fce60fc21ce
4
+ data.tar.gz: d289a2371c8cf2e60188ee6c0364640297116629c15a825b24048914b7470bc4
5
5
  SHA512:
6
- metadata.gz: a01515533387aaa8280be8c5244b80d4b245052e2750732f03c6198827dd5975a32f8f472481ba44b14f731135b31aa3744180475444388c7e1158d49bf95556
7
- data.tar.gz: 53fe3127e8c358f77a3874e26ae1de91dcd72af11fcd45f40b18875c21a884d21105eccce4808b308bde1e938970316cf6d7f54ac21f9bf94cebb9fc2b8dcbd5
6
+ metadata.gz: 6581d6cc73e910bbce33bb6024eda12971a0757a8d53ffa79b7c2fee983c19007821250af2a717217f6213b0a8a7b34285731988fba77f67b66496a03342e035
7
+ data.tar.gz: 8be3f83e953ea70aa80da335f8f2b4dbb7bd23bdb463b1abf3d6625e16f73558ff485112a958ffa737c10febb115c7de7538c8994b420fdd56525052e22c440b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [4.2.0] - 2024-12-23
2
+
3
+ - Skip storing records if auto_store_result is false <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/6>
4
+ - Ruby 3.3 & MySQL 8.4 <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/7>
5
+ - COM_KILL and COM_REFRESH are not supported by MySQL 8.4 <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/8>
6
+ - add Mysql#use_result and fix Mysql::Result#size <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/9>
7
+ - avoid warning for Ruby 3.4 <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/10>
8
+ - bundle update and rubocop <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/11>
9
+
10
+ ## [4.1.0] - 2023-10-08
11
+
12
+ - Support geometry column <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/1>
13
+ - 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>
14
+ - Allow for existing socket when connecting <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/2> <https://gitlab.com/tmtms/ruby-mysql/-/merge_requests/5>
15
+ - add `io` keyword parameter
16
+
1
17
  ## [4.0.0] - 2022-11-09
2
18
 
3
19
  ### Incompatible changes
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>
data/lib/mysql/error.rb CHANGED
@@ -14,7 +14,7 @@ class Mysql
14
14
  errname = errname.to_s
15
15
  next unless errname =~ prefix_re
16
16
  errno = self.const_get errname
17
- 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('_', '')
18
18
  klass = Class.new self
19
19
  klass.const_set 'ERRNO', errno
20
20
  self.const_set excname, klass
@@ -908,7 +908,7 @@ class Mysql
908
908
  end
909
909
 
910
910
  ServerError.define_error_class(/\AER_/)
911
- ServerError::ERROR_MAP.each_value{|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
912
912
 
913
913
  # client side error
914
914
  class ClientError < Error
@@ -24,7 +24,7 @@ class Mysql
24
24
  # @return [Object] converted value.
25
25
  def self.net2value(pkt, type, unsigned)
26
26
  case type
27
- when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_BLOB, Field::TYPE_JSON
27
+ when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_BLOB, Field::TYPE_JSON, Field::TYPE_GEOMETRY
28
28
  return pkt.lcs
29
29
  when Field::TYPE_NEWDECIMAL
30
30
  s = pkt.lcs
@@ -160,6 +160,7 @@ class Mysql
160
160
  # @option :ssl_mode [Integer]
161
161
  # @option :ssl_context_params [Hash<:Symbol, String>]
162
162
  # @option :get_server_public_key [Boolean]
163
+ # @option :io [BasicSocket, OpenSSL::SSL::SSLSocket] Existing socket instance that will be used instead of creating a new socket
163
164
  # @raise [ClientError] connection timeout
164
165
  def initialize(opts)
165
166
  @mutex = Mutex.new
@@ -172,7 +173,9 @@ class Mysql
172
173
  set_state :INIT
173
174
  @get_server_public_key = @opts[:get_server_public_key]
174
175
  begin
175
- if @opts[:host].nil? or @opts[:host].empty? or @opts[:host] == "localhost"
176
+ if @opts[:io]
177
+ @socket = @opts[:io]
178
+ elsif @opts[:host].nil? or @opts[:host].empty? or @opts[:host] == "localhost"
176
179
  socket = @opts[:socket] || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
177
180
  @socket = Socket.unix(socket)
178
181
  else
@@ -185,7 +188,7 @@ class Mysql
185
188
  end
186
189
 
187
190
  def close
188
- @socket.close
191
+ @socket.close rescue nil
189
192
  end
190
193
 
191
194
  # initial negotiate and authenticate.
@@ -510,7 +513,7 @@ class Mysql
510
513
  # @return [Packet] packet data
511
514
  # @rails [ProtocolError] invalid packet sequence number
512
515
  def read
513
- data = ''
516
+ data = +''
514
517
  len = nil
515
518
  begin
516
519
  timeout = @state == :INIT ? @opts[:connect_timeout] : @opts[:read_timeout]
@@ -551,7 +554,7 @@ class Mysql
551
554
 
552
555
  def read_timeout(len, timeout)
553
556
  return @socket.read(len) if timeout.nil? || timeout == 0
554
- result = ''
557
+ result = +''
555
558
  e = Time.now + timeout
556
559
  while result.size < len
557
560
  now = Time.now
@@ -786,7 +789,7 @@ class Mysql
786
789
  username,
787
790
  Packet.lcs(scrambled_password),
788
791
  ]
789
- pack = "VVCa23Z*A*"
792
+ pack = +"VVCa23Z*A*"
790
793
  if databasename
791
794
  data.push databasename
792
795
  pack.concat "Z*"
@@ -814,7 +817,7 @@ class Mysql
814
817
  class ExecutePacket
815
818
  def self.serialize(statement_id, cursor_type, values)
816
819
  nbm = null_bitmap values
817
- netvalues = ""
820
+ netvalues = +""
818
821
  types = values.map do |v|
819
822
  t, n = Protocol.value2net v
820
823
  netvalues.concat n unless v.nil?
data/lib/mysql/result.rb CHANGED
@@ -15,8 +15,9 @@ class Mysql
15
15
  def initialize(fields, protocol, record_class, **opts)
16
16
  @fields = fields
17
17
  @field_index = 0 # index of field
18
- @records = [] # all records
18
+ @records = nil # all records
19
19
  @index = 0 # index of record
20
+ @size = 0 # retrieved record count
20
21
  @fieldname_with_table = nil
21
22
  @protocol = protocol
22
23
  @record_class = record_class
@@ -25,6 +26,7 @@ class Mysql
25
26
 
26
27
  def retrieve
27
28
  @records = @protocol.retr_all_records(@record_class)
29
+ @size = @records.size
28
30
  end
29
31
 
30
32
  # ignore
@@ -34,22 +36,22 @@ class Mysql
34
36
  end
35
37
 
36
38
  # @return [Integer] number of record
37
- def size
38
- @records.size
39
- end
39
+ attr_reader :size
40
40
  alias num_rows size
41
+ alias count size
41
42
 
42
43
  # @return [Array] current record data
43
44
  def fetch(**)
44
- if @index < @records.size
45
+ if @records && @index < @records.size
45
46
  @records[@index] = @records[@index].to_a unless @records[@index].is_a? Array
46
47
  @index += 1
47
48
  return @records[@index-1]
48
49
  end
49
50
  rec = @protocol.retr_record(@record_class)&.to_a
50
51
  return nil unless rec
51
- @records[@index] = rec
52
+ @records[@index] = rec if @records
52
53
  @index += 1
54
+ @size += 1
53
55
  return rec
54
56
  end
55
57
  alias fetch_row fetch
@@ -133,7 +135,6 @@ class Mysql
133
135
  # @private
134
136
  # @param [Array<Mysql::Field>] fields
135
137
  # @param [Mysql::Protocol] protocol
136
- # @param [Boolean] auto_store_result
137
138
  def initialize(fields, protocol=nil, **opts)
138
139
  super fields, protocol, RawRecord, **opts
139
140
  return unless protocol
@@ -182,7 +183,7 @@ class Mysql
182
183
  when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
183
184
  Time.parse(s) rescue nil
184
185
  when Field::TYPE_TIME
185
- h, m, sec = s.split(/:/)
186
+ h, m, sec = s.split(':')
186
187
  if s =~ /\A-/
187
188
  h.to_i*3600 - m.to_i*60 - sec.to_f
188
189
  else
data/lib/mysql.rb CHANGED
@@ -22,7 +22,7 @@ class Mysql
22
22
  require_relative "mysql/protocol"
23
23
  require_relative "mysql/packet"
24
24
 
25
- VERSION = -'4.0.0' # Version number of this library
25
+ VERSION = -'4.2.0' # Version number of this library
26
26
  MYSQL_UNIX_PORT = -"/tmp/mysql.sock" # UNIX domain socket filename
27
27
  MYSQL_TCP_PORT = 3306 # TCP socket port number
28
28
 
@@ -40,6 +40,8 @@ class Mysql
40
40
  # @return [String, nil] socket filename
41
41
  # @!attribute [rw] flags
42
42
  # @return [Integer, nil]
43
+ # @!attribute [rw] io
44
+ # @return [[BasicSocket, OpenSSL::SSL::SSLSocket], nil]
43
45
  # @!attribute [rw] connect_timeout
44
46
  # @return [Numeric, nil]
45
47
  # @!attribute [rw] read_timeout
@@ -78,6 +80,7 @@ class Mysql
78
80
  port: nil,
79
81
  socket: nil,
80
82
  flags: 0,
83
+ io: nil,
81
84
  charset: nil,
82
85
  connect_timeout: nil,
83
86
  read_timeout: nil,
@@ -102,9 +105,6 @@ class Mysql
102
105
  # @return [Array<Mysql::Field>] fields of result set
103
106
  attr_reader :fields
104
107
 
105
- # @return [Mysql::Result]
106
- attr_reader :result
107
-
108
108
  class << self
109
109
  # Make Mysql object and connect to mysqld.
110
110
  # parameter is same as arguments for {#initialize}.
@@ -172,6 +172,7 @@ class Mysql
172
172
  # @option opts :ssl_context_params [Hash<Symbol, String>]
173
173
  # @option opts :get_server_public_key [Boolean]
174
174
  # @option opts :connect_attrs [Hash]
175
+ # @option opts :io [BasicSocket, OpenSSL::SSL::SSLSocket] Existing socket instance that will be used instead of creating a new socket
175
176
  def initialize(*args, **opts)
176
177
  @fields = nil
177
178
  @result = nil
@@ -355,8 +356,7 @@ class Mysql
355
356
  # @param [Integer] pid thread id
356
357
  # @return [Mysql] self
357
358
  def kill(pid)
358
- check_connection
359
- @protocol.kill_command pid
359
+ query "KILL #{pid}"
360
360
  self
361
361
  end
362
362
 
@@ -375,11 +375,13 @@ class Mysql
375
375
  check_connection
376
376
  @fields = nil
377
377
  begin
378
+ @result = nil
378
379
  @protocol.query_command str
379
380
  if block
380
381
  while true
382
+ @result = nil
381
383
  @protocol.get_result
382
- res = store_result(**opts)
384
+ res = result(**opts)
383
385
  block.call res if res || opts[:yield_null_result]
384
386
  break unless more_results?
385
387
  end
@@ -387,7 +389,7 @@ class Mysql
387
389
  end
388
390
  @protocol.get_result
389
391
  return self unless opts[:return_result]
390
- return store_result(**opts)
392
+ return result(**opts)
391
393
  rescue ServerError => e
392
394
  @last_error = e
393
395
  @sqlstate = e.sqlstate
@@ -395,16 +397,32 @@ class Mysql
395
397
  end
396
398
  end
397
399
 
398
- # Get all data for last query.
400
+ # return Mysql::Result for last query.
399
401
  # @return [Mysql::Result]
400
402
  # @return [nil] if no results
401
- def store_result(**opts)
403
+ def result(**opts)
402
404
  return nil if @protocol.field_count.nil? || @protocol.field_count == 0
405
+ return @result if @result
403
406
  @fields = @protocol.retr_fields
404
407
  opts = @opts.merge(opts)
405
408
  @result = Result.new(@fields, @protocol, **opts)
406
409
  end
407
410
 
411
+ # return Mysql::Result for last query with all data.
412
+ # @return [Mysql::Result]
413
+ # @return [nil] if no results
414
+ def store_result(**opts)
415
+ result(auto_store_result: true, **opts.merge)
416
+ end
417
+
418
+ # return Mysql::Result for last query without data.
419
+ # Mysql::Result#data_seek, row_tell, row_seek cannot be used.
420
+ # @return [Mysql::Result]
421
+ # @return [nil] if no results
422
+ def use_result(**opts)
423
+ result(auto_store_result: false, **opts.merge)
424
+ end
425
+
408
426
  # @return [Integer] Thread ID
409
427
  def thread_id
410
428
  check_connection
@@ -433,8 +451,8 @@ class Mysql
433
451
  return nil unless more_results?
434
452
  opts = @opts.merge(opts)
435
453
  @protocol.get_result
436
- @fields = nil
437
- return store_result(**opts) if opts[:return_result]
454
+ @result = @fields = nil
455
+ return result(**opts) if opts[:return_result]
438
456
  true
439
457
  end
440
458
 
@@ -468,8 +486,30 @@ class Mysql
468
486
  # @param [Integer] op operation. Use Mysql::REFRESH_* value.
469
487
  # @return [Mysql] self
470
488
  def refresh(op)
471
- check_connection
472
- @protocol.refresh_command op
489
+ if server_version < 80400
490
+ check_connection
491
+ @protocol.refresh_command op
492
+ else
493
+ q = case op
494
+ when REFRESH_GRANT
495
+ "FLUSH PRIVILEGES"
496
+ when REFRESH_LOG
497
+ "FLUSH LOGS"
498
+ when REFRESH_TABLES
499
+ "FLUSH TABLES"
500
+ when REFRESH_HOSTS
501
+ "TRUNCATE TABLE performance_schema.host_cache"
502
+ when REFRESH_STATUS
503
+ "FLUSH STATUS"
504
+ when REFRESH_SLAVE
505
+ "RESET REPLICA"
506
+ when REFRESH_MASTER
507
+ "RESET BINARY LOGS AND GTIDS"
508
+ else
509
+ raise "unsupported operation for #{server_version}"
510
+ end
511
+ query q
512
+ end
473
513
  self
474
514
  end
475
515
 
metadata CHANGED
@@ -1,57 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-mysql
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomita Masahiro
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-11-13 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: power_assert
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rubocop
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
10
+ date: 2024-12-23 00:00:00.000000000 Z
11
+ dependencies: []
55
12
  description: This is MySQL connector. pure Ruby version
56
13
  email: tommy@tmtm.org
57
14
  executables: []
@@ -81,7 +38,6 @@ metadata:
81
38
  documentation_uri: https://www.rubydoc.info/gems/ruby-mysql
82
39
  source_code_uri: http://gitlab.com/tmtms/ruby-mysql
83
40
  rubygems_mfa_required: 'true'
84
- post_install_message:
85
41
  rdoc_options: []
86
42
  require_paths:
87
43
  - lib
@@ -89,15 +45,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
45
  requirements:
90
46
  - - ">="
91
47
  - !ruby/object:Gem::Version
92
- version: 2.6.0
48
+ version: 3.0.0
93
49
  required_rubygems_version: !ruby/object:Gem::Requirement
94
50
  requirements:
95
51
  - - ">="
96
52
  - !ruby/object:Gem::Version
97
53
  version: '0'
98
54
  requirements: []
99
- rubygems_version: 3.4.0.dev
100
- signing_key:
55
+ rubygems_version: 3.6.0.dev
101
56
  specification_version: 4
102
57
  summary: MySQL connector
103
58
  test_files: []