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.
@@ -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