mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
orm: support fn calls in where
(#17127)
This commit is contained in:
parent
c14d15bd3d
commit
a9a04bba55
@ -125,6 +125,7 @@ const (
|
|||||||
'vlib/orm/orm_sql_or_blocks_test.v',
|
'vlib/orm/orm_sql_or_blocks_test.v',
|
||||||
'vlib/orm/orm_create_and_drop_test.v',
|
'vlib/orm/orm_create_and_drop_test.v',
|
||||||
'vlib/orm/orm_insert_test.v',
|
'vlib/orm/orm_insert_test.v',
|
||||||
|
'vlib/orm/orm_fn_calls_test.v',
|
||||||
'vlib/db/sqlite/sqlite_test.v',
|
'vlib/db/sqlite/sqlite_test.v',
|
||||||
'vlib/db/sqlite/sqlite_orm_test.v',
|
'vlib/db/sqlite/sqlite_orm_test.v',
|
||||||
'vlib/db/sqlite/sqlite_vfs_lowlevel_test.v',
|
'vlib/db/sqlite/sqlite_vfs_lowlevel_test.v',
|
||||||
@ -192,6 +193,7 @@ const (
|
|||||||
'vlib/orm/orm_sql_or_blocks_test.v',
|
'vlib/orm/orm_sql_or_blocks_test.v',
|
||||||
'vlib/orm/orm_create_and_drop_test.v',
|
'vlib/orm/orm_create_and_drop_test.v',
|
||||||
'vlib/orm/orm_insert_test.v',
|
'vlib/orm/orm_insert_test.v',
|
||||||
|
'vlib/orm/orm_fn_calls_test.v',
|
||||||
'vlib/v/tests/orm_sub_struct_test.v',
|
'vlib/v/tests/orm_sub_struct_test.v',
|
||||||
'vlib/v/tests/orm_sub_array_struct_test.v',
|
'vlib/v/tests/orm_sub_array_struct_test.v',
|
||||||
'vlib/v/tests/orm_joined_tables_select_test.v',
|
'vlib/v/tests/orm_joined_tables_select_test.v',
|
||||||
|
48
vlib/orm/orm_fn_calls_test.v
Normal file
48
vlib/orm/orm_fn_calls_test.v
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
struct User {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
name string
|
||||||
|
age int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_acceptable_age() int {
|
||||||
|
return 21
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn_calls() {
|
||||||
|
mut db := sqlite.connect(':memory:') or { panic(err) }
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table User
|
||||||
|
}
|
||||||
|
|
||||||
|
first_user := User{
|
||||||
|
name: 'first'
|
||||||
|
age: 25
|
||||||
|
}
|
||||||
|
|
||||||
|
second_user := User{
|
||||||
|
name: 'second'
|
||||||
|
age: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert first_user into User
|
||||||
|
insert second_user into User
|
||||||
|
}
|
||||||
|
|
||||||
|
users_with_acceptable_age := sql db {
|
||||||
|
select from User where age >= get_acceptable_age()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert users_with_acceptable_age.len == 1
|
||||||
|
assert users_with_acceptable_age.first().name == 'first'
|
||||||
|
|
||||||
|
users_with_non_acceptable_age := sql db {
|
||||||
|
select from User where age < get_acceptable_age()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert users_with_non_acceptable_age.len == 1
|
||||||
|
assert users_with_non_acceptable_age.first().name == 'second'
|
||||||
|
}
|
@ -96,6 +96,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
|||||||
node.sub_structs = sub_structs.move()
|
node.sub_structs = sub_structs.move()
|
||||||
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)
|
||||||
}
|
}
|
||||||
if node.has_offset {
|
if node.has_offset {
|
||||||
c.expr(node.offset_expr)
|
c.expr(node.offset_expr)
|
||||||
@ -273,3 +274,45 @@ fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Pos, t
|
|||||||
}
|
}
|
||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check_expr_has_no_fn_calls_with_non_orm_return_type checks that an expression has no function calls
|
||||||
|
// that return complex types which can't be transformed into SQL.
|
||||||
|
fn (mut c Checker) check_expr_has_no_fn_calls_with_non_orm_return_type(expr &ast.Expr) {
|
||||||
|
if expr is ast.CallExpr {
|
||||||
|
// `expr.return_type` may be empty. For example, a user call function incorrectly without passing all required arguments.
|
||||||
|
// This error will be handled in another place. Otherwise, `c.table.sym` below does panic.
|
||||||
|
//
|
||||||
|
// fn test(flag bool) {}
|
||||||
|
// test()
|
||||||
|
// ~~~~~~ expected 1 arguments, but got 0
|
||||||
|
if expr.return_type == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type_symbol := c.table.sym(expr.return_type)
|
||||||
|
is_result_type := expr.return_type.has_flag(.result)
|
||||||
|
is_option_type := expr.return_type.has_flag(.option)
|
||||||
|
is_time := type_symbol.cname == 'time__Time'
|
||||||
|
is_not_pointer := !type_symbol.is_pointer()
|
||||||
|
is_error_type := is_result_type || is_option_type
|
||||||
|
is_acceptable_type := (type_symbol.is_primitive() || is_time) && is_not_pointer
|
||||||
|
&& !is_error_type
|
||||||
|
|
||||||
|
if !is_acceptable_type {
|
||||||
|
error_type_symbol := if is_result_type {
|
||||||
|
'!'
|
||||||
|
} else if is_option_type {
|
||||||
|
'?'
|
||||||
|
} else {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
c.error('V ORM: function calls must return only primitive types and time.Time, but `${expr.name}` returns `${error_type_symbol}${type_symbol.name}`',
|
||||||
|
expr.pos)
|
||||||
|
}
|
||||||
|
} else if expr is ast.ParExpr {
|
||||||
|
c.check_expr_has_no_fn_calls_with_non_orm_return_type(&expr.expr)
|
||||||
|
} else if expr is ast.InfixExpr {
|
||||||
|
c.check_expr_has_no_fn_calls_with_non_orm_return_type(&expr.left)
|
||||||
|
c.check_expr_has_no_fn_calls_with_non_orm_return_type(&expr.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
vlib/v/checker/tests/orm_fn_call_with_wrong_return_type.vv:52:37: error: V ORM: function calls must return only primitive types and time.Time, but `get_second_child` returns `Child`
|
||||||
|
50 |
|
||||||
|
51 | steve := sql db {
|
||||||
|
52 | select from Parent where child == get_second_child()
|
||||||
|
| ~~~~~~~~~~~~~~~~~~
|
||||||
|
53 | }
|
||||||
|
54 |
|
56
vlib/v/checker/tests/orm_fn_call_with_wrong_return_type.vv
Normal file
56
vlib/v/checker/tests/orm_fn_call_with_wrong_return_type.vv
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
struct Parent {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
name string
|
||||||
|
child Child [fkey: 'parent_id']
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Child {
|
||||||
|
mut:
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
parent_id int
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_first_child() Child {
|
||||||
|
return Child{
|
||||||
|
name: 'Lisa'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_second_child() Child {
|
||||||
|
return Child{
|
||||||
|
name: 'Steve'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mut db := sqlite.connect(':memory:') or { panic(err) }
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table Parent
|
||||||
|
create table Child
|
||||||
|
}
|
||||||
|
|
||||||
|
new_parent := Parent{
|
||||||
|
name: 'test'
|
||||||
|
child: get_second_child()
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert new_parent into Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
first_child := get_first_child()
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert first_child into Child
|
||||||
|
}
|
||||||
|
|
||||||
|
steve := sql db {
|
||||||
|
select from Parent where child == get_second_child()
|
||||||
|
}
|
||||||
|
|
||||||
|
dump(steve)
|
||||||
|
}
|
@ -299,6 +299,9 @@ fn (mut g Gen) sql_expr_to_orm_primitive(expr ast.Expr) {
|
|||||||
ast.SelectorExpr {
|
ast.SelectorExpr {
|
||||||
g.sql_write_orm_primitive(expr.typ, expr)
|
g.sql_write_orm_primitive(expr.typ, expr)
|
||||||
}
|
}
|
||||||
|
ast.CallExpr {
|
||||||
|
g.sql_write_orm_primitive(expr.return_type, expr)
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
eprintln(expr)
|
eprintln(expr)
|
||||||
verror('Unknown expr')
|
verror('Unknown expr')
|
||||||
@ -320,6 +323,7 @@ fn (mut g Gen) sql_write_orm_primitive(t ast.Type, expr ast.Expr) {
|
|||||||
if typ == 'orm__InfixType' {
|
if typ == 'orm__InfixType' {
|
||||||
typ = 'infix'
|
typ = 'infix'
|
||||||
}
|
}
|
||||||
|
|
||||||
g.write('orm__${typ}_to_primitive(')
|
g.write('orm__${typ}_to_primitive(')
|
||||||
if expr is ast.InfixExpr {
|
if expr is ast.InfixExpr {
|
||||||
g.write('(orm__InfixType){')
|
g.write('(orm__InfixType){')
|
||||||
@ -345,6 +349,8 @@ fn (mut g Gen) sql_write_orm_primitive(t ast.Type, expr ast.Expr) {
|
|||||||
g.write('.right = ')
|
g.write('.right = ')
|
||||||
g.sql_expr_to_orm_primitive(expr.right)
|
g.sql_expr_to_orm_primitive(expr.right)
|
||||||
g.write('}')
|
g.write('}')
|
||||||
|
} else if expr is ast.CallExpr {
|
||||||
|
g.call_expr(expr)
|
||||||
} else {
|
} else {
|
||||||
g.expr(expr)
|
g.expr(expr)
|
||||||
}
|
}
|
||||||
@ -422,6 +428,9 @@ fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut parenthese
|
|||||||
ast.BoolLiteral {
|
ast.BoolLiteral {
|
||||||
data << expr
|
data << expr
|
||||||
}
|
}
|
||||||
|
ast.CallExpr {
|
||||||
|
data << expr
|
||||||
|
}
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user