ruby-mysql 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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