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_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
}

View File

@ -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
*/

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}
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
}

View File

@ -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]
}
}
}