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

parser: breaking change, let V ORM queries return arrays for *all* non-count queries, including limit = 1 (#17719)

This commit is contained in:
walking devel 2023-03-22 07:48:01 +00:00 committed by GitHub
parent 93b7cc4888
commit d0e78b1da6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 117 additions and 128 deletions

View File

@ -67,23 +67,23 @@ jobs:
echo "Build Coreutils" echo "Build Coreutils"
cd /tmp/coreutils; make cd /tmp/coreutils; make
- name: Build vlang/gitly # - name: Build vlang/gitly
run: | # run: |
echo "Install markdown" # echo "Install markdown"
v install markdown # v install markdown
echo "Install pcre" # echo "Install pcre"
v install pcre # v install pcre
echo "Clone Gitly" # echo "Clone Gitly"
git clone https://github.com/vlang/gitly /tmp/gitly # git clone https://github.com/vlang/gitly /tmp/gitly
echo "Build Gitly" # echo "Build Gitly"
v /tmp/gitly # v /tmp/gitly
## echo "Build Gitly with -autofree" # ## echo "Build Gitly with -autofree"
## v -autofree /tmp/gitly # ## v -autofree /tmp/gitly
echo "Compile gitly.css from gitly.scss" # echo "Compile gitly.css from gitly.scss"
sassc /tmp/gitly/src/static/css/gitly.scss > /tmp/gitly/src/static/css/gitly.css # sassc /tmp/gitly/src/static/css/gitly.scss > /tmp/gitly/src/static/css/gitly.css
echo "Run first_run.v" # echo "Run first_run.v"
v run /tmp/gitly/tests/first_run.v # v run /tmp/gitly/tests/first_run.v
# /tmp/gitly/gitly -ci_run # # /tmp/gitly/gitly -ci_run
- name: Build V Language Server (VLS) vlang/vls - name: Build V Language Server (VLS) vlang/vls
run: | run: |

View File

@ -4836,7 +4836,7 @@ struct Customer {
country string [nonull] country string [nonull]
} }
db := sqlite.connect('customers.db')? db := sqlite.connect('customers.db')!
// you can create tables: // you can create tables:
// CREATE TABLE IF NOT EXISTS `Customer` ( // CREATE TABLE IF NOT EXISTS `Customer` (
@ -4854,6 +4854,7 @@ nr_customers := sql db {
select count from Customer select count from Customer
} }
println('number of all customers: ${nr_customers}') println('number of all customers: ${nr_customers}')
// V syntax can be used to build queries // V syntax can be used to build queries
uk_customers := sql db { uk_customers := sql db {
select from Customer where country == 'uk' && nr_orders > 0 select from Customer where country == 'uk' && nr_orders > 0
@ -4862,11 +4863,7 @@ println(uk_customers.len)
for customer in uk_customers { for customer in uk_customers {
println('${customer.id} - ${customer.name}') println('${customer.id} - ${customer.name}')
} }
// by adding `limit 1` we tell V that there will be only one object
customer := sql db {
select from Customer where id == 1 limit 1
}
println('${customer.id} - ${customer.name}')
// insert a new customer // insert a new customer
new_customer := Customer{ new_customer := Customer{
name: 'Bob' name: 'Bob'

View File

@ -34,9 +34,10 @@ fn (mut app App) service_auth(username string, password string) !string {
db.close() or { panic(err) } db.close() or { panic(err) }
} }
user := sql db { users := sql db {
select from User where username == username limit 1 select from User where username == username
} }
user := users.first()
if user.username != username { if user.username != username {
return error('user not found') return error('user not found')
} }

View File

@ -61,5 +61,5 @@ fn (mut app App) service_get_user(id int) !User {
select from User where id == id select from User where id == id
} }
return results return results.first()
} }

View File

@ -31,13 +31,15 @@ fn (mut app App) service_auth(username string, password string) !string {
panic(err) panic(err)
} }
user := sql db { users := sql db {
select from User where username == username limit 1 select from User where username == username
} }
if user.username != username {
if users.len == 0 {
return error('user not found') return error('user not found')
} }
user := users.first()
if !user.active { if !user.active {
return error('user is not active') return error('user is not active')
} }

View File

