From a489417484645fbcf1116eeab31b240066d77abd Mon Sep 17 00:00:00 2001 From: walking devel <104449470+walkingdevel@users.noreply.github.com> Date: Mon, 30 Jan 2023 09:26:10 +0000 Subject: [PATCH] orm: detect type mismatching on inserting an object (#17157) --- vlib/v/ast/ast.v | 4 ++++ vlib/v/checker/orm.v | 22 +++++++++++++++++++ ...sert_object_with_mismatched_type_error.out | 7 ++++++ ...nsert_object_with_mismatched_type_error.vv | 21 ++++++++++++++++++ ...using_undefined_object_in_insert_error.out | 7 ++++++ ..._using_undefined_object_in_insert_error.vv | 15 +++++++++++++ vlib/v/parser/sql.v | 4 ++++ 7 files changed, 80 insertions(+) create mode 100644 vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.out create mode 100644 vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.vv create mode 100644 vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.out create mode 100644 vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.vv diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index c3d5add4db..90c93c49eb 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1751,6 +1751,10 @@ pub: pos token.Pos where_expr Expr update_exprs []Expr // for `update` + // is_top_level indicates that a statement is parsed from code + // and is not inserted by ORM for inserting in related tables. + is_top_level bool + scope &Scope = unsafe { nil } pub mut: object_var_name string // `user` updated_columns []string // for `update set x=y` diff --git a/vlib/v/checker/orm.v b/vlib/v/checker/orm.v index 7d670e1ffe..120e41a466 100644 --- a/vlib/v/checker/orm.v +++ b/vlib/v/checker/orm.v @@ -28,6 +28,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { info := sym.info as ast.Struct mut fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, sym.name) mut sub_structs := map[int]ast.SqlExpr{} + 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_)) @@ -52,6 +53,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { tmp_inside_sql := c.inside_sql c.sql_expr(mut n) c.inside_sql = tmp_inside_sql + n.where_expr = ast.InfixExpr{ op: .eq pos: n.pos @@ -85,6 +87,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { sub_structs[int(typ)] = n } + if node.is_count { fields = [ ast.StructField{ @@ -92,8 +95,10 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { }, ] } + node.fields = fields node.sub_structs = sub_structs.move() + if node.has_where { c.expr(node.where_expr) c.check_expr_has_no_fn_calls_with_non_orm_return_type(&node.where_expr) @@ -150,6 +155,23 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { defer { c.cur_orm_ts = old_ts } + + if node.kind == .insert && node.is_top_level { + inserting_object_name := node.object_var_name + inserting_object_var := node.scope.find(inserting_object_name) or { + c.error('undefined ident: `${inserting_object_name}`', node.pos) + return ast.void_type + } + + if inserting_object_var.typ != node.table_expr.typ { + table_name := table_sym.name + inserting_type_name := c.table.sym(inserting_object_var.typ).name + + c.error('cannot use `${inserting_type_name}` as `${table_name}`', node.pos) + return ast.void_type + } + } + if table_sym.info !is ast.Struct { c.error('unknown type `${table_sym.name}`', node.pos) return ast.void_type diff --git a/vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.out b/vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.out new file mode 100644 index 0000000000..30930ed76e --- /dev/null +++ b/vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.vv:19:10: error: cannot use `Tiddler` as `Tiddlers` + 17 | sql db { + 18 | create table Tiddlers + 19 | insert tiddler into Tiddlers + | ~~~~~~~ + 20 | } + 21 | } diff --git a/vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.vv b/vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.vv new file mode 100644 index 0000000000..901977cf05 --- /dev/null +++ b/vlib/v/checker/tests/orm_insert_object_with_mismatched_type_error.vv @@ -0,0 +1,21 @@ +import db.sqlite +import json + +struct Tiddler { + id int + created int +} + +struct Tiddlers { + id int [primary; sql: serial] + created string +} + +fn main() { + tiddler := json.decode(Tiddler, '{}') or { Tiddler{} } + db := sqlite.connect(':memory:') or { panic(err) } + sql db { + create table Tiddlers + insert tiddler into Tiddlers + } +} diff --git a/vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.out b/vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.out new file mode 100644 index 0000000000..3fa81fb368 --- /dev/null +++ b/vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.vv:13:10: error: undefined ident: `bug` + 11 | sql db { + 12 | create table Node + 13 | insert bug into Node + | ~~~ + 14 | } + 15 | } diff --git a/vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.vv b/vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.vv new file mode 100644 index 0000000000..8af032eb17 --- /dev/null +++ b/vlib/v/checker/tests/orm_using_undefined_object_in_insert_error.vv @@ -0,0 +1,15 @@ +import db.sqlite + +struct Node { + id int [primary; sql: serial] + text string +} + +fn main() { + db := sqlite.connect(':memory:') or { panic(err) } + + sql db { + create table Node + insert bug into Node + } +} diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index 177a72f4f0..508d6090d1 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -203,6 +203,7 @@ fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine { typ: typ pos: typ_pos } + scope: p.scope } } else if n == 'drop' { kind = .drop @@ -220,6 +221,7 @@ fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine { typ: typ pos: typ_pos } + scope: p.scope } } mut inserted_var_name := '' @@ -289,6 +291,8 @@ fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine { update_exprs: update_exprs kind: kind where_expr: where_expr + is_top_level: true + scope: p.scope } }