v / vlib / db / mysql / stmt.c.v
365 lines · 312 sloc · 11.24 KB · e683da2db6194ba93f5721e1d5556f6c6e5f5216
Raw
1module mysql
2
3@[typedef]
4pub struct C.MYSQL_STMT {
5 mysql &C.MYSQL
6 stmt_id u32
7}
8
9@[typedef]
10pub struct C.MYSQL_BIND {
11mut:
12 buffer_type int
13 buffer voidptr
14 buffer_length u32
15 length &u32
16 is_null &bool
17}
18
19const mysql_type_decimal = C.MYSQL_TYPE_DECIMAL
20const mysql_type_tiny = C.MYSQL_TYPE_TINY
21const mysql_type_short = C.MYSQL_TYPE_SHORT
22const mysql_type_long = C.MYSQL_TYPE_LONG
23const mysql_type_float = C.MYSQL_TYPE_FLOAT
24const mysql_type_double = C.MYSQL_TYPE_DOUBLE
25const mysql_type_null = C.MYSQL_TYPE_NULL
26const mysql_type_timestamp = C.MYSQL_TYPE_TIMESTAMP
27const mysql_type_longlong = C.MYSQL_TYPE_LONGLONG
28const mysql_type_int24 = C.MYSQL_TYPE_INT24
29const mysql_type_date = C.MYSQL_TYPE_DATE
30const mysql_type_time = C.MYSQL_TYPE_TIME
31const mysql_type_datetime = C.MYSQL_TYPE_DATETIME
32const mysql_type_year = C.MYSQL_TYPE_YEAR
33const mysql_type_varchar = C.MYSQL_TYPE_VARCHAR
34const mysql_type_bit = C.MYSQL_TYPE_BIT
35const mysql_type_timestamp22 = C.MYSQL_TYPE_TIMESTAMP
36const mysql_type_json = C.MYSQL_TYPE_JSON
37const mysql_type_newdecimal = C.MYSQL_TYPE_NEWDECIMAL
38const mysql_type_enum = C.MYSQL_TYPE_ENUM
39const mysql_type_set = C.MYSQL_TYPE_SET
40const mysql_type_tiny_blob = C.MYSQL_TYPE_TINY_BLOB
41const mysql_type_medium_blob = C.MYSQL_TYPE_MEDIUM_BLOB
42const mysql_type_long_blob = C.MYSQL_TYPE_LONG_BLOB
43const mysql_type_blob = C.MYSQL_TYPE_BLOB
44const mysql_type_var_string = C.MYSQL_TYPE_VAR_STRING
45const mysql_type_string = C.MYSQL_TYPE_STRING
46const mysql_type_geometry = C.MYSQL_TYPE_GEOMETRY
47const mysql_no_data = C.MYSQL_NO_DATA
48
49fn C.mysql_stmt_init(&C.MYSQL) &C.MYSQL_STMT
50fn C.mysql_stmt_prepare(&C.MYSQL_STMT, const_query charptr, u32) i32
51fn C.mysql_stmt_bind_param(&C.MYSQL_STMT, &C.MYSQL_BIND) bool
52fn C.mysql_stmt_execute(&C.MYSQL_STMT) i32
53fn C.mysql_stmt_close(&C.MYSQL_STMT) bool
54fn C.mysql_stmt_free_result(&C.MYSQL_STMT) bool
55fn C.mysql_stmt_error(&C.MYSQL_STMT) &char
56fn C.mysql_stmt_errno(&C.MYSQL_STMT) i32
57fn C.mysql_stmt_result_metadata(&C.MYSQL_STMT) &C.MYSQL_RES
58
59fn C.mysql_stmt_field_count(&C.MYSQL_STMT) u16
60fn C.mysql_stmt_bind_result(&C.MYSQL_STMT, &C.MYSQL_BIND) bool
61fn C.mysql_stmt_fetch(&C.MYSQL_STMT) i32
62fn C.mysql_stmt_next_result(&C.MYSQL_STMT) i32
63fn C.mysql_stmt_store_result(&C.MYSQL_STMT) i32
64fn C.mysql_stmt_fetch_column(&C.MYSQL_STMT, &C.MYSQL_BIND, u32, u64) i32
65
66pub struct Stmt {
67 stmt &C.MYSQL_STMT = &C.MYSQL_STMT(unsafe { nil })
68 query string
69mut:
70 binds []C.MYSQL_BIND
71 res []C.MYSQL_BIND
72 auto_res_lengths []u32
73 auto_res_is_null []bool
74}
75
76// str returns a text representation of the given mysql statement `s`.
77pub fn (s &Stmt) str() string {
78 return 'mysql.Stmt{ stmt: ${voidptr(s.stmt):x}, query: `${s.query}`, binds.len: ${s.binds.len}, res.len: ${s.res.len} }'
79}
80
81// init_stmt creates a new statement, given the `query`.
82pub fn (db DB) init_stmt(query string) Stmt {
83 return Stmt{
84 stmt: C.mysql_stmt_init(db.conn)
85 query: query
86 binds: []C.MYSQL_BIND{}
87 }
88}
89
90// prepare a statement for execution.
91pub fn (stmt Stmt) prepare() ! {
92 result := C.mysql_stmt_prepare(stmt.stmt, charptr(stmt.query.str), stmt.query.len)
93
94 if result != 0 && stmt.get_error_msg() != '' {
95 return stmt.error(result)
96 }
97}
98
99// bind_params binds all the parameters in `stmt`.
100pub fn (stmt Stmt) bind_params() ! {
101 result := C.mysql_stmt_bind_param(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.binds.data) })
102
103 if result && stmt.get_error_msg() != '' {
104 return stmt.error(1)
105 }
106}
107
108// execute executes the given `stmt` and waits for the result.
109pub fn (stmt Stmt) execute() !int {
110 result := C.mysql_stmt_execute(stmt.stmt)
111
112 if result != 0 && stmt.get_error_msg() != '' {
113 return stmt.error(result)
114 }
115
116 return result
117}
118
119// next retrieves the next available result from the execution of `stmt`
120pub fn (stmt Stmt) next() !int {
121 result := C.mysql_stmt_next_result(stmt.stmt)
122
123 if result != 0 && stmt.get_error_msg() != '' {
124 return stmt.error(result)
125 }
126
127 return result
128}
129
130// gen_metadata executes mysql_stmt_result_metadata over the given `stmt`
131// It requires that the statement has produced a result set, since the metadata will be for that result set.
132// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-stmt-result-metadata.html
133pub fn (stmt Stmt) gen_metadata() &C.MYSQL_RES {
134 return C.mysql_stmt_result_metadata(stmt.stmt)
135}
136
137// fetch_fields retrieves the fields from the metadata result of the execution of `stmt`.
138// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-fetch-fields.html
139// See also Result.n_fields for the size of the returned C array.
140pub fn (stmt Stmt) fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD {
141 return C.mysql_fetch_fields(res)
142}
143
144// fetch_stmt fetches the next row in the result set. It returns the status of the execution of mysql_stmt_fetch .
145// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-stmt-fetch.html
146pub fn (stmt Stmt) fetch_stmt() !int {
147 result := C.mysql_stmt_fetch(stmt.stmt)
148
149 if result !in [0, 100] && stmt.get_error_msg() != '' {
150 return stmt.error(result)
151 }
152
153 return result
154}
155
156// close disposes the prepared `stmt`. The statement becomes invalid, and should not be used anymore after this call.
157// If the current statement has pending or unread results, this method cancels them too.
158// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-stmt-close.html
159pub fn (stmt Stmt) close() ! {
160 if !C.mysql_stmt_close(stmt.stmt) && stmt.get_error_msg() != '' {
161 return stmt.error(1)
162 }
163
164 if !C.mysql_stmt_free_result(stmt.stmt) && stmt.get_error_msg() != '' {
165 return stmt.error(1)
166 }
167}
168
169fn (stmt Stmt) get_error_msg() string {
170 return get_stmt_error_msg(stmt.stmt)
171}
172
173fn (stmt Stmt) get_error_code() int {
174 return get_stmt_errno(stmt.stmt)
175}
176
177// error returns a proper V error with a human readable description,
178// given the fallback status code returned by the MySQL statement API.
179pub fn (stmt Stmt) error(fallback_code int) IError {
180 msg := stmt.get_error_msg()
181 stmt_code := stmt.get_error_code()
182 code := if stmt_code != 0 {
183 stmt_code
184 } else {
185 fallback_code
186 }
187
188 return &SQLError{
189 msg: '${msg} (${code}) (${stmt.query})'
190 code: code
191 }
192}
193
194fn (stmt Stmt) get_field_count() u16 {
195 return C.mysql_stmt_field_count(stmt.stmt)
196}
197
198// bind_bool binds a single boolean value to the statement `stmt`
199pub fn (mut stmt Stmt) bind_bool(b &bool) {
200 stmt.bind(mysql_type_tiny, b, 0)
201}
202
203// bind_byte binds a single byte value to the statement `stmt`
204pub fn (mut stmt Stmt) bind_byte(b &u8) {
205 stmt.bind(mysql_type_tiny, b, 0)
206}
207
208// bind_u8 binds a single u8 value to the statement `stmt`
209pub fn (mut stmt Stmt) bind_u8(b &u8) {
210 stmt.bind(mysql_type_tiny, b, 0)
211}
212
213// bind_i8 binds a single i8 value to the statement `stmt`
214pub fn (mut stmt Stmt) bind_i8(b &i8) {
215 stmt.bind(mysql_type_tiny, b, 0)
216}
217
218// bind_i16 binds a single i16 value to the statement `stmt`
219pub fn (mut stmt Stmt) bind_i16(b &i16) {
220 stmt.bind(mysql_type_short, b, 0)
221}
222
223// bind_u16 binds a single u16 value to the statement `stmt`
224pub fn (mut stmt Stmt) bind_u16(b &u16) {
225 stmt.bind(mysql_type_short, b, 0)
226}
227
228// bind_int binds a single int value to the statement `stmt`
229pub fn (mut stmt Stmt) bind_int(b &int) {
230 stmt.bind(mysql_type_long, b, 0)
231}
232
233// bind_u32 binds a single u32 value to the statement `stmt`
234pub fn (mut stmt Stmt) bind_u32(b &u32) {
235 stmt.bind(mysql_type_long, b, 0)
236}
237
238// bind_i64 binds a single i64 value to the statement `stmt`
239pub fn (mut stmt Stmt) bind_i64(b &i64) {
240 stmt.bind(mysql_type_longlong, b, 0)
241}
242
243// bind_u64 binds a single u64 value to the statement `stmt`
244pub fn (mut stmt Stmt) bind_u64(b &u64) {
245 stmt.bind(mysql_type_longlong, b, 0)
246}
247
248// bind_f32 binds a single f32 value to the statement `stmt`
249pub fn (mut stmt Stmt) bind_f32(b &f32) {
250 stmt.bind(mysql_type_float, b, 0)
251}
252
253// bind_f64 binds a single f64 value to the statement `stmt`
254pub fn (mut stmt Stmt) bind_f64(b &f64) {
255 stmt.bind(mysql_type_double, b, 0)
256}
257
258// bind_text binds a single string value to the statement `stmt`
259pub fn (mut stmt Stmt) bind_text(b string) {
260 stmt.bind(mysql_type_string, b.str, u32(b.len))
261}
262
263// bind_null binds a single NULL value to the statement `stmt`
264pub fn (mut stmt Stmt) bind_null() {
265 stmt.binds << C.MYSQL_BIND{
266 buffer_type: mysql_type_null
267 length: 0
268 is_null: 0
269 }
270}
271
272// bind binds a single value pointed by `buffer`, to the statement `stmt`. The buffer length must be passed as well in `buf_len`.
273// Note: it is more convenient to use one of the other bind_XYZ methods.
274pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) {
275 stmt.binds << C.MYSQL_BIND{
276 buffer_type: typ
277 buffer: buffer
278 buffer_length: buf_len
279 length: 0
280 is_null: 0
281 }
282}
283
284// bind_res will store one result in the statement `stmt`
285pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&u8, lengths []u32, is_null []bool, num_fields int) {
286 stmt.auto_res_lengths = []u32{}
287 stmt.auto_res_is_null = []bool{}
288 if num_fields <= 0 {
289 stmt.res = []C.MYSQL_BIND{}
290 return
291 }
292 stmt.res = []C.MYSQL_BIND{cap: num_fields}
293 for i in 0 .. num_fields {
294 stmt.res << C.MYSQL_BIND{
295 buffer_type: unsafe { fields[i].type }
296 buffer: dataptr[i]
297 length: &lengths[i]
298 is_null: &is_null[i]
299 }
300 }
301}
302
303fn (mut stmt Stmt) ensure_default_result_binds() {
304 if stmt.res.len > 0 {
305 return
306 }
307 num_fields := int(stmt.get_field_count())
308 if num_fields <= 0 {
309 return
310 }
311 stmt.auto_res_lengths = []u32{len: num_fields}
312 stmt.auto_res_is_null = []bool{len: num_fields}
313 stmt.res = []C.MYSQL_BIND{cap: num_fields}
314 for i in 0 .. num_fields {
315 stmt.res << C.MYSQL_BIND{
316 buffer_type: mysql_type_string
317 buffer: 0
318 buffer_length: 0
319 length: unsafe { &stmt.auto_res_lengths[i] }
320 is_null: unsafe { &stmt.auto_res_is_null[i] }
321 }
322 }
323}
324
325// bind_result_buffer binds one result value, by calling mysql_stmt_bind_result .
326// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-stmt-bind-result.html
327pub fn (mut stmt Stmt) bind_result_buffer() ! {
328 stmt.ensure_default_result_binds()
329 if stmt.res.len == 0 {
330 return
331 }
332 result := C.mysql_stmt_bind_result(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.res.data) })
333
334 if result && stmt.get_error_msg() != '' {
335 return stmt.error(1)
336 }
337}
338
339// store_result will *buffer the complete result set* from the execution of `stmt` *on the client side*.
340// Note: result sets are produced by calling mysql_stmt_execute() to executed prepared statements for SQL
341// statements such as SELECT, SHOW, DESCRIBE, and EXPLAIN.
342// By default, result sets for successfully executed prepared statements are *not buffered on the client*,
343// and mysql_stmt_fetch() fetches them one at a time from the server.
344// Note 2: call store_result, *after* binding data buffers with bind_result_buffer,
345// and *before* calling fetch_stmt to fetch rows.
346// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-stmt-store-result.html
347pub fn (mut stmt Stmt) store_result() ! {
348 result := C.mysql_stmt_store_result(stmt.stmt)
349
350 if result != 0 && stmt.get_error_msg() != '' {
351 return stmt.error(result)
352 }
353}
354
355// fetch_column fetches one column from the current result set row.
356// `bind` provides the buffer where data should be placed.
357// It should be set up the same way as for `mysql_stmt_bind_result()`.
358// `column` indicates which column to fetch. The first column is numbered 0.
359pub fn (mut stmt Stmt) fetch_column(bind &C.MYSQL_BIND, column int) ! {
360 result := C.mysql_stmt_fetch_column(stmt.stmt, bind, column, 0)
361
362 if result != 0 && stmt.get_error_msg() != '' {
363 return stmt.error(result)
364 }
365}
366