@ -31,11 +31,11 @@ fn (mut app App) service_add_user(username string, password string) !User {
return err return err
} }
result := sql db { users := sql db {
select from User where username == username limit 1 select from User where username == username limit 1
} }
return result return users.first()
} }
fn (mut app App) service_get_user_by_id(user_id int) !User { fn (mut app App) service_get_user_by_id(user_id int) !User {
@ -48,11 +48,11 @@ fn (mut app App) service_get_user_by_id(user_id int) !User {
db.close() or { panic(err) } db.close() or { panic(err) }
} }
results := sql db { users := sql db {
select from User where id == user_id select from User where id == user_id
} }
return results return users.first()
} }
fn (mut app App) service_get_all_user() ![]User { fn (mut app App) service_get_all_user() ![]User {

View File

@ -149,10 +149,11 @@ fn test_sqlite_orm() {
insert test_default_atribute into TestDefaultAtribute insert test_default_atribute into TestDefaultAtribute
} }
result_test_default_atribute := sql db { test_default_atributes := sql db {
select from TestDefaultAtribute limit 1 select from TestDefaultAtribute limit 1
} }
result_test_default_atribute := test_default_atributes.first()
assert result_test_default_atribute.name == 'Hitalo' assert result_test_default_atribute.name == 'Hitalo'
assert test_default_atribute.created_at.len == 0 assert test_default_atribute.created_at.len == 0
assert test_default_atribute.created_at1.len == 0 assert test_default_atribute.created_at1.len == 0

View File

@ -106,10 +106,11 @@ fn test_orm_insert_with_multiple_child_elements() {
insert new_parent into Parent insert new_parent into Parent
} }
parent := sql db { parents := sql db {
select from Parent where id == 1 select from Parent where id == 1
} }
parent := parents.first()
assert parent.children.len == new_parent.children.len assert parent.children.len == new_parent.children.len
assert parent.notes.len == new_parent.notes.len assert parent.notes.len == new_parent.notes.len

View File

