| 1 | module mysql |
| 2 | |
| 3 | @[typedef] |
| 4 | pub struct C.MYSQL_STMT { |
| 5 | mysql &C.MYSQL |
| 6 | stmt_id u32 |
| 7 | } |
| 8 | |
| 9 | @[typedef] |
| 10 | pub struct C.MYSQL_BIND { |
| 11 | mut: |
| 12 | buffer_type int |
| 13 | buffer voidptr |
| 14 | buffer_length u32 |
| 15 | length &u32 |
| 16 | is_null &bool |
| 17 | } |
| 18 | |
| 19 | const mysql_type_decimal = C.MYSQL_TYPE_DECIMAL |
| 20 | const mysql_type_tiny = C.MYSQL_TYPE_TINY |
| 21 | const mysql_type_short = C.MYSQL_TYPE_SHORT |
| 22 | const mysql_type_long = C.MYSQL_TYPE_LONG |
| 23 | const mysql_type_float = C.MYSQL_TYPE_FLOAT |
| 24 | const mysql_type_double = C.MYSQL_TYPE_DOUBLE |
| 25 | const mysql_type_null = C.MYSQL_TYPE_NULL |
| 26 | const mysql_type_timestamp = C.MYSQL_TYPE_TIMESTAMP |
| 27 | const mysql_type_longlong = C.MYSQL_TYPE_LONGLONG |
| 28 | const mysql_type_int24 = C.MYSQL_TYPE_INT24 |
| 29 | const mysql_type_date = C.MYSQL_TYPE_DATE |
| 30 | const mysql_type_time = C.MYSQL_TYPE_TIME |
| 31 | const mysql_type_datetime = C.MYSQL_TYPE_DATETIME |
| 32 | const mysql_type_year = C.MYSQL_TYPE_YEAR |
| 33 | const mysql_type_varchar = C.MYSQL_TYPE_VARCHAR |
| 34 | const mysql_type_bit = C.MYSQL_TYPE_BIT |
| 35 | const mysql_type_timestamp22 = C.MYSQL_TYPE_TIMESTAMP |
| 36 | const mysql_type_json = C.MYSQL_TYPE_JSON |
| 37 | const mysql_type_newdecimal = C.MYSQL_TYPE_NEWDECIMAL |
| 38 | const mysql_type_enum = C.MYSQL_TYPE_ENUM |
| 39 | const mysql_type_set = C.MYSQL_TYPE_SET |
| 40 | const mysql_type_tiny_blob = C.MYSQL_TYPE_TINY_BLOB |
| 41 | const mysql_type_medium_blob = C.MYSQL_TYPE_MEDIUM_BLOB |
| 42 | const mysql_type_long_blob = C.MYSQL_TYPE_LONG_BLOB |
| 43 | const mysql_type_blob = C.MYSQL_TYPE_BLOB |
| 44 | const mysql_type_var_string = C.MYSQL_TYPE_VAR_STRING |
| 45 | const mysql_type_string = C.MYSQL_TYPE_STRING |
| 46 | const mysql_type_geometry = C.MYSQL_TYPE_GEOMETRY |
| 47 | const mysql_no_data = C.MYSQL_NO_DATA |
| 48 | |
| 49 | fn C.mysql_stmt_init(&C.MYSQL) &C.MYSQL_STMT |
| 50 | fn C.mysql_stmt_prepare(&C.MYSQL_STMT, const_query charptr, u32) i32 |
| 51 | fn C.mysql_stmt_bind_param(&C.MYSQL_STMT, &C.MYSQL_BIND) bool |
| 52 | fn C.mysql_stmt_execute(&C.MYSQL_STMT) i32 |
| 53 | fn C.mysql_stmt_close(&C.MYSQL_STMT) bool |
| 54 | fn C.mysql_stmt_free_result(&C.MYSQL_STMT) bool |
| 55 | fn C.mysql_stmt_error(&C.MYSQL_STMT) &char |
| 56 | fn C.mysql_stmt_errno(&C.MYSQL_STMT) i32 |
| 57 | fn C.mysql_stmt_result_metadata(&C.MYSQL_STMT) &C.MYSQL_RES |
| 58 | |
| 59 | fn C.mysql_stmt_field_count(&C.MYSQL_STMT) u16 |
| 60 | fn C.mysql_stmt_bind_result(&C.MYSQL_STMT, &C.MYSQL_BIND) bool |
| 61 | fn C.mysql_stmt_fetch(&C.MYSQL_STMT) i32 |
| 62 | fn C.mysql_stmt_next_result(&C.MYSQL_STMT) i32 |
| 63 | fn C.mysql_stmt_store_result(&C.MYSQL_STMT) i32 |
| 64 | fn C.mysql_stmt_fetch_column(&C.MYSQL_STMT, &C.MYSQL_BIND, u32, u64) i32 |
| 65 | |
| 66 | pub struct Stmt { |
| 67 | stmt &C.MYSQL_STMT = &C.MYSQL_STMT(unsafe { nil }) |
| 68 | query string |
| 69 | mut: |
| 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`. |
| 77 | pub 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`. |
| 82 | pub 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. |
| 91 | pub 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`. |
| 100 | pub 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. |
| 109 | pub 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` |
| 120 | pub 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 |
| 133 | pub 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. |
| 140 | pub 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 |
| 146 | pub 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 |
| 159 | pub 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 | |
| 169 | fn (stmt Stmt) get_error_msg() string { |
| 170 | return get_stmt_error_msg(stmt.stmt) |
| 171 | } |
| 172 | |
| 173 | fn (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. |
| 179 | pub 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 | |
| 194 | fn (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` |
| 199 | pub 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` |
| 204 | pub 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` |
| 209 | pub 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` |
| 214 | pub 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` |
| 219 | pub 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` |
| 224 | pub 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` |
| 229 | pub 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` |
| 234 | pub 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` |
| 239 | pub 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` |
| 244 | pub 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` |
| 249 | pub 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` |
| 254 | pub 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` |
| 259 | pub 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` |
| 264 | pub 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. |
| 274 | pub 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` |
| 285 | pub 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 | |
| 303 | fn (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 |
| 327 | pub 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 |
| 347 | pub 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. |
| 359 | pub 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 | |