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:
parent
10261c427f
commit
02fc58d124
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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 |
|
@ -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)
|
||||||
|
}
|
7
vlib/v/checker/tests/orm_wrong_where_expr_error.out
Normal file
7
vlib/v/checker/tests/orm_wrong_where_expr_error.out
Normal 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 |
|
19
vlib/v/checker/tests/orm_wrong_where_expr_error.vv
Normal file
19
vlib/v/checker/tests/orm_wrong_where_expr_error.vv
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user