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

orm: support parenthesized expressions as in select from User where (name == 'Sam' && is_customer == true) || id == 1 (#15693)

This commit is contained in:
Louis Schmieder 2022-09-09 20:08:48 +02:00 committed by GitHub
parent 61a4b469a3
commit f249feb9da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 18 deletions

View File

@ -107,13 +107,15 @@ fn (kind OrderType) to_str() string {
// => fields[abc, b]; data[3, 'test']; types[index of int, index of string]; kinds[.eq, .eq]; is_and[true];
// Every field, data, type & kind of operation in the expr share the same index in the arrays
// is_and defines how they're addicted to each other either and or or
// parentheses defines which fields will be inside ()
pub struct QueryData {
pub:
fields []string
data []Primitive
types []int
kinds []OperationKind
is_and []bool
fields []string
data []Primitive
types []int
parentheses [][]int
kinds []OperationKind
is_and []bool
}
pub struct InfixType {
@ -266,11 +268,25 @@ pub fn orm_stmt_gen(table string, q string, kind StmtKind, num bool, qm string,
}
if kind == .update || kind == .delete {
for i, field in where.fields {
mut pre_par := false
mut post_par := false
for par in where.parentheses {
if i in par {
pre_par = par[0] == i
post_par = par[1] == i
}
}
if pre_par {
str += '('
}
str += '$q$field$q ${where.kinds[i].to_str()} $qm'
if num {
str += '$c'
c++
}
if post_par {
str += ')'
}
if i < where.fields.len - 1 {
str += ' AND '
}
@ -311,11 +327,25 @@ pub fn orm_select_gen(orm SelectConfig, q string, num bool, qm string, start_pos
if orm.has_where {
str += ' WHERE '
for i, field in where.fields {
mut pre_par := false
mut post_par := false
for par in where.parentheses {
if i in par {
pre_par = par[0] == i
post_par = par[1] == i
}
}
if pre_par {
str += '('
}
str += '$q$field$q ${where.kinds[i].to_str()} $qm'
if num {
str += '$c'
c++
}
if post_par {
str += ')'
}
if i < where.fields.len - 1 {
if where.is_and[i] {
str += ' AND '

View File

@ -9,7 +9,7 @@ struct User {
const db_path = os.join_path(os.temp_dir(), 'sql_statement_or_blocks.db')
fn test_ensure_db_exists_and_user_table_is_ok() ? {
db := sqlite.connect(db_path)?
mut db := sqlite.connect(db_path)?
assert true
eprintln('> drop pre-existing User table...')
@ -20,10 +20,11 @@ fn test_ensure_db_exists_and_user_table_is_ok() ? {
create table User
} or { panic(err) }
assert true
db.close()?
}
fn test_sql_or_block_for_insert() ? {
db := sqlite.connect(db_path)?
mut db := sqlite.connect(db_path)?
user := User{1, 'bilbo'}
eprintln('> inserting user 1 (first try)...')
@ -41,10 +42,12 @@ fn test_sql_or_block_for_insert() ? {
assert true
println('user could not be inserted, err: $err')
}
eprintln('LINE: ${@LINE}')
db.close()?
}
fn test_sql_or_block_for_select() ? {
db := sqlite.connect(db_path)?
mut db := sqlite.connect(db_path)?
eprintln('> selecting user with id 1...')
single := sql db {
@ -53,6 +56,7 @@ fn test_sql_or_block_for_select() ? {
eprintln('could not select user, err: $err')
User{0, ''}
}
eprintln('LINE: ${@LINE}')
assert single.id == 1
@ -62,9 +66,11 @@ fn test_sql_or_block_for_select() ? {
eprintln('could not select user, err: $err')
User{0, ''}
}
eprintln('LINE: ${@LINE}')
assert failed.id == 0
assert failed.name == ''
eprintln('LINE: ${@LINE}')
eprintln('> selecting users...')
multiple := sql db {
@ -73,8 +79,11 @@ fn test_sql_or_block_for_select() ? {
eprintln('could not users, err: $err')
[]User{}
}
eprintln('LINE: ${@LINE}')
assert multiple.len == 1
eprintln('LINE: ${@LINE}')
db.close()?
}
fn test_finish() ? {

View File

@ -360,6 +360,12 @@ fn test_orm() {
// has them zeroed, because the db field resolution is seconds.
assert updated_time_mod.created.format_ss() == t.format_ss()
para_select := sql db {
select from User where (name == 'Sam' && is_customer == true) || id == 1
}
assert para_select[0] == first
sql db {
drop table Module
drop table TestTime

View File

@ -8,14 +8,16 @@ import time
pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive {
// 1. Create query and bind necessary data
query := orm.orm_select_gen(config, '`', true, '?', 1, where)
$if trace_sqlite ? {
eprintln('> @select query: "$query"')
}
stmt := db.new_init_stmt(query)?
mut c := 1
sqlite_stmt_binder(stmt, where, query, mut c)?
sqlite_stmt_binder(stmt, data, query, mut c)?
defer {
stmt.finalize()
}
mut c := 1
sqlite_stmt_binder(stmt, where, query, mut c)?
sqlite_stmt_binder(stmt, data, query, mut c)?
mut ret := [][]orm.Primitive{}
@ -89,12 +91,17 @@ pub fn (db DB) drop(table string) ? {
// Executes query and bind prepared statement data directly
fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ? {
$if trace_sqlite ? {
eprintln('> sqlite_stmt_worker query: "$query"')
}
stmt := db.new_init_stmt(query)?
defer {
stmt.finalize()
}
mut c := 1
sqlite_stmt_binder(stmt, data, query, mut c)?
sqlite_stmt_binder(stmt, where, query, mut c)?
stmt.orm_step(query)?
stmt.finalize()
}
// Binds all values of d in the prepared statement

View File

@ -277,6 +277,7 @@ fn (mut g Gen) sql_update(node ast.SqlStmtLine, expr string, table_name string)
g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
g.write('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_int), 0),')
if node.updated_columns.len > 0 {
g.write('.fields = new_array_from_c_array($node.updated_columns.len, $node.updated_columns.len, sizeof(string),')
g.write(' _MOV((string[$node.updated_columns.len]){')
@ -380,11 +381,12 @@ fn (mut g Gen) sql_write_orm_primitive(t ast.Type, expr ast.Expr) {
g.write('),')
}
fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut kinds []string, mut data []ast.Expr, mut is_and []bool) {
fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut parentheses [][]int, mut kinds []string, mut data []ast.Expr, mut is_and []bool) {
match expr {
ast.InfixExpr {
g.sql_side = .left
g.sql_where_data(expr.left, mut fields, mut kinds, mut data, mut is_and)
g.sql_where_data(expr.left, mut fields, mut parentheses, mut kinds, mut data, mut
is_and)
mut kind := match expr.op {
.ne {
'orm__OperationKind__neq'
@ -417,11 +419,19 @@ fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut kinds []st
kind = 'orm__OperationKind__eq'
}
}
if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr {
if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr && kind != '' {
kinds << kind
}
g.sql_side = .right
g.sql_where_data(expr.right, mut fields, mut kinds, mut data, mut is_and)
g.sql_where_data(expr.right, mut fields, mut parentheses, mut kinds, mut data, mut
is_and)
}
ast.ParExpr {
mut par := [fields.len]
g.sql_where_data(expr.expr, mut fields, mut parentheses, mut kinds, mut data, mut
is_and)
par << fields.len - 1
parentheses << par
}
ast.Ident {
if g.sql_side == .left {
@ -450,9 +460,11 @@ fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) {
g.write('(orm__QueryData){')
mut fields := []string{}
mut kinds := []string{}
mut parentheses := [][]int{}
mut data := []ast.Expr{}
mut is_and := []bool{}
g.sql_where_data(where_expr, mut fields, mut kinds, mut data, mut is_and)
g.sql_where_data(where_expr, mut fields, mut parentheses, mut kinds, mut data, mut
is_and)
g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
if fields.len > 0 {
g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),')
@ -476,6 +488,26 @@ fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) {
}
g.write('),')
g.write('.parentheses = ')
if parentheses.len > 0 {
g.write('new_array_from_c_array($parentheses.len, $parentheses.len, sizeof(Array_int), _MOV((Array_int[$parentheses.len]){')
for par in parentheses {
if par.len > 0 {
g.write('new_array_from_c_array($par.len, $par.len, sizeof(int), _MOV((int[$par.len]){')
for val in par {
g.write('$val,')
}
g.write('})),')
} else {
g.write('__new_array_with_default_noscan(0, 0, sizeof(int), 0),')
}
}
g.write('}))')
} else {
g.write('__new_array_with_default_noscan(0, 0, sizeof(Array_int), 0)')
}
g.write(',')
if kinds.len > 0 {
g.write('.kinds = new_array_from_c_array($kinds.len, $kinds.len, sizeof(orm__OperationKind),')
g.write(' _MOV((orm__OperationKind[$kinds.len]){')
@ -618,6 +650,7 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string, or_expr as
g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
g.write('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_int), 0),')
if exprs.len > 0 {
g.write('.data = new_array_from_c_array($exprs.len, $exprs.len, sizeof(orm__Primitive),')
g.write(' _MOV((orm__Primitive[$exprs.len]){')
@ -637,6 +670,7 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string, or_expr as
g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
g.write('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_int), 0),')
g.write('.data = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0)')
g.write('}')
}