1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

orm: add type checker for where (#17179)

This commit is contained in:
walking devel 2023-02-01 08:53:34 +00:00 committed by GitHub
parent 10261c427f
commit 02fc58d124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 3 deletions

View File

@ -1618,6 +1618,12 @@ pub fn (t &TypeSymbol) find_field(name string) ?StructField {
} }
} }
pub fn (t &TypeSymbol) has_field(name string) bool {
t.find_field(name) or { return false }
return true
}
fn (a &Aggregate) find_field(name string) ?StructField { fn (a &Aggregate) find_field(name string) ?StructField {
for mut field in unsafe { a.fields } { for mut field in unsafe { a.fields } {
if field.name == name { if field.name == name {

View File

@ -42,9 +42,11 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
} else { } else {
ast.Type(0) ast.Type(0)
} }
mut n := ast.SqlExpr{ mut n := ast.SqlExpr{
pos: node.pos pos: node.pos
has_where: true has_where: true
where_expr: ast.None{}
typ: typ typ: typ
db_expr: node.db_expr db_expr: node.db_expr
table_expr: ast.TypeNode{ table_expr: ast.TypeNode{
@ -52,6 +54,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
typ: typ typ: typ
} }
} }
tmp_inside_sql := c.inside_sql tmp_inside_sql := c.inside_sql
c.sql_expr(mut n) c.sql_expr(mut n)
c.inside_sql = tmp_inside_sql c.inside_sql = tmp_inside_sql
@ -100,19 +103,19 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
node.fields = fields node.fields = fields
node.sub_structs = sub_structs.move() node.sub_structs = sub_structs.move()
field_names := fields.map(it.name)
if node.has_where { if node.has_where {
c.expr(node.where_expr) c.expr(node.where_expr)
c.check_expr_has_no_fn_calls_with_non_orm_return_type(&node.where_expr) c.check_expr_has_no_fn_calls_with_non_orm_return_type(&node.where_expr)
c.check_where_expr_has_no_pointless_exprs(sym, field_names, &node.where_expr)
} }
if node.has_order { if node.has_order {
if mut node.order_expr is ast.Ident { if mut node.order_expr is ast.Ident {
order_ident_name := node.order_expr.name order_ident_name := node.order_expr.name
sym.find_field(order_ident_name) or { if !sym.has_field(order_ident_name) {
field_names := fields.map(it.name)
c.orm_error(util.new_suggestion(order_ident_name, field_names).say('`${sym.name}` structure has no field with name `${order_ident_name}`'), c.orm_error(util.new_suggestion(order_ident_name, field_names).say('`${sym.name}` structure has no field with name `${order_ident_name}`'),
node.order_expr.pos) node.order_expr.pos)
return ast.void_type return ast.void_type
@ -402,6 +405,48 @@ fn (mut c Checker) check_expr_has_no_fn_calls_with_non_orm_return_type(expr &ast
} }
} }
// check_where_expr_has_no_pointless_exprs checks that an expression has no pointless expressions
// which don't affect the result. For example, `where 3` is pointless.
// Also, it checks that the left side of the infix expression is always the structure field.
fn (mut c Checker) check_where_expr_has_no_pointless_exprs(table_type_symbol &ast.TypeSymbol, field_names []string, expr &ast.Expr) {
// Skip type checking for generated subqueries
// that are not linked to scope and vars but only created for cgen.
if expr is ast.None {
return
}
if expr is ast.InfixExpr {
has_no_field_error := "left side of the `${expr.op}` expression must be one of the `${table_type_symbol.name}`'s fields"
if expr.left is ast.Ident {
left_ident_name := expr.left.name
if !table_type_symbol.has_field(left_ident_name) {
c.orm_error(util.new_suggestion(left_ident_name, field_names).say(has_no_field_error),
expr.left.pos)
}
} else if expr.left is ast.InfixExpr || expr.left is ast.ParExpr
|| expr.left is ast.PrefixExpr {
c.check_where_expr_has_no_pointless_exprs(table_type_symbol, field_names,
&expr.left)
} else {
c.orm_error(has_no_field_error, expr.left.pos())
}
if expr.right is ast.InfixExpr || expr.right is ast.ParExpr || expr.right is ast.PrefixExpr {
c.check_where_expr_has_no_pointless_exprs(table_type_symbol, field_names,
&expr.right)
}
} else if expr is ast.ParExpr {
c.check_where_expr_has_no_pointless_exprs(table_type_symbol, field_names, &expr.expr)
} else if expr is ast.PrefixExpr {
c.check_where_expr_has_no_pointless_exprs(table_type_symbol, field_names, &expr.right)
} else {
c.orm_error('`where` expression must have at least one comparison for filtering rows',
expr.pos())
}
}
fn (_ &Checker) fn_return_type_flag_to_string(typ ast.Type) string { fn (_ &Checker) fn_return_type_flag_to_string(typ ast.Type) string {
is_result_type := typ.has_flag(.result) is_result_type := typ.has_flag(.result)
is_option_type := typ.has_flag(.option) is_option_type := typ.has_flag(.option)

View File

@ -0,0 +1,8 @@
vlib/v/checker/tests/orm_left_side_expr_in_infix_expr_has_no_struct_field_error.vv:18:26: error: V ORM: left side of the `==` expression must be one of the `User`'s fields.
1 possibility: `id`.
16 |
17 | users := sql db {
18 | select from User where first == second
| ~~~~~
19 | }
20 |

View File

@ -0,0 +1,22 @@
import db.sqlite
struct User {
id int [primary; sql: serial]
}
fn main() {
mut db := sqlite.connect(':memory:') or { panic(err) }
sql db {
create table User
}
first := 'first'
second := 'second'
users := sql db {
select from User where first == second
}
println(users)
}

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/orm_wrong_where_expr_error.vv:15:26: error: V ORM: `where` expression must have at least one comparison for filtering rows
13 |
14 | users := sql db {
15 | select from User where 3
| ^
16 | }
17 |

View File

@ -0,0 +1,19 @@
import db.sqlite
struct User {
id int [primary; sql: serial]
}
fn main() {
mut db := sqlite.connect(':memory:') or { panic(err) }
sql db {
create table User
}
users := sql db {
select from User where 3
}
println(users)
}