mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
cgen, checker: add panic in ORM for invalid queries, when there are no or {} blocks, add type checking for the fkey attribute, add tests (#16977)
This commit is contained in:
@@ -5,6 +5,10 @@ module checker
|
||||
import v.ast
|
||||
import v.token
|
||||
|
||||
const (
|
||||
fkey_attr_name = 'fkey'
|
||||
)
|
||||
|
||||
fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
||||
c.inside_sql = true
|
||||
defer {
|
||||
@@ -156,6 +160,8 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||
|| (c.table.sym(it.typ).kind == .array
|
||||
&& c.table.sym(c.table.sym(it.typ).array_info().elem_type).kind == .struct_))
|
||||
&& c.table.get_type_name(it.typ) != 'time.Time') {
|
||||
c.check_orm_struct_field_attributes(f)
|
||||
|
||||
typ := if c.table.sym(f.typ).kind == .struct_ {
|
||||
f.typ
|
||||
} else if c.table.sym(f.typ).kind == .array {
|
||||
@@ -163,6 +169,7 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||
} else {
|
||||
ast.Type(0)
|
||||
}
|
||||
|
||||
mut object_var_name := '${node.object_var_name}.${f.name}'
|
||||
if typ != f.typ {
|
||||
object_var_name = node.object_var_name
|
||||
@@ -204,6 +211,52 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||
return ast.void_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_orm_struct_field_attributes(field ast.StructField) {
|
||||
field_type := c.table.sym(field.typ)
|
||||
mut has_fkey_attr := false
|
||||
|
||||
for attr in field.attrs {
|
||||
if attr.name == checker.fkey_attr_name {
|
||||
if field_type.kind != .array && field_type.kind != .struct_ {
|
||||
c.error('The `${checker.fkey_attr_name}` attribute must be used only with arrays and structures',
|
||||
attr.pos)
|
||||
return
|
||||
}
|
||||
|
||||
if !attr.has_arg {
|
||||
c.error('The `${checker.fkey_attr_name}` attribute must have an argument',
|
||||
attr.pos)
|
||||
return
|
||||
}
|
||||
|
||||
if attr.kind != .string {
|
||||
c.error('`${checker.fkey_attr_name}` attribute must be string. Try [${checker.fkey_attr_name}: \'${attr.arg}\'] instead of [${checker.fkey_attr_name}: ${attr.arg}]',
|
||||
attr.pos)
|
||||
return
|
||||
}
|
||||
|
||||
field_struct_type := if field_type.info is ast.Array {
|
||||
c.table.sym(field_type.info.elem_type)
|
||||
} else {
|
||||
field_type
|
||||
}
|
||||
|
||||
field_struct_type.find_field(attr.arg) or {
|
||||
c.error('`${field_struct_type.name}` struct has no field with name `${attr.arg}`',
|
||||
attr.pos)
|
||||
return
|
||||
}
|
||||
|
||||
has_fkey_attr = true
|
||||
}
|
||||
}
|
||||
|
||||
if field_type.kind == .array && !has_fkey_attr {
|
||||
c.error('A field that holds an array must be defined with the `${checker.fkey_attr_name}` attribute',
|
||||
field.pos)
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Pos, table_name string) []ast.StructField {
|
||||
fields := info.fields.filter(
|
||||
(it.typ in [ast.string_type, ast.bool_type] || int(it.typ) in ast.number_type_idxs
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
module c
|
||||
|
||||
import v.ast
|
||||
import v.token
|
||||
import v.util
|
||||
|
||||
enum SqlExprSide {
|
||||
@@ -29,7 +30,6 @@ fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string, or_expr ast.OrExpr
|
||||
table_name := g.get_table_name(node.table_expr)
|
||||
g.sql_table_name = g.table.sym(node.table_expr.typ).name
|
||||
res := g.new_tmp_var()
|
||||
mut subs := false
|
||||
|
||||
if node.kind != .create {
|
||||
mut fields := []ast.StructField{}
|
||||
@@ -54,15 +54,13 @@ fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string, or_expr ast.OrExpr
|
||||
if node.kind == .create {
|
||||
g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
|
||||
g.sql_create_table(node, expr, table_name)
|
||||
subs = true
|
||||
} else if node.kind == .drop {
|
||||
g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
|
||||
g.writeln('drop(${expr}._object, _SLIT("${table_name}"));')
|
||||
subs = true
|
||||
} else if node.kind == .insert {
|
||||
arr := g.new_tmp_var()
|
||||
g.writeln('Array_orm__Primitive ${arr} = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
|
||||
g.sql_insert(node, expr, table_name, arr, res, '', false, '', or_expr)
|
||||
g.sql_insert(node, expr, table_name, arr, res, '', '', or_expr)
|
||||
} else if node.kind == .update {
|
||||
g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
|
||||
g.sql_update(node, expr, table_name)
|
||||
@@ -70,13 +68,11 @@ fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string, or_expr ast.OrExpr
|
||||
g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
|
||||
g.sql_delete(node, expr, table_name)
|
||||
}
|
||||
|
||||
if or_expr.kind == .block {
|
||||
g.or_block(res, or_expr, ast.int_type.set_flag(.result))
|
||||
}
|
||||
if subs {
|
||||
for _, sub in node.sub_structs {
|
||||
g.sql_stmt_line(sub, expr, or_expr)
|
||||
}
|
||||
} else if or_expr.kind == .absent {
|
||||
g.write_error_handling_for_orm_result(node.pos, res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +117,7 @@ fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr string, table_name st
|
||||
g.writeln('));')
|
||||
}
|
||||
|
||||
fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, is_array bool, fkey string, or_expr ast.OrExpr) {
|
||||
fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, fkey string, or_expr ast.OrExpr) {
|
||||
mut subs := []ast.SqlStmtLine{}
|
||||
mut arrs := []ast.SqlStmtLine{}
|
||||
mut fkeys := []string{}
|
||||
@@ -240,16 +236,13 @@ fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string,
|
||||
arr.fields = fff.clone()
|
||||
unsafe { fff.free() }
|
||||
g.sql_insert(arr, expr, g.get_table_name(arr.table_expr), last_ids, res_,
|
||||
id_name, true, fkeys[i], or_expr)
|
||||
id_name, fkeys[i], or_expr)
|
||||
g.writeln('}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) sql_update(node ast.SqlStmtLine, expr string, table_name string) {
|
||||
// println(table_name)
|
||||
// println(expr)
|
||||
// println(node)
|
||||
g.write('update(${expr}._object, _SLIT("${table_name}"), (orm__QueryData){')
|
||||
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),')
|
||||
@@ -649,6 +642,8 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string, or_expr as
|
||||
g.or_block(tmp_left, node.or_expr, node.typ.set_flag(.result))
|
||||
g.writeln('else {')
|
||||
g.indent++
|
||||
} else if node.or_expr.kind == .absent {
|
||||
g.write_error_handling_for_orm_result(node.pos, '_o${res}')
|
||||
}
|
||||
|
||||
g.writeln('Array_Array_orm__Primitive ${res} = (*(Array_Array_orm__Primitive*)_o${res}.data);')
|
||||
@@ -849,3 +844,17 @@ fn (mut g Gen) get_field_name(field ast.StructField) string {
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_error_handling_for_orm_result(expr_pos &token.Pos, result_var_name string) {
|
||||
g.writeln('if (${result_var_name}.is_error) {')
|
||||
|
||||
if g.pref.is_debug {
|
||||
g.write_v_source_line_info(expr_pos)
|
||||
paline, pafile, pamod, pafn := g.panic_debug_info(expr_pos)
|
||||
g.write('\tpanic_debug(${paline}, tos3("${pafile}"), tos3("${pamod}"), tos3("${pafn}"), IError_str(${result_var_name}.err) );')
|
||||
} else {
|
||||
g.writeln('\t_v_panic(IError_str(${result_var_name}.err));')
|
||||
}
|
||||
|
||||
g.writeln('}')
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import v.util.vtest
|
||||
|
||||
const turn_off_vcolors = os.setenv('VCOLORS', 'never', true)
|
||||
|
||||
const v_ci_ubuntu_musl = os.getenv('V_CI_UBUNTU_MUSL').len > 0
|
||||
|
||||
const skip_files = [
|
||||
'do_not_remove_this',
|
||||
'tmpl_parse_html.vv', // skipped, due to a V template compilation problem after b42c824
|
||||
@@ -36,6 +38,13 @@ fn test_all() {
|
||||
println(term.bright_yellow('SKIP'))
|
||||
continue
|
||||
}
|
||||
if v_ci_ubuntu_musl {
|
||||
if fname.contains('orm_') {
|
||||
// the ORM programs use db.sqlite, which is not easy to install in a way usable by ubuntu-musl, so just skip them:
|
||||
println(term.bright_yellow('SKIP on ubuntu musl'))
|
||||
continue
|
||||
}
|
||||
}
|
||||
program := path
|
||||
tname := rand.ulid()
|
||||
compilation := os.execute('${os.quoted_path(vexe)} -o ${tname} -cflags "-w" -cg ${os.quoted_path(program)}')
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
================ V panic ================
|
||||
module: main
|
||||
function: main()
|
||||
message: db.sqlite.SQLError: no such table: User (1) (INSERT INTO `User` (`id`, `name`) VALUES (?1, ?2);); code: 1
|
||||
file: vlib/v/slow_tests/inout/orm_panic_for_insert_into_not_created_table.vv:16
|
||||
@@ -0,0 +1,18 @@
|
||||
import db.sqlite
|
||||
|
||||
struct User {
|
||||
id int
|
||||
name string
|
||||
}
|
||||
|
||||
fn main() {
|
||||
db := sqlite.connect(':memory:') or { panic(err) }
|
||||
|
||||
user := User{
|
||||
name: 'test'
|
||||
}
|
||||
|
||||
sql db {
|
||||
insert user into User
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
================ V panic ================
|
||||
module: main
|
||||
function: main()
|
||||
message: db.sqlite.SQLError: no such table: User (1) (SELECT `id`, `name` FROM `User`;); code: 1
|
||||
file: vlib/v/slow_tests/inout/orm_panic_for_select_from_not_created_table.vv:11
|
||||
@@ -0,0 +1,16 @@
|
||||
import db.sqlite
|
||||
|
||||
struct User {
|
||||
id int
|
||||
name string
|
||||
}
|
||||
|
||||
fn main() {
|
||||
db := sqlite.connect(':memory:') or { panic(err) }
|
||||
|
||||
users := sql db {
|
||||
select from User
|
||||
}
|
||||
|
||||
println(users)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import db.sqlite
|
||||
|
||||
struct User {
|
||||
id int
|
||||
name string
|
||||
}
|
||||
|
||||
fn test_or_block_error_handling_of_an_invalid_query() {
|
||||
db := sqlite.connect(':memory:') or { panic(err) }
|
||||
|
||||
users := sql db {
|
||||
select from User
|
||||
} or { []User{} }
|
||||
|
||||
println(users)
|
||||
assert true
|
||||
}
|
||||
@@ -18,6 +18,9 @@ fn test_orm_array() {
|
||||
sql db {
|
||||
create table Parent
|
||||
}
|
||||
sql db {
|
||||
create table Child
|
||||
}
|
||||
|
||||
par := Parent{
|
||||
name: 'test'
|
||||
@@ -54,6 +57,9 @@ fn test_orm_relationship() {
|
||||
sql db {
|
||||
create table Parent
|
||||
}
|
||||
sql db {
|
||||
create table Child
|
||||
}
|
||||
|
||||
mut child := Child{
|
||||
name: 'abc'
|
||||
@@ -111,5 +117,5 @@ fn test_orm_relationship() {
|
||||
select from Child
|
||||
}
|
||||
|
||||
assert children.len == 0
|
||||
assert children.len == 2
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ fn test_orm_sub_structs() {
|
||||
sql db {
|
||||
create table Upper
|
||||
}
|
||||
sql db {
|
||||
create table SubStruct
|
||||
}
|
||||
|
||||
upper_1 := Upper{
|
||||
sub: SubStruct{
|
||||
|
||||
Reference in New Issue
Block a user