From 8198e9ecda84101484435df6f612e3609e9aec37 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Tue, 28 Oct 2025 14:01:25 +0800 Subject: [PATCH] orm: fix double fkey insert and update (fix #25593) (#25606) --- vlib/orm/orm_fk_test.v | 63 ++++++++++++++++++++++++ vlib/v/checker/orm.v | 7 +++ vlib/v/checker/tests/orm_fkey_update.out | 7 +++ vlib/v/checker/tests/orm_fkey_update.vv | 58 ++++++++++++++++++++++ vlib/v/gen/c/orm.v | 3 +- 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 vlib/v/checker/tests/orm_fkey_update.out create mode 100644 vlib/v/checker/tests/orm_fkey_update.vv diff --git a/vlib/orm/orm_fk_test.v b/vlib/orm/orm_fk_test.v index 552068bf4..7573a0213 100644 --- a/vlib/orm/orm_fk_test.v +++ b/vlib/orm/orm_fk_test.v @@ -103,3 +103,66 @@ fn test_fkey_insert_as_assignment_expr() { }! assert res.len == 2 } + +struct Foo2 { + id int @[primary; sql: serial] + name string + children []Child2 @[fkey: 'parent_id'] +} + +struct Child2 { + id int @[primary; sql: serial] + parent_id int + name string + bar ?Bar2 @[fkey: 'child_id'] +} + +struct Bar2 { + id int @[primary; sql: serial] + child_id int + name string +} + +fn test_double_fkey_insert() { + db := sqlite.connect(':memory:')! + + sql db { + create table Foo2 + create table Child2 + create table Bar2 + }! + + child_one := Child2{ + name: 'abc' + } + + child_two := Child2{ + name: 'def' + } + + bar_one := Bar2{ + id: 0 + name: 'name' + } + + foo := Foo2{ + name: 'abc' + children: [ + child_one, + child_two, + ] + } + _ := sql db { + insert foo into Foo2 + }! + + result := sql db { + select from Foo2 where id == 1 + }! + assert result[0].children.len == 2 + + res := sql db { + select from Child2 + }! + assert res.len == 2 +} diff --git a/vlib/v/checker/orm.v b/vlib/v/checker/orm.v index 337618bb3..069957de7 100644 --- a/vlib/v/checker/orm.v +++ b/vlib/v/checker/orm.v @@ -327,6 +327,13 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { } field := updated_fields.first() + for attr in field.attrs { + if attr.name == 'fkey' { + c.orm_error("`${column}` is a foreign column of `${table_sym.name}`, it can't update here", + node.pos) + break + } + } node.updated_columns[i] = c.fetch_field_name(field) } diff --git a/vlib/v/checker/tests/orm_fkey_update.out b/vlib/v/checker/tests/orm_fkey_update.out new file mode 100644 index 000000000..0bbb9e7a5 --- /dev/null +++ b/vlib/v/checker/tests/orm_fkey_update.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/orm_fkey_update.vv:56:10: error: ORM: `bar` is a foreign column of `Child`, it can't update here + 54 | + 55 | sql db { + 56 | update Child set bar = bar_one where id == 0 + | ~~~~~ + 57 | }! + 58 | } diff --git a/vlib/v/checker/tests/orm_fkey_update.vv b/vlib/v/checker/tests/orm_fkey_update.vv new file mode 100644 index 000000000..878e3a6df --- /dev/null +++ b/vlib/v/checker/tests/orm_fkey_update.vv @@ -0,0 +1,58 @@ +import db.sqlite + +struct Foo { + id int @[primary; sql: serial] + name string + children []Child @[fkey: 'parent_id'] +} + +struct Child { + id int @[primary; sql: serial] + parent_id int + name string + bar ?Bar @[fkey: 'child_id'] +} + +struct Bar { + id int @[primary; sql: serial] + child_id int + name string +} + +fn main() { + db := sqlite.connect(':memory:')! + + sql db { + create table Foo + create table Child + create table Bar + }! + + child_one := Child{ + name: 'abc' + } + + child_two := Child{ + name: 'def' + } + + bar_one := Bar{ + id: 0 + name: 'name' + } + + foo := Foo{ + name: 'abc' + children: [ + child_one, + child_two, + ] + } + _ := sql db { + insert foo into Foo + }! + + sql db { + update Child set bar = bar_one where id == 0 + }! +} diff --git a/vlib/v/gen/c/orm.v b/vlib/v/gen/c/orm.v index ed72b64d9..012ba250b 100644 --- a/vlib/v/gen/c/orm.v +++ b/vlib/v/gen/c/orm.v @@ -558,6 +558,7 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v last_ids := g.new_tmp_var() res_ := g.new_tmp_var() tmp_var := g.new_tmp_var() + g.writeln('Array_orm__Primitive ${last_ids} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);') if is_option { g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)builtin__array_get(*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data, ${idx}));') } else { @@ -583,8 +584,6 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v unsafe { fff.free() } g.write_orm_insert_with_last_ids(arr, connection_var_name, g.get_table_name_by_struct_type(arr.table_expr.typ), last_ids, res_, id_name, fkeys[i], or_expr) - // Validates sub insertion success otherwise, handled and propagated error. - g.or_block(res_, or_expr, ast.int_type.set_flag(.result)) g.indent-- g.writeln('}') } -- 2.39.5