From dead5e69b44b4f8c4999ac82b730c21250cb0583 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Thu, 17 Apr 2025 19:45:04 +0800 Subject: [PATCH] json: fix option time (fix #24242) (fix #24175) (#24243) --- vlib/db/mysql/_cdefs.c.v | 4 +- vlib/db/mysql/mysql.c.v | 4 +- vlib/db/mysql/result.c.v | 2 +- vlib/db/mysql/stmt.c.v | 4 +- vlib/v/gen/c/json.v | 34 +++++++++--- vlib/v/gen/c/testdata/json_option_time.out | 13 +++++ vlib/v/gen/c/testdata/json_option_time.vv | 62 ++++++++++++++++++++++ 7 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 vlib/v/gen/c/testdata/json_option_time.out create mode 100644 vlib/v/gen/c/testdata/json_option_time.vv diff --git a/vlib/db/mysql/_cdefs.c.v b/vlib/db/mysql/_cdefs.c.v index 28ec54bee9..7cd3676443 100644 --- a/vlib/db/mysql/_cdefs.c.v +++ b/vlib/db/mysql/_cdefs.c.v @@ -40,7 +40,7 @@ fn C.mysql_real_connect(mysql &C.MYSQL, host &char, user &char, passwd &char, db client_flag ConnectionFlag) &C.MYSQL // C.mysql_query executes the SQL statement pointed to by the null-terminated string `stmt_str`. -fn C.mysql_query(mysql &C.MYSQL, q &u8) int +fn C.mysql_query(mysql &C.MYSQL, const_q charptr) int // C.mysql_use_result initiates a result set retrieval but does not actually read // the result set into the client like `mysql_store_result()` does. @@ -102,7 +102,7 @@ fn C.mysql_ping(mysql &C.MYSQL) int fn C.mysql_store_result(mysql &C.MYSQL) &C.MYSQL_RES // C.mysql_fetch_row retrieves the next row of a result set. -fn C.mysql_fetch_row(res &C.MYSQL_RES) &&u8 +fn C.mysql_fetch_row(res &C.MYSQL_RES) &charptr // C.mysql_fetch_fields returns an array of all `MYSQL_FIELD` structures for a result set. // Each structure provides the field definition for one column of the result set. diff --git a/vlib/db/mysql/mysql.c.v b/vlib/db/mysql/mysql.c.v index 005569ace5..b05626956d 100644 --- a/vlib/db/mysql/mysql.c.v +++ b/vlib/db/mysql/mysql.c.v @@ -108,7 +108,7 @@ pub fn connect(config Config) !DB { // It cannot be used for statements that contain binary data; // Use `real_query()` instead. pub fn (db &DB) query(q string) !Result { - if C.mysql_query(db.conn, q.str) != 0 { + if C.mysql_query(db.conn, charptr(q.str)) != 0 { db.throw_mysql_error()! } @@ -369,7 +369,7 @@ pub fn (db &DB) exec_one(query string) !Row { mut row := Row{} for i in 0 .. num_cols { - if unsafe { row_vals == &u8(0) } || unsafe { row_vals[i] == nil } { + if unsafe { row_vals[i] == nil } { row.vals << '' } else { row.vals << mystring(unsafe { &u8(row_vals[i]) }) diff --git a/vlib/db/mysql/result.c.v b/vlib/db/mysql/result.c.v index d7992473f5..34c98e822e 100644 --- a/vlib/db/mysql/result.c.v +++ b/vlib/db/mysql/result.c.v @@ -34,7 +34,7 @@ pub struct Field { } // fetch_row fetches the next row from a result. -pub fn (r Result) fetch_row() &&u8 { +pub fn (r Result) fetch_row() &charptr { return C.mysql_fetch_row(r.result) } diff --git a/vlib/db/mysql/stmt.c.v b/vlib/db/mysql/stmt.c.v index 5320c4e32a..10a927c60c 100644 --- a/vlib/db/mysql/stmt.c.v +++ b/vlib/db/mysql/stmt.c.v @@ -47,7 +47,7 @@ const mysql_type_geometry = C.MYSQL_TYPE_GEOMETRY const mysql_no_data = C.MYSQL_NO_DATA fn C.mysql_stmt_init(&C.MYSQL) &C.MYSQL_STMT -fn C.mysql_stmt_prepare(&C.MYSQL_STMT, &char, u32) int +fn C.mysql_stmt_prepare(&C.MYSQL_STMT, const_query charptr, u32) int fn C.mysql_stmt_bind_param(&C.MYSQL_STMT, &C.MYSQL_BIND) bool fn C.mysql_stmt_execute(&C.MYSQL_STMT) int fn C.mysql_stmt_close(&C.MYSQL_STMT) bool @@ -86,7 +86,7 @@ pub fn (db DB) init_stmt(query string) Stmt { // prepare a statement for execution. pub fn (stmt Stmt) prepare() ! { - result := C.mysql_stmt_prepare(stmt.stmt, stmt.query.str, stmt.query.len) + result := C.mysql_stmt_prepare(stmt.stmt, charptr(stmt.query.str), stmt.query.len) if result != 0 && stmt.get_error_msg() != '' { return stmt.error(result) diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index ba3b36a237..e0272e391f 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -495,7 +495,14 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st dec.writeln('\t\t\tif (strcmp("Time", ${type_var}) == 0) {') gen_js_get(ret_styp, tmp, 'value', mut dec, true) dec.writeln('\t\t\t\t${variant_typ} ${tmp} = time__unix(${js_dec_name('i64')}(jsonroot_${tmp}));') - dec.writeln('\t\t\t\t${prefix}res = ${variant_typ}_to_sumtype_${sym.cname}(&${tmp});') + if utyp.has_flag(.option) { + dec.writeln('\t\t\t\t${prefix}res.state = 0;') + tmp_time_var := g.new_tmp_var() + dec.writeln('\t\t\t\t${g.base_type(utyp)} ${tmp_time_var} = ${variant_typ}_to_sumtype_${sym.cname}(&${tmp});') + dec.writeln('\t\t\t\tvmemcpy(&${prefix}res.data, ${tmp_time_var}._time__Time, sizeof(${variant_typ}));') + } else { + dec.writeln('\t\t\t\t${prefix}res = ${variant_typ}_to_sumtype_${sym.cname}(&${tmp});') + } dec.writeln('\t\t\t}') } else if !is_js_prim(variant_typ) && variant_sym.kind != .enum { dec.writeln('\t\t\tif (strcmp("${unmangled_variant_name}", ${type_var}) == 0 && ${variant_sym.kind == .array} == cJSON_IsArray(root)) {') @@ -771,11 +778,20 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st tmp := g.new_tmp_var() gen_js_get(styp, tmp, name, mut dec, is_required) dec.writeln('\tif (jsonroot_${tmp}) {') - dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = time__unix(json__decode_u64(jsonroot_${tmp}));') - if field.has_default_expr { - dec.writeln('\t} else {') - dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${g.expr_string_opt(field.typ, - field.default_expr)};') + if field.typ.has_flag(.option) { + dec.writeln('\t\tif (!(cJSON_IsNull(jsonroot_${tmp}))) {\n') + dec.writeln('\t\t\t${prefix}${op}${c_name(field.name)}.state = 0;\n') + tmp_time_var := g.new_tmp_var() + dec.writeln('\t\t\t${g.base_type(field.typ)} ${tmp_time_var} = time__unix(json__decode_u64(jsonroot_${tmp}));\n') + dec.writeln('\t\t\tvmemcpy(&${prefix}${op}${c_name(field.name)}.data, &${tmp_time_var}, sizeof(${g.base_type(field.typ)}));') + dec.writeln('\t\t}\n') + } else { + dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = time__unix(json__decode_u64(jsonroot_${tmp}));') + if field.has_default_expr { + dec.writeln('\t} else {') + dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${g.expr_string_opt(field.typ, + field.default_expr)};') + } } dec.writeln('\t}') } else if field_sym.kind == .alias { @@ -937,7 +953,11 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st if field_sym.name == 'time.Time' { // time struct requires special treatment // it has to be encoded as a unix timestamp number - enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}.__v_unix));') + if is_option { + enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64((*(${g.base_type(field.typ)}*)(${prefix_enc}${op}${c_name(field.name)}.data)).__v_unix));') + } else { + enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}.__v_unix));') + } } else { if !field.typ.is_any_kind_of_pointer() { if field_sym.kind == .alias && field.typ.has_flag(.option) { diff --git a/vlib/v/gen/c/testdata/json_option_time.out b/vlib/v/gen/c/testdata/json_option_time.out new file mode 100644 index 0000000000..1a7ac8fc8d --- /dev/null +++ b/vlib/v/gen/c/testdata/json_option_time.out @@ -0,0 +1,13 @@ +[{'id': Option(Any('123')), 'time': Option(Any(2025-04-16 12:00:00))}, {'id': Option(Any('asd')), 'time': Option(Any(2025-04-16 12:00:00))}] +[{"id":"123","time":{"_type":"Time","value":1744804800}},{"id":"asd","time":{"_type":"Time","value":1744804800}}] +{"a":1,"s":"hello","t":1744804800,"opt_u64":123456} +MyStruct{ + a: 1 + opt_a: Option(none) + s: 'hello' + opt_s: Option(none) + t: 2025-04-16 12:00:00 + opt_v: Option(none) + opt_t: Option(none) + opt_u64: Option(123456) +} diff --git a/vlib/v/gen/c/testdata/json_option_time.vv b/vlib/v/gen/c/testdata/json_option_time.vv new file mode 100644 index 0000000000..955bbeb32a --- /dev/null +++ b/vlib/v/gen/c/testdata/json_option_time.vv @@ -0,0 +1,62 @@ +import json +import time + +const default_time = time.parse('2025-04-16 12:00:00') or {time.now()} + +type Any = string | []map[string]?Any | time.Time + +struct Users { +mut: + id string + created_at time.Time +} + +struct EmbeddedStruct { + v int +} + +struct MyStruct { + a int + opt_a ?int + s string + opt_s ?string + t time.Time = default_time + opt_v ?EmbeddedStruct + opt_t ?time.Time + opt_u64 ?u64 +} + +data_all := [Users{ + id: '123' + created_at: default_time +}, Users{ + id: 'asd' + created_at: default_time +}] + +mut result := []map[string]?Any{} +for raw in data_all { + mut data := map[string]?Any{} + data['id'] = raw.id + data['time'] = raw.created_at + result << data +} + +println(result) + +msg := json.encode(result) +println(msg) + +x := MyStruct{ + a: 1 + s: 'hello' + t: default_time + // opt_t : default_time + opt_u64: 123456 +} + +y := json.encode(x) +println(y) + +k := json.decode(MyStruct, y)! +println(k)