diff --git a/vlib/db/mysql/mysql.c.v b/vlib/db/mysql/mysql.c.v index 35b98e3d22..005569ace5 100644 --- a/vlib/db/mysql/mysql.c.v +++ b/vlib/db/mysql/mysql.c.v @@ -448,6 +448,7 @@ pub fn (stmt &StmtHandle) execute(params []string) ![]Row { buffer: param.str buffer_length: u32(param.len) length: 0 + is_null: 0 } bind_params << bind } @@ -470,6 +471,7 @@ pub fn (stmt &StmtHandle) execute(params []string) ![]Row { } num_cols := C.mysql_num_fields(query_metadata) mut length := []u32{len: num_cols} + mut is_null := []bool{len: num_cols} mut binds := []C.MYSQL_BIND{} for i in 0 .. num_cols { @@ -478,6 +480,7 @@ pub fn (stmt &StmtHandle) execute(params []string) ![]Row { buffer: 0 buffer_length: 0 length: unsafe { &length[i] } + is_null: unsafe { &is_null[i] } } binds << bind } diff --git a/vlib/db/mysql/mysql_orm_test.v b/vlib/db/mysql/mysql_orm_test.v index a0e89f00c4..39edafe825 100644 --- a/vlib/db/mysql/mysql_orm_test.v +++ b/vlib/db/mysql/mysql_orm_test.v @@ -28,6 +28,7 @@ mut: created_at time.Time @[sql_type: 'DATETIME'] updated_at string @[sql_type: 'DATETIME'] deleted_at time.Time + null_date ?time.Time @[sql_type: 'DATETIME'] } struct TestDefaultAttribute { @@ -179,11 +180,26 @@ fn test_mysql_orm() { panic(err) } - model := TestTimeType{ + model1 := TestTimeType{ username: 'hitalo' created_at: today updated_at: today.str() deleted_at: today + // null_date is null + } + model2 := TestTimeType{ + username: 'tom' + created_at: today + updated_at: today.str() + deleted_at: today + null_date: today + } + model3 := TestTimeType{ + username: 'kitty' + created_at: today + updated_at: today.str() + deleted_at: today + // null_date is null } sql db { @@ -191,21 +207,41 @@ fn test_mysql_orm() { }! sql db { - insert model into TestTimeType + insert model1 into TestTimeType + insert model2 into TestTimeType + insert model3 into TestTimeType }! results := sql db { - select from TestTimeType where username == 'hitalo' + select from TestTimeType }! sql db { drop table TestTimeType }! - assert results[0].created_at == model.created_at - assert results[0].username == model.username - assert results[0].updated_at == model.updated_at - assert results[0].deleted_at == model.deleted_at + assert results[0].created_at == model1.created_at + assert results[0].username == model1.username + assert results[0].updated_at == model1.updated_at + assert results[0].deleted_at == model1.deleted_at + assert results[0].null_date == none + + assert results[1].created_at == model2.created_at + assert results[1].username == model2.username + assert results[1].updated_at == model2.updated_at + assert results[1].deleted_at == model2.deleted_at + if x := results[1].null_date { + // should not by `none`/`NULL` + assert x == model2.deleted_at + } else { + assert false + } + + assert results[2].created_at == model3.created_at + assert results[2].username == model3.username + assert results[2].updated_at == model3.updated_at + assert results[2].deleted_at == model3.deleted_at + assert results[2].null_date == none /** test default attribute */ diff --git a/vlib/db/mysql/orm.c.v b/vlib/db/mysql/orm.c.v index d7beedfb61..6852a6a84d 100644 --- a/vlib/db/mysql/orm.c.v +++ b/vlib/db/mysql/orm.c.v @@ -53,8 +53,9 @@ pub fn (db DB) select(config orm.SelectConfig, data orm.QueryData, where orm.Que } } - lengths := []u32{len: int(num_fields), init: 0} - stmt.bind_res(fields, data_pointers, lengths, num_fields) + mut lengths := []u32{len: int(num_fields), init: 0} + mut is_null := []bool{len: int(num_fields)} + stmt.bind_res(fields, data_pointers, lengths, is_null, num_fields) mut types := config.types.clone() mut field_types := []FieldType{} @@ -112,7 +113,7 @@ pub fn (db DB) select(config orm.SelectConfig, data orm.QueryData, where orm.Que stmt.fetch_column(bind, index)! } - result << data_pointers_to_primitives(data_pointers, types, field_types)! + result << data_pointers_to_primitives(is_null, data_pointers, types, field_types)! } stmt.close()! @@ -251,71 +252,70 @@ fn stmt_bind_primitive(mut stmt Stmt, data orm.Primitive) { // data_pointers_to_primitives returns an array of `Primitive` // cast from `data_pointers` using `types`. -fn data_pointers_to_primitives(data_pointers []&u8, types []int, field_types []FieldType) ![]orm.Primitive { +fn data_pointers_to_primitives(is_null []bool, data_pointers []&u8, types []int, field_types []FieldType) ![]orm.Primitive { mut result := []orm.Primitive{} for i, data in data_pointers { mut primitive := orm.Primitive(0) - match types[i] { - orm.type_idx['i8'] { - primitive = *(unsafe { &i8(data) }) - } - orm.type_idx['i16'] { - primitive = *(unsafe { &i16(data) }) - } - orm.type_idx['int'], orm.serial { - primitive = *(unsafe { &int(data) }) - } - orm.type_idx['i64'] { - primitive = *(unsafe { &i64(data) }) - } - orm.type_idx['u8'] { - primitive = *(unsafe { &u8(data) }) - } - orm.type_idx['u16'] { - primitive = *(unsafe { &u16(data) }) - } - orm.type_idx['u32'] { - primitive = *(unsafe { &u32(data) }) - } - orm.type_idx['u64'] { - primitive = *(unsafe { &u64(data) }) - } - orm.type_idx['f32'] { - primitive = *(unsafe { &f32(data) }) - } - orm.type_idx['f64'] { - primitive = *(unsafe { &f64(data) }) - } - orm.type_idx['bool'] { - primitive = *(unsafe { &bool(data) }) - } - orm.type_string { - primitive = unsafe { cstring_to_vstring(&char(data)) } - } - orm.time_ { - match field_types[i] { - .type_long { - timestamp := *(unsafe { &int(data) }) - primitive = time.unix(timestamp) - } - .type_datetime, .type_timestamp { - string_time := unsafe { cstring_to_vstring(&char(data)) } - if string_time == '' { - primitive = orm.Null{} - } else { - primitive = time.parse(string_time)! + if !is_null[i] { + match types[i] { + orm.type_idx['i8'] { + primitive = *(unsafe { &i8(data) }) + } + orm.type_idx['i16'] { + primitive = *(unsafe { &i16(data) }) + } + orm.type_idx['int'], orm.serial { + primitive = *(unsafe { &int(data) }) + } + orm.type_idx['i64'] { + primitive = *(unsafe { &i64(data) }) + } + orm.type_idx['u8'] { + primitive = *(unsafe { &u8(data) }) + } + orm.type_idx['u16'] { + primitive = *(unsafe { &u16(data) }) + } + orm.type_idx['u32'] { + primitive = *(unsafe { &u32(data) }) + } + orm.type_idx['u64'] { + primitive = *(unsafe { &u64(data) }) + } + orm.type_idx['f32'] { + primitive = *(unsafe { &f32(data) }) + } + orm.type_idx['f64'] { + primitive = *(unsafe { &f64(data) }) + } + orm.type_idx['bool'] { + primitive = *(unsafe { &bool(data) }) + } + orm.type_string { + primitive = unsafe { cstring_to_vstring(&char(data)) } + } + orm.time_ { + match field_types[i] { + .type_long { + timestamp := *(unsafe { &int(data) }) + primitive = time.unix(timestamp) } + .type_datetime, .type_timestamp { + primitive = time.parse(unsafe { cstring_to_vstring(&char(data)) })! + } + else {} } - else {} + } + orm.enum_ { + primitive = *(unsafe { &i64(data) }) + } + else { + return error('Unknown type ${types[i]}') } } - orm.enum_ { - primitive = *(unsafe { &i64(data) }) - } - else { - return error('Unknown type ${types[i]}') - } + } else { + primitive = orm.Null{} } result << primitive } diff --git a/vlib/db/mysql/stmt.c.v b/vlib/db/mysql/stmt.c.v index aa98db378c..5320c4e32a 100644 --- a/vlib/db/mysql/stmt.c.v +++ b/vlib/db/mysql/stmt.c.v @@ -13,6 +13,7 @@ mut: buffer voidptr buffer_length u32 length &u32 + is_null &bool } const mysql_type_decimal = C.MYSQL_TYPE_DECIMAL @@ -250,6 +251,7 @@ pub fn (mut stmt Stmt) bind_null() { stmt.binds << C.MYSQL_BIND{ buffer_type: mysql_type_null length: 0 + is_null: 0 } } @@ -261,16 +263,18 @@ pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) { buffer: buffer buffer_length: buf_len length: 0 + is_null: 0 } } // bind_res will store one result in the statement `stmt` -pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&u8, lengths []u32, num_fields int) { +pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&u8, lengths []u32, is_null []bool, num_fields int) { for i in 0 .. num_fields { stmt.res << C.MYSQL_BIND{ buffer_type: unsafe { fields[i].type } buffer: dataptr[i] length: &lengths[i] + is_null: &is_null[i] } } }