From ca2820da5fa913774b0826d0d4fc8938dc798f7e Mon Sep 17 00:00:00 2001 From: Mark aka walkingdevel <104449470+walkingdevel@users.noreply.github.com> Date: Tue, 2 May 2023 08:14:42 +0000 Subject: [PATCH] checker, orm: don't insert an uninitialized struct in the related table. (#18093) --- vlib/orm/orm_insert_test.v | 67 ++++++++++++++++++++++++++++++++++++-- vlib/v/checker/orm.v | 21 ++++++++++-- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/vlib/orm/orm_insert_test.v b/vlib/orm/orm_insert_test.v index 62a42b198d..e4a43d7cbd 100644 --- a/vlib/orm/orm_insert_test.v +++ b/vlib/orm/orm_insert_test.v @@ -25,14 +25,75 @@ struct Account { id int [primary; sql: serial] } +struct Package { + id int [primary; sql: serial] + name string [unique] + author User [fkey: 'id'] +} + +struct User { +pub mut: + id int [primary; sql: serial] + username string [unique] +} + pub fn insert_parent(db sqlite.DB, mut parent Parent) ! { sql db { insert parent into Parent }! } +fn test_does_not_insert_uninitialized_field() { + db := sqlite.connect(':memory:')! + + sql db { + create table User + create table Package + }! + + package := Package{ + name: 'xml' + // author + } + + sql db { + insert package into Package + }! + + users := sql db { + select from User + }! + + // users must be empty because the package doesn't have an initialized `User` structure. + assert users.len == 0 +} + +fn test_insert_empty_field() { + db := sqlite.connect(':memory:')! + + sql db { + create table User + create table Package + }! + + package := Package{ + name: 'xml' + author: User{} + } + + sql db { + insert package into Package + }! + + users := sql db { + select from User + }! + + assert users.len == 1 +} + fn test_insert_empty_object() { - db := sqlite.connect(':memory:') or { panic(err) } + db := sqlite.connect(':memory:')! account := Account{} @@ -49,7 +110,7 @@ fn test_insert_empty_object() { } fn test_orm_insert_mut_object() { - db := sqlite.connect(':memory:') or { panic(err) } + db := sqlite.connect(':memory:')! sql db { create table Parent @@ -71,7 +132,7 @@ fn test_orm_insert_mut_object() { } fn test_orm_insert_with_multiple_child_elements() { - mut db := sqlite.connect(':memory:') or { panic(err) } + mut db := sqlite.connect(':memory:')! sql db { create table Parent diff --git a/vlib/v/checker/orm.v b/vlib/v/checker/orm.v index 84b6d3c6e9..ad952942f7 100644 --- a/vlib/v/checker/orm.v +++ b/vlib/v/checker/orm.v @@ -182,9 +182,9 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { defer { c.cur_orm_ts = old_ts } + inserting_object_name := node.object_var_name if node.kind == .insert && !node.is_generated { - inserting_object_name := node.object_var_name inserting_object := node.scope.find(inserting_object_name) or { c.error('undefined ident: `${inserting_object_name}`', node.pos) return ast.void_type @@ -210,12 +210,19 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { } info := table_sym.info as ast.Struct - fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name) + mut fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name) mut sub_structs := map[int]ast.SqlStmtLine{} for f in fields.filter((c.table.type_symbols[int(it.typ)].kind == .struct_ || (c.table.sym(it.typ).kind == .array && c.table.sym(c.table.sym(it.typ).array_info().elem_type).kind == .struct_)) && c.table.get_type_name(it.typ) != 'time.Time') { + // Delete an uninitialized struct from fields and skip adding the current field + // to sub structs to skip inserting an empty struct in the related table. + if c.check_field_of_inserting_struct_is_uninitialized(node, f.name) { + fields.delete(fields.index(f)) + continue + } + c.check_orm_struct_field_attributes(f) typ := if c.table.sym(f.typ).kind == .struct_ { @@ -529,3 +536,13 @@ fn (mut c Checker) check_db_expr(db_expr &ast.Expr) bool { return true } + +fn (_ &Checker) check_field_of_inserting_struct_is_uninitialized(node &ast.SqlStmtLine, field_name string) bool { + struct_scope := node.scope.find_var(node.object_var_name) or { return false } + + if struct_scope.expr is ast.StructInit { + return struct_scope.expr.fields.filter(it.name == field_name).len == 0 + } + + return false +}