ruby-mysql 3.0.1 → 4.0.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 +4 -4
- data/CHANGELOG.md +50 -0
- data/lib/mysql/authenticator/caching_sha2_password.rb +3 -2
- data/lib/mysql/authenticator/mysql_native_password.rb +1 -0
- data/lib/mysql/authenticator/sha256_password.rb +1 -0
- data/lib/mysql/authenticator.rb +6 -4
- data/lib/mysql/charset.rb +6 -6
- data/lib/mysql/constants.rb +8 -0
- data/lib/mysql/error.rb +7 -5
- data/lib/mysql/field.rb +95 -0
- data/lib/mysql/packet.rb +6 -5
- data/lib/mysql/protocol.rb +294 -190
- data/lib/mysql/result.rb +212 -0
- data/lib/mysql/stmt.rb +219 -0
- data/lib/mysql.rb +109 -523
- metadata +56 -14
- data/test/test_mysql.rb +0 -1603
- data/test/test_mysql_packet.rb +0 -149
data/lib/mysql/result.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
class Mysql
|
2
|
+
# @!visibility public
|
3
|
+
# Result set
|
4
|
+
class ResultBase
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
# @return [Array<Mysql::Field>] field list
|
8
|
+
attr_reader :fields
|
9
|
+
alias fetch_fields fields
|
10
|
+
|
11
|
+
# @return [Mysql::StatementResult]
|
12
|
+
attr_reader :result
|
13
|
+
|
14
|
+
# @param [Array of Mysql::Field] fields
|
15
|
+
def initialize(fields, protocol, record_class, **opts)
|
16
|
+
@fields = fields
|
17
|
+
@field_index = 0 # index of field
|
18
|
+
@records = [] # all records
|
19
|
+
@index = 0 # index of record
|
20
|
+
@fieldname_with_table = nil
|
21
|
+
@protocol = protocol
|
22
|
+
@record_class = record_class
|
23
|
+
@opts = opts
|
24
|
+
end
|
25
|
+
|
26
|
+
def retrieve
|
27
|
+
@records = @protocol.retr_all_records(@record_class)
|
28
|
+
end
|
29
|
+
|
30
|
+
# ignore
|
31
|
+
# @return [void]
|
32
|
+
def free
|
33
|
+
# dummy
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Integer] number of record
|
37
|
+
def size
|
38
|
+
@records.size
|
39
|
+
end
|
40
|
+
alias num_rows size
|
41
|
+
|
42
|
+
# @return [Array] current record data
|
43
|
+
def fetch(**)
|
44
|
+
if @index < @records.size
|
45
|
+
@records[@index] = @records[@index].to_a unless @records[@index].is_a? Array
|
46
|
+
@index += 1
|
47
|
+
return @records[@index-1]
|
48
|
+
end
|
49
|
+
rec = @protocol.retr_record(@record_class)&.to_a
|
50
|
+
return nil unless rec
|
51
|
+
@records[@index] = rec
|
52
|
+
@index += 1
|
53
|
+
return rec
|
54
|
+
end
|
55
|
+
alias fetch_row fetch
|
56
|
+
|
57
|
+
# Return data of current record as Hash.
|
58
|
+
# The hash key is field name.
|
59
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
60
|
+
# @return [Hash] current record data
|
61
|
+
def fetch_hash(**opts)
|
62
|
+
row = fetch(**opts)
|
63
|
+
return nil unless row
|
64
|
+
with_table = @opts.merge(opts)[:with_table]
|
65
|
+
if with_table and @fieldname_with_table.nil?
|
66
|
+
@fieldname_with_table = @fields.map{|f| [f.table, f.name].join(".")}
|
67
|
+
end
|
68
|
+
ret = {}
|
69
|
+
@fields.each_index do |i|
|
70
|
+
fname = with_table ? @fieldname_with_table[i] : @fields[i].name
|
71
|
+
ret[fname] = row[i]
|
72
|
+
end
|
73
|
+
ret
|
74
|
+
end
|
75
|
+
|
76
|
+
# Iterate block with record.
|
77
|
+
# @yield [Array] record data
|
78
|
+
# @return [self] self. If block is not specified, this returns Enumerator.
|
79
|
+
def each(**opts, &block)
|
80
|
+
@index = 0
|
81
|
+
return enum_for(:each, **opts) unless block
|
82
|
+
while (rec = fetch(**opts))
|
83
|
+
block.call rec
|
84
|
+
end
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
# Iterate block with record as Hash.
|
89
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
90
|
+
# @yield [Hash] record data
|
91
|
+
# @return [self] self. If block is not specified, this returns Enumerator.
|
92
|
+
def each_hash(**opts, &block)
|
93
|
+
@index = 0
|
94
|
+
return enum_for(:each_hash, **opts) unless block
|
95
|
+
while (rec = fetch_hash(**opts))
|
96
|
+
block.call rec
|
97
|
+
end
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# Set record position
|
102
|
+
# @param [Integer] n record index
|
103
|
+
# @return [self] self
|
104
|
+
def data_seek(n)
|
105
|
+
@index = n
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [Integer] current record position
|
110
|
+
def row_tell
|
111
|
+
@index
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set current position of record
|
115
|
+
# @param [Integer] n record index
|
116
|
+
# @return [Integer] previous position
|
117
|
+
def row_seek(n)
|
118
|
+
ret = @index
|
119
|
+
@index = n
|
120
|
+
ret
|
121
|
+
end
|
122
|
+
|
123
|
+
# Server status value
|
124
|
+
# @return [Integer] server status value
|
125
|
+
def server_status
|
126
|
+
@protocol.server_status
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# @!visibility public
|
131
|
+
# Result set for simple query
|
132
|
+
class Result < ResultBase
|
133
|
+
# @private
|
134
|
+
# @param [Array<Mysql::Field>] fields
|
135
|
+
# @param [Mysql::Protocol] protocol
|
136
|
+
# @param [Boolean] auto_store_result
|
137
|
+
def initialize(fields, protocol=nil, **opts)
|
138
|
+
super fields, protocol, RawRecord, **opts
|
139
|
+
return unless protocol
|
140
|
+
fields.each{|f| f.result = self} # for calculating max_field
|
141
|
+
retrieve if @opts.merge(opts)[:auto_store_result]
|
142
|
+
end
|
143
|
+
|
144
|
+
def fetch(**opts)
|
145
|
+
rec = super
|
146
|
+
rec = rec.map.with_index{|s, i| convert_type(@fields[i], s)} if rec && @opts.merge(opts)[:cast]
|
147
|
+
rec
|
148
|
+
end
|
149
|
+
alias fetch_row fetch
|
150
|
+
|
151
|
+
# @private
|
152
|
+
# calculate max_length of all fields
|
153
|
+
def calculate_field_max_length
|
154
|
+
return unless @records
|
155
|
+
max_length = Array.new(@fields.size, 0)
|
156
|
+
@records.each_with_index do |rec, i|
|
157
|
+
rec = @records[i] = rec.to_a if rec.is_a? RawRecord
|
158
|
+
max_length.each_index do |j|
|
159
|
+
max_length[j] = rec[j].to_s.length if rec[j] && rec[j].to_s.length > max_length[j]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
max_length.each_with_index do |len, i|
|
163
|
+
@fields[i].max_length = len
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def convert_type(f, s)
|
170
|
+
return nil if s.nil?
|
171
|
+
case f.type
|
172
|
+
when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_BLOB, Field::TYPE_JSON
|
173
|
+
s
|
174
|
+
when Field::TYPE_NEWDECIMAL
|
175
|
+
s =~ /\./ && s !~ /\.0*\z/ ? BigDecimal(s) : s.to_i
|
176
|
+
when Field::TYPE_TINY, Field::TYPE_SHORT, Field::TYPE_INT24, Field::TYPE_LONG, Field::TYPE_LONGLONG
|
177
|
+
s.to_i
|
178
|
+
when Field::TYPE_FLOAT, Field::TYPE_DOUBLE
|
179
|
+
s.to_f
|
180
|
+
when Field::TYPE_DATE
|
181
|
+
Date.parse(s) rescue nil
|
182
|
+
when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
|
183
|
+
Time.parse(s) rescue nil
|
184
|
+
when Field::TYPE_TIME
|
185
|
+
h, m, sec = s.split(/:/)
|
186
|
+
if s =~ /\A-/
|
187
|
+
h.to_i*3600 - m.to_i*60 - sec.to_f
|
188
|
+
else
|
189
|
+
h.to_i*3600 + m.to_i*60 + sec.to_f
|
190
|
+
end
|
191
|
+
when Field::TYPE_YEAR
|
192
|
+
s.to_i
|
193
|
+
when Field::TYPE_BIT
|
194
|
+
s.b
|
195
|
+
else
|
196
|
+
s.b
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# @!visibility private
|
202
|
+
# Result set for prepared statement
|
203
|
+
class StatementResult < ResultBase
|
204
|
+
# @private
|
205
|
+
# @param [Array<Mysql::Field>] fields
|
206
|
+
# @param [Mysql::Protocol] protocol
|
207
|
+
def initialize(fields, protocol, **opts)
|
208
|
+
super fields, protocol, StmtRawRecord, **opts
|
209
|
+
retrieve if @opts.merge(opts)[:auto_store_result]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
data/lib/mysql/stmt.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
class Mysql
|
2
|
+
# @!visibility public
|
3
|
+
# Prepared statement
|
4
|
+
# @!attribute [r] affected_rows
|
5
|
+
# @return [Integer]
|
6
|
+
# @!attribute [r] insert_id
|
7
|
+
# @return [Integer]
|
8
|
+
# @!attribute [r] server_status
|
9
|
+
# @return [Integer]
|
10
|
+
# @!attribute [r] warning_count
|
11
|
+
# @return [Integer]
|
12
|
+
# @!attribute [r] param_count
|
13
|
+
# @return [Integer]
|
14
|
+
# @!attribute [r] fields
|
15
|
+
# @return [Array<Mysql::Field>]
|
16
|
+
# @!attribute [r] sqlstate
|
17
|
+
# @return [String]
|
18
|
+
class Stmt
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
attr_reader :affected_rows, :info, :insert_id, :server_status, :warning_count
|
22
|
+
attr_reader :param_count, :fields, :sqlstate
|
23
|
+
|
24
|
+
# @private
|
25
|
+
def self.finalizer(protocol, statement_id)
|
26
|
+
proc do
|
27
|
+
protocol.gc_stmt statement_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private
|
32
|
+
# @param [Mysql::Protocol] protocol
|
33
|
+
def initialize(protocol, **opts)
|
34
|
+
@protocol = protocol
|
35
|
+
@opts = opts
|
36
|
+
@statement_id = nil
|
37
|
+
@affected_rows = @insert_id = @server_status = @warning_count = 0
|
38
|
+
@sqlstate = "00000"
|
39
|
+
@param_count = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# @private
|
43
|
+
# parse prepared-statement and return {Mysql::Stmt} object
|
44
|
+
# @param [String] str query string
|
45
|
+
# @return self
|
46
|
+
def prepare(str)
|
47
|
+
raise ClientError, 'MySQL client is not connected' unless @protocol
|
48
|
+
close
|
49
|
+
begin
|
50
|
+
@sqlstate = "00000"
|
51
|
+
@statement_id, @param_count, @fields = @protocol.stmt_prepare_command(str)
|
52
|
+
rescue ServerError => e
|
53
|
+
@last_error = e
|
54
|
+
@sqlstate = e.sqlstate
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(@protocol, @statement_id))
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Execute prepared statement.
|
62
|
+
# @param [Object] values values passed to query
|
63
|
+
# @return [Mysql::Result] if return_result is true and the query returns result set.
|
64
|
+
# @return [nil] if return_result is true and the query does not return result set.
|
65
|
+
# @return [self] if return_result is false or block is specified.
|
66
|
+
def execute(*values, **opts, &block)
|
67
|
+
raise ClientError, "Invalid statement handle" unless @statement_id
|
68
|
+
raise ClientError, "not prepared" unless @param_count
|
69
|
+
raise ClientError, "parameter count mismatch" if values.length != @param_count
|
70
|
+
values = values.map{|v| @protocol.charset.convert v}
|
71
|
+
opts = @opts.merge(opts)
|
72
|
+
begin
|
73
|
+
@sqlstate = "00000"
|
74
|
+
@protocol.stmt_execute_command @statement_id, values
|
75
|
+
@fields = @result = nil
|
76
|
+
if block
|
77
|
+
while true
|
78
|
+
get_result
|
79
|
+
res = store_result(**opts)
|
80
|
+
block.call res if res || opts[:yield_null_result]
|
81
|
+
break unless more_results?
|
82
|
+
end
|
83
|
+
return self
|
84
|
+
end
|
85
|
+
get_result
|
86
|
+
return self unless opts[:return_result]
|
87
|
+
return store_result(**opts)
|
88
|
+
rescue ServerError => e
|
89
|
+
@last_error = e
|
90
|
+
@sqlstate = e.sqlstate
|
91
|
+
raise
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_result
|
96
|
+
@protocol.get_result
|
97
|
+
@affected_rows, @insert_id, @server_status, @warning_count, @info =
|
98
|
+
@protocol.affected_rows, @protocol.insert_id, @protocol.server_status, @protocol.warning_count, @protocol.message
|
99
|
+
end
|
100
|
+
|
101
|
+
def store_result(**opts)
|
102
|
+
return nil if @protocol.field_count.nil? || @protocol.field_count == 0
|
103
|
+
@fields = @protocol.retr_fields
|
104
|
+
opts = @opts.merge(opts)
|
105
|
+
@result = StatementResult.new(@fields, @protocol, **opts)
|
106
|
+
end
|
107
|
+
|
108
|
+
def more_results?
|
109
|
+
@protocol.more_results?
|
110
|
+
end
|
111
|
+
|
112
|
+
# execute next query if precedure is called.
|
113
|
+
# @return [Mysql::StatementResult] result set of query if return_result is true.
|
114
|
+
# @return [true] if return_result is false and result exists.
|
115
|
+
# @return [nil] query returns no results or no more results.
|
116
|
+
def next_result(**opts)
|
117
|
+
return nil unless more_results?
|
118
|
+
opts = @opts.merge(opts)
|
119
|
+
@fields = @result = nil
|
120
|
+
get_result
|
121
|
+
return self unless opts[:return_result]
|
122
|
+
return store_result(**opts)
|
123
|
+
rescue ServerError => e
|
124
|
+
@last_error = e
|
125
|
+
@sqlstate = e.sqlstate
|
126
|
+
raise
|
127
|
+
end
|
128
|
+
|
129
|
+
# Close prepared statement
|
130
|
+
# @return [void]
|
131
|
+
def close
|
132
|
+
ObjectSpace.undefine_finalizer(self)
|
133
|
+
@protocol.stmt_close_command @statement_id if @statement_id
|
134
|
+
@statement_id = nil
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [Array] current record data
|
138
|
+
def fetch(**opts)
|
139
|
+
@result.fetch(**opts)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Return data of current record as Hash.
|
143
|
+
# The hash key is field name.
|
144
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
145
|
+
# @return [Hash] record data
|
146
|
+
def fetch_hash(**opts)
|
147
|
+
@result.fetch_hash(**opts)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Iterate block with record.
|
151
|
+
# @yield [Array] record data
|
152
|
+
# @return [Mysql::Stmt] self
|
153
|
+
# @return [Enumerator] If block is not specified
|
154
|
+
def each(**opts, &block)
|
155
|
+
return enum_for(:each, **opts) unless block
|
156
|
+
while (rec = fetch(*opts))
|
157
|
+
block.call rec
|
158
|
+
end
|
159
|
+
self
|
160
|
+
end
|
161
|
+
|
162
|
+
# Iterate block with record as Hash.
|
163
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
164
|
+
# @yield [Hash] record data
|
165
|
+
# @return [Mysql::Stmt] self
|
166
|
+
# @return [Enumerator] If block is not specified
|
167
|
+
def each_hash(**opts, &block)
|
168
|
+
return enum_for(:each_hash, **opts) unless block
|
169
|
+
while (rec = fetch_hash(**opts))
|
170
|
+
block.call rec
|
171
|
+
end
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [Integer] number of record
|
176
|
+
def size
|
177
|
+
@result.size
|
178
|
+
end
|
179
|
+
alias num_rows size
|
180
|
+
|
181
|
+
# Set record position
|
182
|
+
# @param [Integer] n record index
|
183
|
+
# @return [void]
|
184
|
+
def data_seek(n)
|
185
|
+
@result.data_seek(n)
|
186
|
+
end
|
187
|
+
|
188
|
+
# @return [Integer] current record position
|
189
|
+
def row_tell
|
190
|
+
@result.row_tell
|
191
|
+
end
|
192
|
+
|
193
|
+
# Set current position of record
|
194
|
+
# @param [Integer] n record index
|
195
|
+
# @return [Integer] previous position
|
196
|
+
def row_seek(n)
|
197
|
+
@result.row_seek(n)
|
198
|
+
end
|
199
|
+
|
200
|
+
# @return [Integer] number of columns for last query
|
201
|
+
def field_count
|
202
|
+
@fields.length
|
203
|
+
end
|
204
|
+
|
205
|
+
# ignore
|
206
|
+
# @return [void]
|
207
|
+
def free_result
|
208
|
+
# dummy
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns Mysql::Result object that is empty.
|
212
|
+
# Use fields to get list of fields.
|
213
|
+
# @return [Mysql::Result]
|
214
|
+
def result_metadata
|
215
|
+
return nil if @fields.empty?
|
216
|
+
Result.new @fields
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|