From cd64ae9ca0a169550249546ec60cabb5a8851406 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 04:37:02 +0300 Subject: [PATCH] orm: fix inserting many-to-many relation (fixes #24832) --- vlib/v/ast/ast.v | 4 +-- vlib/v/checker/orm.v | 10 +++--- vlib/v/gen/c/orm.v | 18 +++++------ vlib/v/tests/orm_sub_struct_test.v | 52 ++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 66d3cc5a0..c03d7b8e2 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -2308,7 +2308,7 @@ pub mut: updated_columns []string // for `update set x=y` table_expr TypeNode fields []StructField - sub_structs map[int]SqlStmtLine + sub_structs map[string]SqlStmtLine where_expr Expr update_exprs []Expr // for `update` pre_comments []Comment @@ -2368,7 +2368,7 @@ pub mut: offset_expr Expr table_expr TypeNode fields []StructField - sub_structs map[int]SqlExpr + sub_structs map[string]SqlExpr or_expr OrExpr joins []JoinClause // JOIN clauses for this query aggregate_field_type Type diff --git a/vlib/v/checker/orm.v b/vlib/v/checker/orm.v index 1a309d990..65e6c2c02 100644 --- a/vlib/v/checker/orm.v +++ b/vlib/v/checker/orm.v @@ -51,7 +51,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { info := table_sym.info as ast.Struct mut fields := c.fetch_and_check_orm_fields(info, node.table_expr.pos, table_sym.name) non_primitive_fields := c.get_orm_non_primitive_fields(fields) - mut sub_structs := map[int]ast.SqlExpr{} + mut sub_structs := map[string]ast.SqlExpr{} mut has_primary := false mut primary_field := ast.StructField{} @@ -179,12 +179,12 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { } } - sub_structs[int(field.typ)] = subquery_expr + sub_structs[field.name] = subquery_expr } field_names := fields.map(it.name) if node.aggregate_kind != .none { - node.sub_structs = map[int]ast.SqlExpr{} + node.sub_structs = map[string]ast.SqlExpr{} if node.aggregate_kind == .count { node.fields = [ ast.StructField{ @@ -358,7 +358,7 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { } fields = insert_fields.clone() - mut sub_structs := map[int]ast.SqlStmtLine{} + mut sub_structs := map[string]ast.SqlStmtLine{} non_primitive_fields := c.get_orm_non_primitive_fields(fields) for field in non_primitive_fields { @@ -394,7 +394,7 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { tmp_inside_sql := c.inside_sql c.sql_stmt_line(mut subquery_expr) c.inside_sql = tmp_inside_sql - sub_structs[field.typ] = subquery_expr + sub_structs[field.name] = subquery_expr } node.fields = fields diff --git a/vlib/v/gen/c/orm.v b/vlib/v/gen/c/orm.v index 81d66ba95..1e2c1f9a3 100644 --- a/vlib/v/gen/c/orm.v +++ b/vlib/v/gen/c/orm.v @@ -104,9 +104,9 @@ fn (mut g Gen) sql_insert_expr(node ast.SqlExpr) { } fn (mut g Gen) build_sql_stmt_line_from_sql_expr(node ast.SqlExpr) ast.SqlStmtLine { - mut sub_structs := map[int]ast.SqlStmtLine{} - for typ, sub in node.sub_structs { - sub_structs[typ] = g.build_sql_stmt_line_from_sql_expr(sub) + mut sub_structs := map[string]ast.SqlStmtLine{} + for key, sub in node.sub_structs { + sub_structs[key] = g.build_sql_stmt_line_from_sql_expr(sub) } return ast.SqlStmtLine{ object_var: node.inserted_var @@ -422,8 +422,8 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v final_field_typ := g.table.final_type(field.typ) sym := g.table.sym(final_field_typ) if sym.kind == .struct && sym.name != 'time.Time' { - if final_field_typ in node.sub_structs { - subs << unsafe { node.sub_structs[int(final_field_typ)] } + if field.name in node.sub_structs { + subs << unsafe { node.sub_structs[field.name] } unwrapped_c_typ := g.styp(final_field_typ.clear_flag(.option)) subs_unwrapped_c_typ << if final_field_typ.has_flag(.option) { @@ -442,8 +442,8 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v if final_field_typ.has_flag(.option) { opt_fields << arrs.len } - if final_field_typ in node.sub_structs { - arrs << unsafe { node.sub_structs[int(final_field_typ)] } + if field.name in node.sub_structs { + arrs << unsafe { node.sub_structs[field.name] } } field_names << field.name } @@ -1291,7 +1291,7 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, re field_var := '${tmp}.${orm_field_access_name(field.name)}' field_c_typ := g.styp(final_field_typ) if sym.kind == .struct && sym.name != 'time.Time' { - mut sub := node.sub_structs[int(final_field_typ)] or { continue } + mut sub := node.sub_structs[field.name] or { continue } mut where_expr := sub.where_expr as ast.InfixExpr mut ident := where_expr.right as ast.Ident primitive_type_index := g.table.find_type('orm.Primitive') @@ -1328,7 +1328,7 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, re } else { verror('missing fkey attribute') } - sub := node.sub_structs[final_field_typ] or { continue } + sub := node.sub_structs[field.name] or { continue } if sub.has_where { mut where_expr := sub.where_expr as ast.InfixExpr mut left_where_expr := where_expr.left as ast.Ident diff --git a/vlib/v/tests/orm_sub_struct_test.v b/vlib/v/tests/orm_sub_struct_test.v index 2847e64f2..8cfbf481f 100644 --- a/vlib/v/tests/orm_sub_struct_test.v +++ b/vlib/v/tests/orm_sub_struct_test.v @@ -11,6 +11,18 @@ struct SubStruct { name string } +struct OrmDuplicateHuman { + id int @[primary; sql: serial] + name string +} + +struct OrmDuplicateRelation { + id int @[primary; sql: serial] + kind string + human1 OrmDuplicateHuman @[fkey: 'id'] + human2 OrmDuplicateHuman @[fkey: 'id'] +} + fn test_orm_sub_structs() { mut db := sqlite.connect(':memory:') or { panic(err) } sql db { @@ -37,3 +49,43 @@ fn test_orm_sub_structs() { assert uppers.first().sub.name == upper_1.sub.name db.close()! } + +fn test_orm_sub_structs_with_duplicate_foreign_type_fields() { + mut db := sqlite.connect(':memory:') or { panic(err) } + sql db { + create table OrmDuplicateHuman + }! + sql db { + create table OrmDuplicateRelation + }! + + relation := OrmDuplicateRelation{ + kind: 'couple' + human1: OrmDuplicateHuman{ + name: 'adam' + } + human2: OrmDuplicateHuman{ + name: 'ewa' + } + } + + sql db { + insert relation into OrmDuplicateRelation + }! + + humans := sql db { + select from OrmDuplicateHuman order by id + }! + assert humans.len == 2 + assert humans[0].name == 'adam' + assert humans[1].name == 'ewa' + + relations := sql db { + select from OrmDuplicateRelation where id == 1 + }! + assert relations.len == 1 + assert relations[0].human1.name == 'adam' + assert relations[0].human2.name == 'ewa' + assert relations[0].human1.id != relations[0].human2.id + db.close()! +} -- 2.39.5