| 1 | module mysql |
| 2 | |
| 3 | import orm |
| 4 | import strconv |
| 5 | import time |
| 6 | |
| 7 | // select is used internally by V's ORM for processing `SELECT ` queries. |
| 8 | pub fn (db DB) select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive { |
| 9 | where_with_tenant := orm.apply_tenant_filter(config.table, where) |
| 10 | query := orm.orm_select_gen(config, '`', false, '?', 0, where_with_tenant) |
| 11 | mut result := [][]orm.Primitive{} |
| 12 | mut stmt := db.init_stmt(query) |
| 13 | stmt.prepare()! |
| 14 | |
| 15 | mysql_stmt_bind_query_data(mut stmt, where_with_tenant)! |
| 16 | mysql_stmt_bind_query_data(mut stmt, data)! |
| 17 | |
| 18 | if data.data.len > 0 || where_with_tenant.data.len > 0 { |
| 19 | stmt.bind_params()! |
| 20 | } |
| 21 | |
| 22 | stmt.execute()! |
| 23 | metadata := stmt.gen_metadata() |
| 24 | fields := stmt.fetch_fields(metadata) |
| 25 | num_fields := stmt.get_field_count() |
| 26 | mut data_pointers := []&u8{cap: int(num_fields)} |
| 27 | |
| 28 | // Allocate memory for each column. |
| 29 | for i in 0 .. num_fields { |
| 30 | field := unsafe { fields[i] } |
| 31 | match unsafe { FieldType(field.type) } { |
| 32 | .type_tiny { |
| 33 | data_pointers << unsafe { malloc(1) } |
| 34 | } |
| 35 | .type_short { |
| 36 | data_pointers << unsafe { malloc(2) } |
| 37 | } |
| 38 | .type_long, .type_float { |
| 39 | data_pointers << unsafe { malloc(4) } |
| 40 | } |
| 41 | .type_longlong, .type_double { |
| 42 | data_pointers << unsafe { malloc(8) } |
| 43 | } |
| 44 | .type_time, .type_date, .type_datetime, .type_time2, .type_datetime2, .type_timestamp { |
| 45 | data_pointers << unsafe { malloc(sizeof(C.MYSQL_TIME)) } |
| 46 | } |
| 47 | .type_decimal, .type_newdecimal, .type_string, .type_var_string, .type_blob, |
| 48 | .type_tiny_blob, .type_medium_blob, .type_long_blob { |
| 49 | // Memory will be allocated later dynamically depending on the length of the value. |
| 50 | data_pointers << &u8(unsafe { nil }) |
| 51 | } |
| 52 | else { |
| 53 | return error('\'${unsafe { FieldType(field.type) }}\' is not yet implemented. Please create a new issue at https://github.com/vlang/v/issues/new') |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | mut lengths := []u32{len: int(num_fields), init: 0} |
| 59 | mut is_null := []bool{len: int(num_fields)} |
| 60 | stmt.bind_res(fields, data_pointers, lengths, is_null, num_fields) |
| 61 | |
| 62 | mut types := config.types.clone() |
| 63 | mut field_types := []FieldType{} |
| 64 | if config.aggregate_kind == .count { |
| 65 | types = [orm.type_idx['u64']] |
| 66 | } |
| 67 | |
| 68 | // Map stores column indexes and their binds in order to extract values |
| 69 | // for these columns separately, with individual memory allocation for each value. |
| 70 | mut string_binds_map := map[int]C.MYSQL_BIND{} |
| 71 | |
| 72 | for i, mut mysql_bind in stmt.res { |
| 73 | field := unsafe { fields[i] } |
| 74 | field_type := unsafe { FieldType(field.type) } |
| 75 | field_types << field_type |
| 76 | |
| 77 | match field_type { |
| 78 | .type_decimal, .type_newdecimal, .type_string, .type_var_string, .type_blob, |
| 79 | .type_tiny_blob, .type_medium_blob, .type_long_blob { |
| 80 | if field_type in [.type_decimal, .type_newdecimal] { |
| 81 | mysql_bind.buffer_type = C.MYSQL_TYPE_STRING |
| 82 | } |
| 83 | string_binds_map[i] = mysql_bind |
| 84 | } |
| 85 | .type_long { |
| 86 | mysql_bind.buffer_type = C.MYSQL_TYPE_LONG |
| 87 | } |
| 88 | .type_time, .type_date, .type_datetime, .type_timestamp { |
| 89 | // FIXME: Allocate memory for blobs dynamically. |
| 90 | mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB |
| 91 | mysql_bind.buffer_length = FieldType.type_blob.get_len() |
| 92 | } |
| 93 | else {} |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | stmt.bind_result_buffer()! |
| 98 | stmt.store_result()! |
| 99 | |
| 100 | for { |
| 101 | // Fetch every row from the `select` result. |
| 102 | status := stmt.fetch_stmt()! |
| 103 | is_error := status == 1 |
| 104 | are_no_rows_to_fetch := status == mysql_no_data |
| 105 | |
| 106 | if is_error || are_no_rows_to_fetch { |
| 107 | break |
| 108 | } |
| 109 | |
| 110 | // Fetch columns that should be allocated dynamically. |
| 111 | for index, mut bind in string_binds_map { |
| 112 | string_length := lengths[index] + 1 |
| 113 | data_pointers[index] = unsafe { malloc(string_length) } |
| 114 | bind.buffer = data_pointers[index] |
| 115 | bind.buffer_length = string_length |
| 116 | bind.length = unsafe { nil } |
| 117 | |
| 118 | stmt.fetch_column(bind, index)! |
| 119 | } |
| 120 | |
| 121 | mut row := data_pointers_to_primitives(is_null, data_pointers, types, field_types)! |
| 122 | if config.aggregate_kind == .count && row.len > 0 { |
| 123 | count_value := row[0] |
| 124 | row[0] = match count_value { |
| 125 | u64 { orm.Primitive(int(count_value)) } |
| 126 | i64 { orm.Primitive(int(count_value)) } |
| 127 | int { count_value } |
| 128 | else { count_value } |
| 129 | } |
| 130 | } |
| 131 | result << row |
| 132 | } |
| 133 | |
| 134 | stmt.close()! |
| 135 | |
| 136 | return result |
| 137 | } |
| 138 | |
| 139 | // insert is used internally by V's ORM for processing `INSERT ` queries |
| 140 | pub fn (db DB) insert(table orm.Table, data orm.QueryData) ! { |
| 141 | mut converted_primitive_array := db.convert_query_data_to_primitives(table.name, data)! |
| 142 | |
| 143 | converted_primitive_data := orm.QueryData{ |
| 144 | fields: data.fields |
| 145 | data: converted_primitive_array |
| 146 | types: data.types |
| 147 | parentheses: data.parentheses |
| 148 | kinds: data.kinds |
| 149 | auto_fields: data.auto_fields |
| 150 | is_and: data.is_and |
| 151 | batch_rows: data.batch_rows |
| 152 | batch_key: data.batch_key |
| 153 | } |
| 154 | |
| 155 | query, converted_data := orm.orm_stmt_gen(.default, table, '`', .insert, false, '?', 1, |
| 156 | converted_primitive_data, orm.QueryData{}) |
| 157 | mysql_stmt_worker(db, query, converted_data, orm.QueryData{})! |
| 158 | } |
| 159 | |
| 160 | // update is used internally by V's ORM for processing `UPDATE ` queries |
| 161 | pub fn (db DB) update(table orm.Table, data orm.QueryData, where orm.QueryData) ! { |
| 162 | where_with_tenant := orm.apply_tenant_filter(table, where) |
| 163 | query, _ := orm.orm_stmt_gen(.default, table, '`', .update, false, '?', 1, data, |
| 164 | where_with_tenant) |
| 165 | mysql_stmt_worker(db, query, data, where_with_tenant)! |
| 166 | } |
| 167 | |
| 168 | // delete is used internally by V's ORM for processing `DELETE ` queries |
| 169 | pub fn (db DB) delete(table orm.Table, where orm.QueryData) ! { |
| 170 | where_with_tenant := orm.apply_tenant_filter(table, where) |
| 171 | query, _ := orm.orm_stmt_gen(.default, table, '`', .delete, false, '?', 1, orm.QueryData{}, |
| 172 | where_with_tenant) |
| 173 | mysql_stmt_worker(db, query, orm.QueryData{}, where_with_tenant)! |
| 174 | } |
| 175 | |
| 176 | // last_id is used internally by V's ORM for post-processing `INSERT ` queries |
| 177 | pub fn (db DB) last_id() int { |
| 178 | query := 'SELECT last_insert_id();' |
| 179 | id := db.query(query) or { return 0 } |
| 180 | |
| 181 | return id.rows()[0].vals[0].int() |
| 182 | } |
| 183 | |
| 184 | // create is used internally by V's ORM for processing table creation queries (DDL) |
| 185 | pub fn (db DB) create(table orm.Table, fields []orm.TableField) ! { |
| 186 | query := orm.orm_table_gen(.mysql, table, '`', true, 0, fields, mysql_type_from_v, false) or { |
| 187 | return err |
| 188 | } |
| 189 | mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})! |
| 190 | } |
| 191 | |
| 192 | // drop is used internally by V's ORM for processing table destroying queries (DDL) |
| 193 | pub fn (db DB) drop(table orm.Table) ! { |
| 194 | query := 'DROP TABLE `${table.name}`;' |
| 195 | mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})! |
| 196 | } |
| 197 | |
| 198 | // orm_begin starts a transaction for ORM helpers. |
| 199 | pub fn (mut db DB) orm_begin() ! { |
| 200 | db.begin()! |
| 201 | } |
| 202 | |
| 203 | // orm_commit commits a transaction for ORM helpers. |
| 204 | pub fn (mut db DB) orm_commit() ! { |
| 205 | db.commit()! |
| 206 | } |
| 207 | |
| 208 | // orm_rollback rolls back a transaction for ORM helpers. |
| 209 | pub fn (mut db DB) orm_rollback() ! { |
| 210 | db.rollback()! |
| 211 | } |
| 212 | |
| 213 | // orm_savepoint creates a savepoint for ORM helpers. |
| 214 | pub fn (mut db DB) orm_savepoint(name string) ! { |
| 215 | db.savepoint(name)! |
| 216 | } |
| 217 | |
| 218 | // orm_rollback_to rolls back to a savepoint for ORM helpers. |
| 219 | pub fn (mut db DB) orm_rollback_to(name string) ! { |
| 220 | db.rollback_to(name)! |
| 221 | } |
| 222 | |
| 223 | // orm_release_savepoint releases a savepoint for ORM helpers. |
| 224 | pub fn (mut db DB) orm_release_savepoint(name string) ! { |
| 225 | db.release_savepoint(name)! |
| 226 | } |
| 227 | |
| 228 | // mysql_stmt_worker executes the `query` with the provided `data` and `where` parameters |
| 229 | // without returning the result. |
| 230 | // This is commonly used for `INSERT`, `UPDATE`, `CREATE`, `DROP`, and `DELETE` queries. |
| 231 | fn mysql_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ! { |
| 232 | mut stmt := db.init_stmt(query) |
| 233 | stmt.prepare()! |
| 234 | |
| 235 | mysql_stmt_bind_query_data(mut stmt, data)! |
| 236 | mysql_stmt_bind_query_data(mut stmt, where)! |
| 237 | |
| 238 | if data.data.len > 0 || where.data.len > 0 { |
| 239 | stmt.bind_params()! |
| 240 | } |
| 241 | |
| 242 | stmt.execute()! |
| 243 | stmt.close()! |
| 244 | } |
| 245 | |
| 246 | // mysql_stmt_bind_query_data binds all the fields of `q` to the `stmt`. |
| 247 | fn mysql_stmt_bind_query_data(mut stmt Stmt, d orm.QueryData) ! { |
| 248 | for data in d.data { |
| 249 | stmt_bind_primitive(mut stmt, data) |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | fn stmt_bind_array[T](mut stmt Stmt, data []T) { |
| 254 | for element in data { |
| 255 | stmt_bind_primitive(mut stmt, orm.Primitive(element)) |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | // stmt_bind_primitive binds the `data` to the `stmt`. |
| 260 | fn stmt_bind_primitive(mut stmt Stmt, data orm.Primitive) { |
| 261 | match data { |
| 262 | bool { |
| 263 | stmt.bind_bool(&data) |
| 264 | } |
| 265 | i8 { |
| 266 | stmt.bind_i8(&data) |
| 267 | } |
| 268 | i16 { |
| 269 | stmt.bind_i16(&data) |
| 270 | } |
| 271 | int { |
| 272 | stmt.bind_int(&data) |
| 273 | } |
| 274 | i64 { |
| 275 | stmt.bind_i64(&data) |
| 276 | } |
| 277 | u8 { |
| 278 | stmt.bind_u8(&data) |
| 279 | } |
| 280 | u16 { |
| 281 | stmt.bind_u16(&data) |
| 282 | } |
| 283 | u32 { |
| 284 | stmt.bind_u32(&data) |
| 285 | } |
| 286 | u64 { |
| 287 | stmt.bind_u64(&data) |
| 288 | } |
| 289 | f32 { |
| 290 | stmt.bind_f32(unsafe { &f32(&data) }) |
| 291 | } |
| 292 | f64 { |
| 293 | stmt.bind_f64(unsafe { &f64(&data) }) |
| 294 | } |
| 295 | string { |
| 296 | stmt.bind_text(data) |
| 297 | } |
| 298 | time.Time { |
| 299 | unix := int(data.unix()) |
| 300 | stmt_bind_primitive(mut stmt, unix) |
| 301 | } |
| 302 | orm.InfixType { |
| 303 | stmt_bind_primitive(mut stmt, data.right) |
| 304 | } |
| 305 | orm.Null { |
| 306 | stmt.bind_null() |
| 307 | } |
| 308 | []orm.Primitive { |
| 309 | stmt_bind_array(mut stmt, data) |
| 310 | } |
| 311 | []bool { |
| 312 | stmt_bind_array(mut stmt, data) |
| 313 | } |
| 314 | []f32 { |
| 315 | stmt_bind_array(mut stmt, data) |
| 316 | } |
| 317 | []f64 { |
| 318 | stmt_bind_array(mut stmt, data) |
| 319 | } |
| 320 | []i16 { |
| 321 | stmt_bind_array(mut stmt, data) |
| 322 | } |
| 323 | []i64 { |
| 324 | stmt_bind_array(mut stmt, data) |
| 325 | } |
| 326 | []i8 { |
| 327 | stmt_bind_array(mut stmt, data) |
| 328 | } |
| 329 | []int { |
| 330 | stmt_bind_array(mut stmt, data) |
| 331 | } |
| 332 | []string { |
| 333 | stmt_bind_array(mut stmt, data) |
| 334 | } |
| 335 | []time.Time { |
| 336 | stmt_bind_array(mut stmt, data) |
| 337 | } |
| 338 | []u16 { |
| 339 | stmt_bind_array(mut stmt, data) |
| 340 | } |
| 341 | []u32 { |
| 342 | stmt_bind_array(mut stmt, data) |
| 343 | } |
| 344 | []u64 { |
| 345 | stmt_bind_array(mut stmt, data) |
| 346 | } |
| 347 | []u8 { |
| 348 | stmt_bind_array(mut stmt, data) |
| 349 | } |
| 350 | []orm.InfixType { |
| 351 | stmt_bind_array(mut stmt, data) |
| 352 | } |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | // data_pointers_to_primitives returns an array of `Primitive` |
| 357 | // cast from `data_pointers` using `types`. |
| 358 | fn data_pointers_to_primitives(is_null []bool, data_pointers []&u8, types []int, field_types []FieldType) ![]orm.Primitive { |
| 359 | mut result := []orm.Primitive{} |
| 360 | |
| 361 | for i, data in data_pointers { |
| 362 | mut primitive := orm.Primitive(0) |
| 363 | if !is_null[i] { |
| 364 | if field_types[i] in [.type_decimal, .type_newdecimal] { |
| 365 | decimal_value := unsafe { cstring_to_vstring(&char(data)) } |
| 366 | primitive = decimal_string_to_primitive(decimal_value, types[i])! |
| 367 | result << primitive |
| 368 | continue |
| 369 | } |
| 370 | match types[i] { |
| 371 | orm.type_idx['i8'] { |
| 372 | primitive = *(unsafe { &i8(data) }) |
| 373 | } |
| 374 | orm.type_idx['i16'] { |
| 375 | primitive = *(unsafe { &i16(data) }) |
| 376 | } |
| 377 | orm.type_idx['int'], orm.serial { |
| 378 | primitive = *(unsafe { &int(data) }) |
| 379 | } |
| 380 | orm.type_idx['i64'] { |
| 381 | primitive = *(unsafe { &i64(data) }) |
| 382 | } |
| 383 | orm.type_idx['u8'] { |
| 384 | primitive = *(unsafe { &u8(data) }) |
| 385 | } |
| 386 | orm.type_idx['u16'] { |
| 387 | primitive = *(unsafe { &u16(data) }) |
| 388 | } |
| 389 | orm.type_idx['u32'] { |
| 390 | primitive = *(unsafe { &u32(data) }) |
| 391 | } |
| 392 | orm.type_idx['u64'] { |
| 393 | primitive = *(unsafe { &u64(data) }) |
| 394 | } |
| 395 | orm.type_idx['f32'] { |
| 396 | primitive = *(unsafe { &f32(data) }) |
| 397 | } |
| 398 | orm.type_idx['f64'] { |
| 399 | primitive = *(unsafe { &f64(data) }) |
| 400 | } |
| 401 | orm.type_idx['bool'] { |
| 402 | primitive = *(unsafe { &bool(data) }) |
| 403 | } |
| 404 | orm.type_string { |
| 405 | primitive = unsafe { cstring_to_vstring(&char(data)) } |
| 406 | } |
| 407 | orm.time_ { |
| 408 | match field_types[i] { |
| 409 | .type_long { |
| 410 | timestamp := *(unsafe { &int(data) }) |
| 411 | primitive = time.unix(timestamp) |
| 412 | } |
| 413 | .type_datetime, .type_timestamp { |
| 414 | primitive = time.parse(unsafe { cstring_to_vstring(&char(data)) })! |
| 415 | } |
| 416 | else {} |
| 417 | } |
| 418 | } |
| 419 | orm.enum_ { |
| 420 | primitive = *(unsafe { &i64(data) }) |
| 421 | } |
| 422 | else { |
| 423 | return error('Unknown type ${types[i]}') |
| 424 | } |
| 425 | } |
| 426 | } else { |
| 427 | primitive = orm.Null{} |
| 428 | } |
| 429 | result << primitive |
| 430 | } |
| 431 | |
| 432 | return result |
| 433 | } |
| 434 | |
| 435 | fn decimal_string_to_primitive(value string, typ int) !orm.Primitive { |
| 436 | return match typ { |
| 437 | orm.type_idx['i8'] { |
| 438 | orm.Primitive(strconv.atoi8(value)!) |
| 439 | } |
| 440 | orm.type_idx['i16'] { |
| 441 | orm.Primitive(strconv.atoi16(value)!) |
| 442 | } |
| 443 | orm.type_idx['int'], orm.serial { |
| 444 | orm.Primitive(strconv.atoi(value)!) |
| 445 | } |
| 446 | orm.type_idx['i64'], orm.enum_ { |
| 447 | orm.Primitive(strconv.atoi64(value)!) |
| 448 | } |
| 449 | orm.type_idx['u8'] { |
| 450 | orm.Primitive(strconv.atou8(value)!) |
| 451 | } |
| 452 | orm.type_idx['u16'] { |
| 453 | orm.Primitive(strconv.atou16(value)!) |
| 454 | } |
| 455 | orm.type_idx['u32'] { |
| 456 | orm.Primitive(strconv.atou32(value)!) |
| 457 | } |
| 458 | orm.type_idx['u64'] { |
| 459 | orm.Primitive(strconv.atou64(value)!) |
| 460 | } |
| 461 | orm.type_idx['f32'] { |
| 462 | orm.Primitive(f32(strconv.atof64(value)!)) |
| 463 | } |
| 464 | orm.type_idx['f64'] { |
| 465 | orm.Primitive(strconv.atof64(value)!) |
| 466 | } |
| 467 | else { |
| 468 | return error('Unknown decimal target type ${typ}') |
| 469 | } |
| 470 | } |
| 471 | } |
| 472 | |
| 473 | // mysql_type_from_v converts the V type to the corresponding MySQL type. |
| 474 | fn mysql_type_from_v(typ int) !string { |
| 475 | sql_type := match typ { |
| 476 | orm.type_idx['i8'], orm.type_idx['u8'] { |
| 477 | 'TINYINT' |
| 478 | } |
| 479 | orm.type_idx['i16'], orm.type_idx['u16'] { |
| 480 | 'SMALLINT' |
| 481 | } |
| 482 | orm.type_idx['int'], orm.type_idx['u32'], orm.time_ { |
| 483 | 'INT' |
| 484 | } |
| 485 | orm.type_idx['i64'], orm.type_idx['u64'], orm.enum_ { |
| 486 | 'BIGINT' |
| 487 | } |
| 488 | orm.type_idx['f32'] { |
| 489 | 'FLOAT' |
| 490 | } |
| 491 | orm.type_idx['f64'] { |
| 492 | 'DOUBLE' |
| 493 | } |
| 494 | orm.type_string { |
| 495 | 'TEXT' |
| 496 | } |
| 497 | orm.serial { |
| 498 | 'SERIAL' |
| 499 | } |
| 500 | orm.type_idx['bool'] { |
| 501 | 'BOOLEAN' |
| 502 | } |
| 503 | else { |
| 504 | return error('Unknown type ${typ}') |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | return sql_type |
| 509 | } |
| 510 | |
| 511 | // convert_query_data_to_primitives converts the `data` representing the `QueryData` |
| 512 | // into an array of `Primitive`. |
| 513 | fn (db DB) convert_query_data_to_primitives(table string, data orm.QueryData) ![]orm.Primitive { |
| 514 | mut column_type_map := db.get_table_column_type_map(table)! |
| 515 | mut converted_data := []orm.Primitive{} |
| 516 | if data.fields.len == 0 { |
| 517 | return converted_data |
| 518 | } |
| 519 | |
| 520 | for i, primitive in data.data { |
| 521 | field := data.fields[i % data.fields.len] |
| 522 | if primitive.type_name() == 'time.Time' { |
| 523 | if column_type_map[field] in ['datetime', 'timestamp'] { |
| 524 | converted_data << orm.Primitive((primitive as time.Time).str()) |
| 525 | } else { |
| 526 | converted_data << primitive |
| 527 | } |
| 528 | } else { |
| 529 | converted_data << primitive |
| 530 | } |
| 531 | } |
| 532 | |
| 533 | return converted_data |
| 534 | } |
| 535 | |
| 536 | // get_table_column_type_map returns a map where the key represents the column name, |
| 537 | // and the value represents its data type. |
| 538 | fn (db DB) get_table_column_type_map(table string) !map[string]string { |
| 539 | data_type_query := "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '${table}'" |
| 540 | mut column_type_map := map[string]string{} |
| 541 | results := db.query(data_type_query)! |
| 542 | |
| 543 | for row in results.rows() { |
| 544 | column_type_map[row.vals[0]] = row.vals[1] |
| 545 | } |
| 546 | |
| 547 | unsafe { results.free() } |
| 548 | |
| 549 | return column_type_map |
| 550 | } |
| 551 | |