db.mysql: add null result support (fix #24130) (#24131)

This commit is contained in:
kbkpbot 2025-04-04 19:26:16 +08:00 committed by GitHub
parent 87f92b1f66
commit b1ab54bb1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 111 additions and 68 deletions

View File

@ -448,6 +448,7 @@ pub fn (stmt &StmtHandle) execute(params []string) ![]Row {
buffer: param.str buffer: param.str
buffer_length: u32(param.len) buffer_length: u32(param.len)
length: 0 length: 0
is_null: 0
} }
bind_params << bind bind_params << bind
} }
@ -470,6 +471,7 @@ pub fn (stmt &StmtHandle) execute(params []string) ![]Row {
} }
num_cols := C.mysql_num_fields(query_metadata) num_cols := C.mysql_num_fields(query_metadata)
mut length := []u32{len: num_cols} mut length := []u32{len: num_cols}
mut is_null := []bool{len: num_cols}
mut binds := []C.MYSQL_BIND{} mut binds := []C.MYSQL_BIND{}
for i in 0 .. num_cols { for i in 0 .. num_cols {
@ -478,6 +480,7 @@ pub fn (stmt &StmtHandle) execute(params []string) ![]Row {
buffer: 0 buffer: 0
buffer_length: 0 buffer_length: 0
length: unsafe { &length[i] } length: unsafe { &length[i] }
is_null: unsafe { &is_null[i] }
} }
binds << bind binds << bind
} }

View File

@ -28,6 +28,7 @@ mut:
created_at time.Time @[sql_type: 'DATETIME'] created_at time.Time @[sql_type: 'DATETIME']
updated_at string @[sql_type: 'DATETIME'] updated_at string @[sql_type: 'DATETIME']
deleted_at time.Time deleted_at time.Time
null_date ?time.Time @[sql_type: 'DATETIME']
} }
struct TestDefaultAttribute { struct TestDefaultAttribute {
@ -179,11 +180,26 @@ fn test_mysql_orm() {
panic(err) panic(err)
} }
model := TestTimeType{ model1 := TestTimeType{
username: 'hitalo' username: 'hitalo'
created_at: today created_at: today
updated_at: today.str() updated_at: today.str()
deleted_at: today 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 { sql db {
@ -191,21 +207,41 @@ fn test_mysql_orm() {
}! }!
sql db { sql db {
insert model into TestTimeType insert model1 into TestTimeType
insert model2 into TestTimeType
insert model3 into TestTimeType
}! }!
results := sql db { results := sql db {
select from TestTimeType where username == 'hitalo' select from TestTimeType
}! }!
sql db { sql db {
drop table TestTimeType drop table TestTimeType
}! }!
assert results[0].created_at == model.created_at assert results[0].created_at == model1.created_at
assert results[0].username == model.username assert results[0].username == model1.username
assert results[0].updated_at == model.updated_at assert results[0].updated_at == model1.updated_at
assert results[0].deleted_at == model.deleted_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 /** test default attribute
*/ */

View File

@ -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} mut lengths := []u32{len: int(num_fields), init: 0}
stmt.bind_res(fields, data_pointers, lengths, num_fields) 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 types := config.types.clone()
mut field_types := []FieldType{} 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)! 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()! 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` // data_pointers_to_primitives returns an array of `Primitive`
// cast from `data_pointers` using `types`. // 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{} mut result := []orm.Primitive{}
for i, data in data_pointers { for i, data in data_pointers {
mut primitive := orm.Primitive(0) mut primitive := orm.Primitive(0)
match types[i] { if !is_null[i] {
orm.type_idx['i8'] { match types[i] {
primitive = *(unsafe { &i8(data) }) orm.type_idx['i8'] {
} primitive = *(unsafe { &i8(data) })
orm.type_idx['i16'] { }
primitive = *(unsafe { &i16(data) }) orm.type_idx['i16'] {
} primitive = *(unsafe { &i16(data) })
orm.type_idx['int'], orm.serial { }
primitive = *(unsafe { &int(data) }) orm.type_idx['int'], orm.serial {
} primitive = *(unsafe { &int(data) })
orm.type_idx['i64'] { }
primitive = *(unsafe { &i64(data) }) orm.type_idx['i64'] {
} primitive = *(unsafe { &i64(data) })
orm.type_idx['u8'] { }
primitive = *(unsafe { &u8(data) }) orm.type_idx['u8'] {
} primitive = *(unsafe { &u8(data) })
orm.type_idx['u16'] { }
primitive = *(unsafe { &u16(data) }) orm.type_idx['u16'] {
} primitive = *(unsafe { &u16(data) })
orm.type_idx['u32'] { }
primitive = *(unsafe { &u32(data) }) orm.type_idx['u32'] {
} primitive = *(unsafe { &u32(data) })
orm.type_idx['u64'] { }
primitive = *(unsafe { &u64(data) }) orm.type_idx['u64'] {
} primitive = *(unsafe { &u64(data) })
orm.type_idx['f32'] { }
primitive = *(unsafe { &f32(data) }) orm.type_idx['f32'] {
} primitive = *(unsafe { &f32(data) })
orm.type_idx['f64'] { }
primitive = *(unsafe { &f64(data) }) orm.type_idx['f64'] {
} primitive = *(unsafe { &f64(data) })
orm.type_idx['bool'] { }
primitive = *(unsafe { &bool(data) }) orm.type_idx['bool'] {
} primitive = *(unsafe { &bool(data) })
orm.type_string { }
primitive = unsafe { cstring_to_vstring(&char(data)) } orm.type_string {
} primitive = unsafe { cstring_to_vstring(&char(data)) }
orm.time_ { }
match field_types[i] { orm.time_ {
.type_long { match field_types[i] {
timestamp := *(unsafe { &int(data) }) .type_long {
primitive = time.unix(timestamp) 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)!
} }
.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_ { } else {
primitive = *(unsafe { &i64(data) }) primitive = orm.Null{}
}
else {
return error('Unknown type ${types[i]}')
}
} }
result << primitive result << primitive
} }

View File

@ -13,6 +13,7 @@ mut:
buffer voidptr buffer voidptr
buffer_length u32 buffer_length u32
length &u32 length &u32
is_null &bool
} }
const mysql_type_decimal = C.MYSQL_TYPE_DECIMAL const mysql_type_decimal = C.MYSQL_TYPE_DECIMAL
@ -250,6 +251,7 @@ pub fn (mut stmt Stmt) bind_null() {
stmt.binds << C.MYSQL_BIND{ stmt.binds << C.MYSQL_BIND{
buffer_type: mysql_type_null buffer_type: mysql_type_null
length: 0 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: buffer
buffer_length: buf_len buffer_length: buf_len
length: 0 length: 0
is_null: 0
} }
} }
// bind_res will store one result in the statement `stmt` // 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 { for i in 0 .. num_fields {
stmt.res << C.MYSQL_BIND{ stmt.res << C.MYSQL_BIND{
buffer_type: unsafe { fields[i].type } buffer_type: unsafe { fields[i].type }
buffer: dataptr[i] buffer: dataptr[i]
length: &lengths[i] length: &lengths[i]
is_null: &is_null[i]
} }
} }
} }