diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index 8affb83040..dc2bd7e8d5 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -248,6 +248,7 @@ const skip_on_ubuntu_musl = [ 'vlib/orm/orm_serial_attribute_test.v', 'vlib/orm/orm_option_subselect_test.v', 'vlib/orm/orm_func_test.v', + 'vlib/orm/orm_where_in_test.v', 'vlib/v/tests/orm_enum_test.v', 'vlib/v/tests/orm_sub_struct_test.v', 'vlib/v/tests/orm_sub_array_struct_test.v', diff --git a/vlib/db/mysql/orm.c.v b/vlib/db/mysql/orm.c.v index 2988644b9a..113159c82b 100644 --- a/vlib/db/mysql/orm.c.v +++ b/vlib/db/mysql/orm.c.v @@ -247,6 +247,11 @@ fn stmt_bind_primitive(mut stmt Stmt, data orm.Primitive) { orm.Null { stmt.bind_null() } + []orm.Primitive { + for element in data { + stmt_bind_primitive(mut stmt, element) + } + } } } diff --git a/vlib/db/pg/orm.v b/vlib/db/pg/orm.v index f81d6bf59c..b80e9f82ff 100644 --- a/vlib/db/pg/orm.v +++ b/vlib/db/pg/orm.v @@ -180,6 +180,11 @@ fn pg_stmt_match(mut types []u32, mut vals []&char, mut lens []int, mut formats lens << int(0) // ignored formats << 0 // ignored } + []orm.Primitive { + for element in data { + pg_stmt_match(mut types, mut vals, mut lens, mut formats, element) + } + } } } diff --git a/vlib/db/sqlite/orm.v b/vlib/db/sqlite/orm.v index a6b09d3e1e..f375893453 100644 --- a/vlib/db/sqlite/orm.v +++ b/vlib/db/sqlite/orm.v @@ -114,7 +114,7 @@ fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryDa // Binds all values of d in the prepared statement fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ! { for data in d.data { - err := bind(stmt, c, data) + err := bind(stmt, mut c, data) if err != 0 { return stmt.db.error_message(err, query) @@ -124,7 +124,7 @@ fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ! { } // Universal bind function -fn bind(stmt Stmt, c &int, data orm.Primitive) int { +fn bind(stmt Stmt, mut c &int, data orm.Primitive) int { mut err := 0 match data { i8, i16, int, u8, u16, u32, bool { @@ -143,11 +143,21 @@ fn bind(stmt Stmt, c &int, data orm.Primitive) int { err = stmt.bind_int(c, int(data.unix())) } orm.InfixType { - err = bind(stmt, c, data.right) + err = bind(stmt, mut c, data.right) } orm.Null { err = stmt.bind_null(c) } + []orm.Primitive { + for element in data { + tmp_err := bind(stmt, mut c, element) + c++ + if tmp_err != 0 { + err = tmp_err + break + } + } + } } return err } diff --git a/vlib/orm/orm.v b/vlib/orm/orm.v index 679f9fd8fb..14face0ddf 100644 --- a/vlib/orm/orm.v +++ b/vlib/orm/orm.v @@ -52,6 +52,7 @@ pub type Primitive = InfixType | u32 | u64 | u8 + | []Primitive pub struct Null {} @@ -66,6 +67,8 @@ pub enum OperationKind { orm_ilike // ILIKE is_null // IS NULL is_not_null // IS NOT NULL + in // IN + not_in // NOT IN } pub enum MathOperationKind { @@ -105,6 +108,8 @@ fn (kind OperationKind) to_str() string { .orm_ilike { 'ILIKE' } .is_null { 'IS NULL' } .is_not_null { 'IS NOT NULL' } + .in { 'IN' } + .not_in { 'NOT IN' } } return str } @@ -402,10 +407,23 @@ fn gen_where_clause(where QueryData, q string, qm string, num bool, mut c &int) } str += '${q}${field}${q} ${where.kinds[i].to_str()}' if !where.kinds[i].is_unary() { - str += ' ${qm}' - if num { - str += '${c}' - c++ + if where.data.len > i && where.data[i] is []Primitive { + len := (where.data[i] as []Primitive).len + mut tmp := []string{len: len} + for j in 0 .. len { + tmp[j] = '${qm}' + if num { + tmp[j] += '${c}' + c++ + } + } + str += ' (${tmp.join(', ')})' + } else { + str += ' ${qm}' + if num { + str += '${c}' + c++ + } } } if current_post_par > 0 { @@ -629,6 +647,10 @@ fn option_bool_to_primitive(b ?bool) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_bool_to_primitive(b []bool) Primitive { + return Primitive(b.map(bool_to_primitive(it))) +} + fn f32_to_primitive(b f32) Primitive { return Primitive(b) } @@ -637,6 +659,10 @@ fn option_f32_to_primitive(b ?f32) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_f32_to_primitive(b []f32) Primitive { + return Primitive(b.map(f32_to_primitive(it))) +} + fn f64_to_primitive(b f64) Primitive { return Primitive(b) } @@ -645,6 +671,10 @@ fn option_f64_to_primitive(b ?f64) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_f64_to_primitive(b []f64) Primitive { + return Primitive(b.map(f64_to_primitive(it))) +} + fn i8_to_primitive(b i8) Primitive { return Primitive(b) } @@ -653,6 +683,10 @@ fn option_i8_to_primitive(b ?i8) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_i8_to_primitive(b []i8) Primitive { + return Primitive(b.map(i8_to_primitive(it))) +} + fn i16_to_primitive(b i16) Primitive { return Primitive(b) } @@ -661,6 +695,10 @@ fn option_i16_to_primitive(b ?i16) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_i16_to_primitive(b []i16) Primitive { + return Primitive(b.map(i16_to_primitive(it))) +} + fn int_to_primitive(b int) Primitive { return Primitive(b) } @@ -669,6 +707,10 @@ fn option_int_to_primitive(b ?int) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_int_to_primitive(b []int) Primitive { + return Primitive(b.map(int_to_primitive(it))) +} + // int_literal_to_primitive handles int literal value fn int_literal_to_primitive(b int) Primitive { return Primitive(b) @@ -678,6 +720,10 @@ fn option_int_literal_to_primitive(b ?int) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_int_literal_to_primitive(b []int) Primitive { + return Primitive(b.map(int_literal_to_primitive(it))) +} + // float_literal_to_primitive handles float literal value fn float_literal_to_primitive(b f64) Primitive { return Primitive(b) @@ -687,6 +733,10 @@ fn option_float_literal_to_primitive(b ?f64) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_float_literal_to_primitive(b []f64) Primitive { + return Primitive(b.map(float_literal_to_primitive(it))) +} + fn i64_to_primitive(b i64) Primitive { return Primitive(b) } @@ -695,6 +745,10 @@ fn option_i64_to_primitive(b ?i64) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_i64_to_primitive(b []i64) Primitive { + return Primitive(b.map(i64_to_primitive(it))) +} + fn u8_to_primitive(b u8) Primitive { return Primitive(b) } @@ -703,6 +757,10 @@ fn option_u8_to_primitive(b ?u8) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_u8_to_primitive(b []u8) Primitive { + return Primitive(b.map(u8_to_primitive(it))) +} + fn u16_to_primitive(b u16) Primitive { return Primitive(b) } @@ -711,6 +769,10 @@ fn option_u16_to_primitive(b ?u16) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_u16_to_primitive(b []u16) Primitive { + return Primitive(b.map(u16_to_primitive(it))) +} + fn u32_to_primitive(b u32) Primitive { return Primitive(b) } @@ -719,6 +781,10 @@ fn option_u32_to_primitive(b ?u32) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_u32_to_primitive(b []u32) Primitive { + return Primitive(b.map(u32_to_primitive(it))) +} + fn u64_to_primitive(b u64) Primitive { return Primitive(b) } @@ -727,6 +793,10 @@ fn option_u64_to_primitive(b ?u64) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_u64_to_primitive(b []u64) Primitive { + return Primitive(b.map(u64_to_primitive(it))) +} + fn string_to_primitive(b string) Primitive { return Primitive(b) } @@ -735,6 +805,10 @@ fn option_string_to_primitive(b ?string) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_string_to_primitive(b []string) Primitive { + return Primitive(b.map(string_to_primitive(it))) +} + fn time_to_primitive(b time.Time) Primitive { return Primitive(b) } @@ -743,6 +817,10 @@ fn option_time_to_primitive(b ?time.Time) Primitive { return if b_ := b { Primitive(b_) } else { null_primitive } } +fn array_time_to_primitive(b []time.Time) Primitive { + return Primitive(b.map(time_to_primitive(it))) +} + fn infix_to_primitive(b InfixType) Primitive { return Primitive(b) } diff --git a/vlib/orm/orm_where_in_test.v b/vlib/orm/orm_where_in_test.v new file mode 100644 index 0000000000..6f3a8614a7 --- /dev/null +++ b/vlib/orm/orm_where_in_test.v @@ -0,0 +1,51 @@ +// vtest flaky: true +// vtest retry: 3 +import db.sqlite + +struct User { + id int @[primary; sql: serial] + name string +} + +fn get_users_in(mut db sqlite.DB, names []string) ![]User { + return sql db { + select from User where name in names + }! +} + +fn get_users_not_in(mut db sqlite.DB, names []string) ![]User { + return sql db { + select from User where name !in names + }! +} + +fn test_orm_mut_db() { + mut db := sqlite.connect(':memory:') or { panic(err) } + + sql db { + create table User + }! + + first_user := User{ + name: 'first' + } + second_user := User{ + name: 'second' + } + + sql db { + insert first_user into User + insert second_user into User + }! + + in_users := get_users_in(mut db, ['first'])! + + assert in_users.len == 1 + + not_in_users := get_users_not_in(mut db, ['second'])! + + assert not_in_users.len == 1 + + all_users := get_users_in(mut db, ['first', 'second'])! + assert all_users.len == 2 +} diff --git a/vlib/v/gen/c/orm.v b/vlib/v/gen/c/orm.v index 23f03f8d3a..df7ad5c74b 100644 --- a/vlib/v/gen/c/orm.v +++ b/vlib/v/gen/c/orm.v @@ -625,6 +625,8 @@ fn (mut g Gen) write_orm_primitive(t ast.Type, expr ast.Expr) { typ = 'option_${typ}' } else if g.table.final_sym(t).kind == .enum { typ = g.table.sym(g.table.final_type(t)).cname + } else if g.table.final_sym(t).kind == .array { + typ = g.table.sym(g.table.final_type(t)).cname.to_lower() } g.write('orm__${typ}_to_primitive(') if expr is ast.CallExpr { @@ -778,6 +780,12 @@ fn (mut g Gen) write_orm_where_expr(expr ast.Expr, mut fields []string, mut pare .not_is { 'orm__OperationKind__is_not_null' } + .key_in { + 'orm__OperationKind__in' + } + .not_in { + 'orm__OperationKind__not_in' + } else { '' }