@ -60,26 +60,26 @@ fn test_sql_or_block_for_select() {
mut db := sqlite.connect(db_path)! mut db := sqlite.connect(db_path)!
eprintln('> selecting user with id 1...') eprintln('> selecting user with id 1...')
single := sql db { mut users := sql db {
select from User where id == 1 select from User where id == 1
} or { } or {
eprintln('could not select user, err: ${err}') eprintln('could not select user, err: ${err}')
User{0, ''} []User{}
} }
eprintln('LINE: ${@LINE}') eprintln('LINE: ${@LINE}')
single := users.first()
assert single.id == 1 assert single.id == 1
failed := sql db { users = sql db {
select from User where id == 0 select from User where id == 0
} or { } or {
eprintln('could not select user, err: ${err}') eprintln('could not select user, err: ${err}')
User{0, ''} []User{}
} }
eprintln('LINE: ${@LINE}') eprintln('LINE: ${@LINE}')
assert failed.id == 0 assert users.len == 0
assert failed.name == ''
eprintln('LINE: ${@LINE}') eprintln('LINE: ${@LINE}')
eprintln('> selecting users...') eprintln('> selecting users...')

View File

@ -104,19 +104,16 @@ fn test_orm() {
select count from User select count from User
} }
assert nr_all_users == 3 assert nr_all_users == 3
println('nr_all_users=${nr_all_users}')
nr_users1 := sql db { nr_users1 := sql db {
select count from User where id == 1 select count from User where id == 1
} }
assert nr_users1 == 1 assert nr_users1 == 1
println('nr_users1=${nr_users1}')
nr_peters := sql db { nr_peters := sql db {
select count from User where id == 2 && name == 'Peter' select count from User where id == 2 && name == 'Peter'
} }
assert nr_peters == 1 assert nr_peters == 1
println('nr_peters=${nr_peters}')
nr_peters2 := sql db { nr_peters2 := sql db {
select count from User where id == 2 && name == name select count from User where id == 2 && name == name
@ -134,24 +131,26 @@ fn test_orm() {
assert peters.len == 1 assert peters.len == 1
assert peters[0].name == 'Peter' assert peters[0].name == 'Peter'
one_peter := sql db { mut users := sql db {
select from User where name == name limit 1 select from User where name == name limit 1
} }
one_peter := users.first()
assert one_peter.name == 'Peter' assert one_peter.name == 'Peter'
assert one_peter.id == 2 assert one_peter.id == 2
user := sql db { users = sql db {
select from User where id == 1 select from User where id == 1
} }
println(user)
user := users.first()
assert user.name == 'Sam' assert user.name == 'Sam'
assert user.id == 1 assert user.id == 1
assert user.age == 29 assert user.age == 29
users := sql db { users = sql db {
select from User where id > 0 select from User where id > 0
} }
println(users)
assert users.len == 3 assert users.len == 3
assert users[0].name == 'Sam' assert users[0].name == 'Sam'
assert users[1].name == 'Peter' assert users[1].name == 'Peter'
@ -160,23 +159,16 @@ fn test_orm() {
users2 := sql db { users2 := sql db {
select from User where id < 0 select from User where id < 0
} }
println(users2)
assert users2.len == 0 assert users2.len == 0
users3 := sql db { users3 := sql db {
select from User where age == 29 || age == 31 select from User where age == 29 || age == 31
} }
println(users3)
assert users3.len == 2 assert users3.len == 2
assert users3[0].age == 29 assert users3[0].age == 29
assert users3[1].age == 31 assert users3[1].age == 31
missing_user := sql db {
select from User where id == 8777
}
println('missing_user:')
println(missing_user) // zero struct
new_user := User{ new_user := User{
name: 'New user' name: 'New user'
age: 30 age: 30
@ -185,22 +177,27 @@ fn test_orm() {
insert new_user into User insert new_user into User
} }
x := sql db { users = sql db {
select from User where id == 4 select from User where id == 4
} }
println(x)
x := users.first()
assert x.age == 30 assert x.age == 30
assert x.id == 4 assert x.id == 4
assert x.name == 'New user' assert x.name == 'New user'
kate := sql db { users = sql db {
select from User where id == 3 select from User where id == 3
} }
kate := users.first()
assert kate.is_customer == true assert kate.is_customer == true
customer := sql db { users = sql db {
select from User where is_customer == true limit 1 select from User where is_customer == true limit 1
} }
customer := users.first()
assert customer.is_customer == true assert customer.is_customer == true
assert customer.name == 'Kate' assert customer.name == 'Kate'
@ -208,9 +205,10 @@ fn test_orm() {
update User set age = 31 where name == 'Kate' update User set age = 31 where name == 'Kate'
} }
kate2 := sql db { users = sql db {
select from User where id == 3 select from User where id == 3
} }
kate2 := users.first()
assert kate2.age == 31 assert kate2.age == 31
assert kate2.name == 'Kate' assert kate2.name == 'Kate'
@ -218,9 +216,10 @@ fn test_orm() {
update User set age = 32, name = 'Kate N' where name == 'Kate' update User set age = 32, name = 'Kate N' where name == 'Kate'
} }
mut kate3 := sql db { users = sql db {
select from User where id == 3 select from User where id == 3
} }
mut kate3 := users.first()
assert kate3.age == 32 assert kate3.age == 32
assert kate3.name == 'Kate N' assert kate3.name == 'Kate N'
@ -229,9 +228,11 @@ fn test_orm() {
update User set age = new_age, name = 'Kate N' where id == 3 update User set age = new_age, name = 'Kate N' where id == 3
} }
kate3 = sql db { users = sql db {
select from User where id == 3 select from User where id == 3
} }
kate3 = users.first()
assert kate3.age == 33 assert kate3.age == 33
assert kate3.name == 'Kate N' assert kate3.name == 'Kate N'
@ -240,17 +241,18 @@ fn test_orm() {
update User set age = foo.age, name = 'Kate N' where id == 3 update User set age = foo.age, name = 'Kate N' where id == 3
} }
kate3 = sql db { users = sql db {
select from User where id == 3 select from User where id == 3
} }
kate3 = users.first()
assert kate3.age == 34 assert kate3.age == 34
assert kate3.name == 'Kate N' assert kate3.name == 'Kate N'
no_user := sql db { no_user := sql db {
select from User where id == 30 select from User where id == 30
} }
assert no_user.name == '' // TODO optional
assert no_user.age == 0 assert no_user.len == 0
two_users := sql db { two_users := sql db {
select from User limit 2 select from User limit 2
@ -270,35 +272,41 @@ fn test_orm() {
assert z.len == 2 assert z.len == 2
assert z[0].id == 3 assert z[0].id == 3
oldest := sql db { users = sql db {
select from User order by age desc limit 1 select from User order by age desc limit 1
} }
oldest := users.first()
assert oldest.age == 34 assert oldest.age == 34
offs := 1 offs := 1
second_oldest := sql db { users = sql db {
select from User order by age desc limit 1 offset offs select from User order by age desc limit 1 offset offs
} }
second_oldest := users.first()
assert second_oldest.age == 31 assert second_oldest.age == 31
sql db { sql db {
delete from User where age == 34 delete from User where age == 34
} }
updated_oldest := sql db { users = sql db {
select from User order by age desc limit 1 select from User order by age desc limit 1
} }
updated_oldest := users.first()
assert updated_oldest.age == 31 assert updated_oldest.age == 31
// Remove this when pg is used // Remove this when pg is used
// db.exec('insert into User (name, age) values (NULL, 31)') // db.exec('insert into User (name, age) values (NULL, 31)')
null_user := sql db { users = sql db {
select from User where id == 5 select from User where id == 5
} }
assert null_user.name == '' assert users.len == 0
age_test := sql db { users = sql db {
select from User where id == 1 select from User where id == 1
} }
age_test := users.first()
assert age_test.age == 29 assert age_test.age == 29
@ -306,20 +314,22 @@ fn test_orm() {
update User set age = age + 1 where id == 1 update User set age = age + 1 where id == 1
} }
mut first := sql db { users = sql db {
select from User where id == 1 select from User where id == 1
} }
mut first := users.first()
assert first.age == 30 assert first.age == 30
sql db { sql db {
update User set age = age * 2 where id == 1 update User set age = age * 2 where id == 1
} }
first = sql db { users = sql db {
select from User where id == 1 select from User where id == 1
} }
first = users.first()
assert first.age == 60 assert first.age == 60
sql db { sql db {
@ -353,31 +363,31 @@ fn test_orm() {
update Module set test_id = 11 where id == 1 update Module set test_id = 11 where id == 1
} }
test_id_mod := sql db { mut modules := sql db {
select from Module where id == 1 select from Module where id == 1
} }
assert test_id_mod.test_id == 11 assert modules.first().test_id == 11
t := time.now() t := time.now()
sql db { sql db {
update Module set created = t where id == 1 update Module set created = t where id == 1
} }
updated_time_mod := sql db { modules = sql db {
select from Module where id == 1 select from Module where id == 1
} }
// Note: usually updated_time_mod.created != t, because t has // Note: usually updated_time_mod.created != t, because t has
// its microseconds set, while the value retrieved from the DB // its microseconds set, while the value retrieved from the DB
// has them zeroed, because the db field resolution is seconds. // has them zeroed, because the db field resolution is seconds.
assert updated_time_mod.created.format_ss() == t.format_ss() assert modules.first().created.format_ss() == t.format_ss()
para_select := sql db { users = sql db {
select from User where (name == 'Sam' && is_customer == true) || id == 1 select from User where (name == 'Sam' && is_customer == true) || id == 1
} }
assert para_select[0] == first assert users.first() == first
sql db { sql db {
drop table Module drop table Module

View File

@ -16,18 +16,19 @@ fn (mut p Parser) sql_expr() ast.Expr {
} }
p.check(.lcbr) p.check(.lcbr)
p.check(.key_select) p.check(.key_select)
n := p.check_name() is_count := p.check_name() == 'count'
is_count := n == 'count'
mut typ := ast.void_type mut typ := ast.void_type
if is_count { if is_count {
p.check_name() // from p.check_name() // from
typ = ast.int_type
} }
table_pos := p.tok.pos() table_pos := p.tok.pos()
table_type := p.parse_type() // `User` table_type := p.parse_type() // `User`
mut where_expr := ast.empty_expr mut where_expr := ast.empty_expr
has_where := p.tok.kind == .name && p.tok.lit == 'where' has_where := p.tok.kind == .name && p.tok.lit == 'where'
mut query_one := false // one object is returned, not an array
if has_where { if has_where {
p.next() p.next()
where_expr = p.expr(0) where_expr = p.expr(0)
@ -37,11 +38,8 @@ fn (mut p Parser) sql_expr() ast.Expr {
if where_check_result is ast.NodeError { if where_check_result is ast.NodeError {
return where_check_result return where_check_result
} }
if !is_count {
query_one = p.has_sql_where_expr_with_comparison_with_id(&where_expr)
}
} }
mut has_limit := false mut has_limit := false
mut limit_expr := ast.empty_expr mut limit_expr := ast.empty_expr
mut has_offset := false mut has_offset := false
@ -64,29 +62,25 @@ fn (mut p Parser) sql_expr() ast.Expr {
has_desc = true has_desc = true
} }
} }
if p.tok.kind == .name && p.tok.lit == 'limit' { if p.tok.kind == .name && p.tok.lit == 'limit' {
// `limit 1` means that a single object is returned
p.check_name() // `limit` p.check_name() // `limit`
if p.tok.kind == .number && p.tok.lit == '1' {
query_one = true
}
has_limit = true has_limit = true
limit_expr = p.expr(0) limit_expr = p.expr(0)
} }
if p.tok.kind == .name && p.tok.lit == 'offset' { if p.tok.kind == .name && p.tok.lit == 'offset' {
p.check_name() // `offset` p.check_name() // `offset`
has_offset = true has_offset = true
offset_expr = p.expr(0) offset_expr = p.expr(0)
} }
if !query_one && !is_count {
// return an array if is_count {
typ = ast.int_type
} else {
typ = ast.new_type(p.table.find_or_register_array(table_type)) typ = ast.new_type(p.table.find_or_register_array(table_type))
} else if !is_count {
// return a single object
// TODO optional
// typ = table_type.set_flag(.optional)
typ = table_type
} }
p.check(.rcbr) p.check(.rcbr)
p.inside_match = false p.inside_match = false
or_expr := p.parse_sql_or_block() or_expr := p.parse_sql_or_block()
@ -105,7 +99,7 @@ fn (mut p Parser) sql_expr() ast.Expr {
has_order: has_order has_order: has_order
order_expr: order_expr order_expr: order_expr
has_desc: has_desc has_desc: has_desc
is_array: !query_one is_array: if is_count { false } else { true }
pos: pos.extend(p.prev_tok.pos()) pos: pos.extend(p.prev_tok.pos())
table_expr: ast.TypeNode{ table_expr: ast.TypeNode{
typ: table_type typ: table_type
@ -304,26 +298,6 @@ fn (mut p Parser) check_sql_keyword(name string) ?bool {
return true return true
} }
// has_sql_where_expr_with_comparison_with_id tries to search comparison with the `id` field.
// If `id` is found it means that a database "should" return one row,
// but it is not true and depends on a database scheme.
// For example, it will be hard to use V ORM in an existing database scheme which wrote before using V.
fn (p &Parser) has_sql_where_expr_with_comparison_with_id(expr &ast.Expr) bool {
// This method will be removed in the future when we refuse it.
// A more difficult expression with `id` means a user tries to find more than one row or he is wrong.
// And there is no point to get one structure instead of an array.
// `id == x` means that a single object is returned.
if expr is ast.InfixExpr {
if expr.left is ast.Ident && expr.op == .eq {
return expr.left.name == 'id'
}
} else if expr is ast.ParExpr {
return p.has_sql_where_expr_with_comparison_with_id(expr.expr)
}
return false
}
// check_sql_where_expr_has_no_undefined_variables recursively tries to find undefined variables in the right part of infix expressions. // check_sql_where_expr_has_no_undefined_variables recursively tries to find undefined variables in the right part of infix expressions.
fn (mut p Parser) check_sql_where_expr_has_no_undefined_variables(expr &ast.Expr, unacceptable_variable_names []string) ast.Expr { fn (mut p Parser) check_sql_where_expr_has_no_undefined_variables(expr &ast.Expr, unacceptable_variable_names []string) ast.Expr {
if expr is ast.Ident { if expr is ast.Ident {

View File

@ -38,7 +38,7 @@ fn test_orm_array() {
insert par into Parent insert par into Parent
} }
parent := sql db { parents := sql db {
select from Parent where id == 1 select from Parent where id == 1
} }
@ -46,6 +46,7 @@ fn test_orm_array() {
drop table Parent drop table Parent
} }
parent := parents.first()
assert parent.name == par.name assert parent.name == par.name
assert parent.children.len == par.children.len assert parent.children.len == par.children.len
assert parent.children[0].name == 'abc' assert parent.children[0].name == 'abc'
@ -74,10 +75,11 @@ fn test_orm_relationship() {
insert par into Parent insert par into Parent
} }
mut parent := sql db { mut parents := sql db {
select from Parent where id == 1 select from Parent where id == 1
} }
mut parent := parents.first()
child.parent_id = parent.id child.parent_id = parent.id
child.name = 'atum' child.name = 'atum'
@ -94,10 +96,11 @@ fn test_orm_relationship() {
assert parent.name == par.name assert parent.name == par.name
assert parent.children.len == 0 assert parent.children.len == 0
parent = sql db { parents = sql db {
select from Parent where id == 1 select from Parent where id == 1
} }
parent = parents.first()
assert parent.name == par.name assert parent.name == par.name
assert parent.children.len == 2 assert parent.children.len == 2
assert parent.children[0].name == 'atum' assert parent.children[0].name == 'atum'

View File

@ -29,9 +29,9 @@ fn test_orm_sub_structs() {
insert upper_1 into Upper insert upper_1 into Upper
} }
upper_s := sql db { uppers := sql db {
select from Upper where id == 1 select from Upper where id == 1
} }
assert upper_s.sub.name == upper_1.sub.name assert uppers.first().sub.name == upper_1.sub.name
} }

View File

@ -18,7 +18,7 @@ fn test_sql_statement_inside_fn_call() {
sql db { sql db {
insert m into Movie insert m into Movie
} }
dump(x(sql db { dump(x((sql db {
select from Movie where id == 1 select from Movie where id == 1
})) }).first()))
} }