From a13b8ff0c86cc025e13ad6db2e2d3e689c3de93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hitalo=20de=20Jesus=20do=20Ros=C3=A1rio=20Souza?= <63821277+enghitalo@users.noreply.github.com> Date: Tue, 19 Jul 2022 12:29:09 -0300 Subject: [PATCH] mysql: fix for adapting mysql types to v structs (#15100) --- vlib/mysql/_cdefs.c.v | 2 + vlib/mysql/mysql.v | 12 ++++ vlib/mysql/mysql_orm_test.v | 62 +++++++++++++++++++- vlib/mysql/orm.v | 110 ++++++++++++++++++++++++++++++++---- vlib/orm/README.md | 12 +++- 5 files changed, 184 insertions(+), 14 deletions(-) diff --git a/vlib/mysql/_cdefs.c.v b/vlib/mysql/_cdefs.c.v index bc76e923c..9c69d33a2 100644 --- a/vlib/mysql/_cdefs.c.v +++ b/vlib/mysql/_cdefs.c.v @@ -38,6 +38,8 @@ fn C.mysql_real_connect(mysql &C.MYSQL, host &char, user &char, passwd &char, db fn C.mysql_query(mysql &C.MYSQL, q &u8) int +fn C.mysql_use_result(mysql &C.MYSQL) + fn C.mysql_real_query(mysql &C.MYSQL, q &u8, len u32) int fn C.mysql_select_db(mysql &C.MYSQL, db &u8) int diff --git a/vlib/mysql/mysql.v b/vlib/mysql/mysql.v index a0d1bbe95..5bf3a4d03 100644 --- a/vlib/mysql/mysql.v +++ b/vlib/mysql/mysql.v @@ -58,6 +58,18 @@ pub fn (conn Connection) query(q string) ?Result { return Result{res} } +// use_result - reads the result of a query +// used after invoking mysql_real_query() or mysql_query(), +// for every statement that successfully produces a result set +// (SELECT, SHOW, DESCRIBE, EXPLAIN, CHECK TABLE, and so forth). +// This reads the result of a query directly from the server +// without storing it in a temporary table or local buffer, +// mysql_use_result is faster and uses much less memory than C.mysql_store_result(). +// You must mysql_free_result() after you are done with the result set. +pub fn (conn Connection) use_result() { + C.mysql_use_result(conn.conn) +} + // real_query - make an SQL query and receive the results. // `real_query()` can be used for statements containing binary data. // (Binary data may contain the `\0` character, which `query()` diff --git a/vlib/mysql/mysql_orm_test.v b/vlib/mysql/mysql_orm_test.v index 49bc406d2..bed6e23fe 100644 --- a/vlib/mysql/mysql_orm_test.v +++ b/vlib/mysql/mysql_orm_test.v @@ -1,5 +1,6 @@ import orm import mysql +import time struct TestCustomSqlType { id int [primary; sql: serial] @@ -19,6 +20,15 @@ struct TestCustomWrongSqlType { custom3 string [sql_type: 'xml'] } +struct TestTimeType { +mut: + id int [primary; sql: serial] + username string + created_at time.Time [sql_type: 'DATETIME'] + updated_at string [sql_type: 'DATETIME'] + deleted_at time.Time +} + fn test_mysql_orm() { mut mdb := mysql.Connection{ host: 'localhost' @@ -78,6 +88,8 @@ fn test_mysql_orm() { name := res[0][1] age := res[0][2] + mdb.close() + assert id is int if id is int { assert id == 1 @@ -153,9 +165,57 @@ fn test_orm() { }, ] + sql db { + drop table TestCustomSqlType + } + db.close() + assert result_custom_sql.maps() == information_schema_custom_sql +} + +fn test_orm_time_type() ? { + mut db := mysql.Connection{ + host: 'localhost' + port: 3306 + username: 'root' + password: '' + dbname: 'mysql' + } + + db.connect() or { + println(err) + panic(err) + } + + today := time.parse('2022-07-16 15:13:27')? + + model := TestTimeType{ + username: 'hitalo' + created_at: today + updated_at: today.str() + deleted_at: today + } sql db { - drop table TestCustomSqlType + create table TestTimeType } + + sql db { + insert model into TestTimeType + } + + results := sql db { + select from TestTimeType where username == 'hitalo' + } + + sql db { + drop table TestTimeType + } + + db.close() + + assert results[0].username == model.username + assert results[0].created_at == model.created_at + assert results[0].updated_at == model.updated_at + assert results[0].deleted_at == model.deleted_at } diff --git a/vlib/mysql/orm.v b/vlib/mysql/orm.v index 9d17e4dd2..578fcb8ba 100644 --- a/vlib/mysql/orm.v +++ b/vlib/mysql/orm.v @@ -24,7 +24,6 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher num_fields := stmt.get_field_count() metadata := stmt.gen_metadata() fields := stmt.fetch_fields(metadata) - mut dataptr := []&u8{} for i in 0 .. num_fields { @@ -48,26 +47,60 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher .type_double { dataptr << unsafe { malloc(8) } } + .type_time, .type_date, .type_datetime, .type_time2, .type_datetime2 { + dataptr << unsafe { malloc(sizeof(C.MYSQL_TIME)) } + } .type_string, .type_blob { dataptr << unsafe { malloc(512) } } + .type_var_string { + dataptr << unsafe { malloc(2) } + } else { - dataptr << &u8(0) + return error('\'${FieldType(f.@type)}\' is not yet implemented. Please create a new issue at https://github.com/vlang/v/issues/new') } } } lens := []u32{len: int(num_fields), init: 0} stmt.bind_res(fields, dataptr, lens, num_fields) - stmt.bind_result_buffer()? - stmt.store_result()? mut row := 0 mut types := config.types + mut field_types := []FieldType{} if config.is_count { types = [orm.type_idx['u64']] } + for i, mut mysql_bind in stmt.res { + f := unsafe { fields[i] } + field_types << FieldType(f.@type) + match types[i] { + orm.string { + mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB + mysql_bind.buffer_length = FieldType.type_blob.get_len() + } + orm.time { + match FieldType(f.@type) { + .type_long { + mysql_bind.buffer_type = C.MYSQL_TYPE_LONG + } + .type_time, .type_date, .type_datetime { + mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB + mysql_bind.buffer_length = FieldType.type_blob.get_len() + } + .type_string, .type_blob {} + else { + return error('Unknown type ${f.@type}') + } + } + } + else {} + } + } + + stmt.bind_result_buffer()? + stmt.store_result()? for { status = stmt.fetch_stmt()? @@ -76,7 +109,7 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher } row++ - data_list := buffer_to_primitive(dataptr, types)? + data_list := buffer_to_primitive(dataptr, types, field_types)? ret << data_list } @@ -88,8 +121,19 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher // sql stmt pub fn (db Connection) insert(table string, data orm.QueryData) ? { - query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, data, orm.QueryData{}) - mysql_stmt_worker(db, query, data, orm.QueryData{})? + mut converted_primitive_array := db.factory_orm_primitive_converted_from_sql(table, + data)? + + converted_data := orm.QueryData{ + fields: data.fields + data: converted_primitive_array + types: [] + kinds: [] + is_and: [] + } + + query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, converted_data, orm.QueryData{}) + mysql_stmt_worker(db, query, converted_data, orm.QueryData{})? } pub fn (db Connection) update(table string, data orm.QueryData, where orm.QueryData) ? { @@ -191,7 +235,7 @@ fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) { } } -fn buffer_to_primitive(data_list []&u8, types []int) ?[]orm.Primitive { +fn buffer_to_primitive(data_list []&u8, types []int, field_types []FieldType) ?[]orm.Primitive { mut res := []orm.Primitive{} for i, data in data_list { @@ -234,8 +278,17 @@ fn buffer_to_primitive(data_list []&u8, types []int) ?[]orm.Primitive { primitive = unsafe { cstring_to_vstring(&char(data)) } } orm.time { - timestamp := *(unsafe { &int(data) }) - primitive = time.unix(timestamp) + match field_types[i] { + .type_long { + timestamp := *(unsafe { &int(data) }) + primitive = time.unix(timestamp) + } + .type_datetime { + string_time := unsafe { cstring_to_vstring(&char(data)) } + primitive = time.parse(string_time)? + } + else {} + } } else { return error('Unknown type ${types[i]}') @@ -285,3 +338,40 @@ fn mysql_type_from_v(typ int) ?string { } return str } + +fn (db Connection) factory_orm_primitive_converted_from_sql(table string, data orm.QueryData) ?[]orm.Primitive { + mut map_val := db.get_table_data_type_map(table)? + + // adapt v type to sql time + mut converted_data := []orm.Primitive{} + for i, field in data.fields { + match data.data[i].type_name() { + 'time.Time' { + if map_val[field] == 'datetime' { + converted_data << orm.Primitive((data.data[i] as time.Time).str()) + } else { + converted_data << data.data[i] + } + } + else { + converted_data << data.data[i] + } + } + } + return converted_data +} + +fn (db Connection) get_table_data_type_map(table string) ?map[string]string { + data_type_querys := "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table'" + mut map_val := map[string]string{} + + results := db.query(data_type_querys)? + db.use_result() + + for row in results.rows() { + map_val[row.vals[0]] = row.vals[1] + } + + unsafe { results.free() } + return map_val +} diff --git a/vlib/orm/README.md b/vlib/orm/README.md index 7ad71a30b..d41377ec5 100644 --- a/vlib/orm/README.md +++ b/vlib/orm/README.md @@ -20,8 +20,11 @@ ```v ignore struct Foo { - id int [primary; sql: serial] - name string [nonull] + id int [primary; sql: serial] + name string [nonull] + created_at time.Time [sql_type: 'DATETIME'] + updated_at string [sql_type: 'DATETIME'] + deleted_at time.Time } ``` @@ -45,7 +48,10 @@ sql db { ```v ignore var := Foo{ - name: 'abc' + name: 'abc' + created_at: time.now() + updated_at: time.now().str() + deleted_at: time.now() } sql db { -- 2.30.2