alex

/

v Public
0 Issues 1 Contributor 0 Releases 4 Branches
Additions: 176 Deletions: 32 View patch
1 import time
2 import db.sqlite
3
4+const (
5+ offset_const = 2
6+)
7+
8 struct Module {
9 id int [primary; sql: serial]
10 name string
11 assert y.len == 2
12 assert y[0].id == 2
13
14- offset_const := 2
15 z := sql db {
16 select from User order by id limit 2 offset offset_const
17 }
18 assert z.len == 2
19 assert z[0].id == 3
20+
21 oldest := sql db {
22 select from User order by age desc limit 1
23 }
24 assert oldest.age == 34
25+
26 offs := 1
27 second_oldest := sql db {
28 select from User order by age desc limit 1 offset offs
29
1 module checker
2
3 import v.ast
4-import v.pref
5 import v.util
6 import v.token
7 import strings
8 return expr.val.i64()
9 }
10 if mut expr is ast.Ident {
11- if mut obj := c.table.global_scope.find_const(expr.name) {
12+ has_expr_mod_in_name := expr.name.contains('.')
13+ expr_name := if has_expr_mod_in_name { expr.name } else { '${expr.mod}.${expr.name}' }
14+
15+ if mut obj := c.table.global_scope.find_const(expr_name) {
16 if obj.typ == 0 {
17 obj.typ = c.expr(obj.expr)
18 }
19
1
2 import v.ast
3 import v.token
4+import v.util
5
6 const (
7 fkey_attr_name = 'fkey'
8+ v_orm_prefix = 'V ORM'
9 )
10
11 fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
12 c.cur_orm_ts = old_ts
13 }
14 if sym.info !is ast.Struct {
15- c.error('the table symbol `${sym.name}` has to be a struct', node.table_expr.pos)
16+ c.orm_error('the table symbol `${sym.name}` has to be a struct', node.table_expr.pos)
17 return ast.void_type
18 }
19 info := sym.info as ast.Struct
20 c.expr(node.where_expr)
21 c.check_expr_has_no_fn_calls_with_non_orm_return_type(&node.where_expr)
22 }
23- if node.has_offset {
24- c.expr(node.offset_expr)
25+
26+ if node.has_order {
27+ if mut node.order_expr is ast.Ident {
28+ order_ident_name := node.order_expr.name
29+
30+ sym.find_field(order_ident_name) or {
31+ field_names := fields.map(it.name)
32+
33+ c.orm_error(util.new_suggestion(order_ident_name, field_names).say('`${sym.name}` structure has no field with name `${order_ident_name}`'),
34+ node.order_expr.pos)
35+ return ast.void_type
36+ }
37+ } else {
38+ c.orm_error("expected `${sym.name}` structure's field", node.order_expr.pos())
39+ return ast.void_type
40+ }
41+
42+ c.expr(node.order_expr)
43 }
44+
45 if node.has_limit {
46 c.expr(node.limit_expr)
47+ c.check_sql_value_expr_is_comptime_with_natural_number_or_expr_with_int_type(mut node.limit_expr,
48+ 'limit')
49 }
50- if node.has_order {
51- c.expr(node.order_expr)
52+
53+ if node.has_offset {
54+ c.expr(node.offset_expr)
55+ c.check_sql_value_expr_is_comptime_with_natural_number_or_expr_with_int_type(mut node.offset_expr,
56+ 'offset')
57 }
58 c.expr(node.db_expr)
59
60 if node.or_expr.kind == .block {
61 if node.or_expr.stmts.len == 0 {
62- c.error('or block needs to return a default value', node.or_expr.pos)
63+ c.orm_error('or block needs to return a default value', node.or_expr.pos)
64 }
65 if node.or_expr.stmts.len > 0 && node.or_expr.stmts.last() is ast.ExprStmt {
66 c.expected_or_type = node.typ
67 c.error('unknown type `${table_sym.name}`', node.pos)
68 return ast.void_type
69 }
70+
71 info := table_sym.info as ast.Struct
72 fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name)
73 mut sub_structs := map[int]ast.SqlStmtLine{}
74 for i, column in node.updated_columns {
75 x := node.fields.filter(it.name == column)
76 if x.len == 0 {
77- c.error('type `${table_sym.name}` has no field named `${column}`', node.pos)
78+ c.orm_error('type `${table_sym.name}` has no field named `${column}`', node.pos)
79 continue
80 }
81 field := x[0]
82 for attr in field.attrs {
83 if attr.name == checker.fkey_attr_name {
84 if field_type.kind != .array && field_type.kind != .struct_ {
85- c.error('The `${checker.fkey_attr_name}` attribute must be used only with arrays and structures',
86+ c.orm_error('the `${checker.fkey_attr_name}` attribute must be used only with arrays and structures',
87 attr.pos)
88 return
89 }
90
91 if !attr.has_arg {
92- c.error('The `${checker.fkey_attr_name}` attribute must have an argument',
93+ c.orm_error('the `${checker.fkey_attr_name}` attribute must have an argument',
94 attr.pos)
95 return
96 }
97
98 if attr.kind != .string {
99- 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}]',
100+ c.orm_error("`${checker.fkey_attr_name}` attribute must be string. Try [${checker.fkey_attr_name}: '${attr.arg}'] instead of [${checker.fkey_attr_name}: ${attr.arg}]",
101 attr.pos)
102 return
103 }
104 }
105
106 field_struct_type.find_field(attr.arg) or {
107- c.error('`${field_struct_type.name}` struct has no field with name `${attr.arg}`',
108+ c.orm_error('`${field_struct_type.name}` struct has no field with name `${attr.arg}`',
109 attr.pos)
110 return
111 }
112 }
113
114 if field_type.kind == .array && !has_fkey_attr {
115- c.error('A field that holds an array must be defined with the `${checker.fkey_attr_name}` attribute',
116+ c.orm_error('a field that holds an array must be defined with the `${checker.fkey_attr_name}` attribute',
117 field.pos)
118 }
119 }
120 && c.table.sym(c.table.sym(it.typ).array_info().elem_type).kind == .struct_))
121 && !it.attrs.contains('skip'))
122 if fields.len == 0 {
123- c.error('V orm: select: empty fields in `${table_name}`', pos)
124+ c.orm_error('select: empty fields in `${table_name}`', pos)
125 return []ast.StructField{}
126 }
127 if fields[0].name != 'id' {
128- c.error('V orm: `id int` must be the first field in `${table_name}`', pos)
129+ c.orm_error('`id int` must be the first field in `${table_name}`', pos)
130 }
131 return fields
132 }
133
134+// check_sql_value_expr_is_comptime_with_natural_number_or_expr_with_int_type checks that an expression is compile-time
135+// and contains an integer greater than or equal to zero or it is a runtime expression with an integer type.
136+fn (mut c Checker) check_sql_value_expr_is_comptime_with_natural_number_or_expr_with_int_type(mut expr ast.Expr, sql_keyword string) {
137+ comptime_number := c.get_comptime_number_value(mut expr) or {
138+ c.check_sql_expr_type_is_int(expr, sql_keyword)
139+ return
140+ }
141+
142+ if comptime_number < 0 {
143+ c.orm_error('`${sql_keyword}` must be greater than or equal to zero', expr.pos())
144+ }
145+}
146+
147+fn (mut c Checker) check_sql_expr_type_is_int(expr &ast.Expr, sql_keyword string) {
148+ if expr is ast.Ident {
149+ if expr.obj.typ.is_int() {
150+ return
151+ }
152+ } else if expr is ast.CallExpr {
153+ if expr.return_type == 0 {
154+ return
155+ }
156+
157+ type_symbol := c.table.sym(expr.return_type)
158+ is_error_type := expr.return_type.has_flag(.result) || expr.return_type.has_flag(.option)
159+ is_acceptable_type := type_symbol.is_int() && !is_error_type
160+
161+ if !is_acceptable_type {
162+ error_type_symbol := c.fn_return_type_flag_to_string(expr.return_type)
163+ c.orm_error('function calls in `${sql_keyword}` must return only an integer type, but `${expr.name}` returns `${error_type_symbol}${type_symbol.name}`',
164+ expr.pos)
165+ }
166+
167+ return
168+ } else if expr is ast.ParExpr {
169+ c.check_sql_expr_type_is_int(&expr.expr, sql_keyword)
170+ return
171+ }
172+
173+ c.orm_error('the type of `${sql_keyword}` must be an integer type', expr.pos())
174+}
175+
176+fn (mut c Checker) orm_error(message string, pos token.Pos) {
177+ c.error('${checker.v_orm_prefix}: ${message}', pos)
178+}
179+
180 // check_expr_has_no_fn_calls_with_non_orm_return_type checks that an expression has no function calls
181 // that return complex types which can't be transformed into SQL.
182 fn (mut c Checker) check_expr_has_no_fn_calls_with_non_orm_return_type(expr &ast.Expr) {
183 }
184
185 type_symbol := c.table.sym(expr.return_type)
186- is_result_type := expr.return_type.has_flag(.result)
187- is_option_type := expr.return_type.has_flag(.option)
188 is_time := type_symbol.cname == 'time__Time'
189 is_not_pointer := !type_symbol.is_pointer()
190- is_error_type := is_result_type || is_option_type
191+ is_error_type := expr.return_type.has_flag(.result) || expr.return_type.has_flag(.option)
192 is_acceptable_type := (type_symbol.is_primitive() || is_time) && is_not_pointer
193 && !is_error_type
194
195 if !is_acceptable_type {
196- error_type_symbol := if is_result_type {
197- '!'
198- } else if is_option_type {
199- '?'
200- } else {
201- ''
202- }
203- c.error('V ORM: function calls must return only primitive types and time.Time, but `${expr.name}` returns `${error_type_symbol}${type_symbol.name}`',
204+ error_type_symbol := c.fn_return_type_flag_to_string(expr.return_type)
205+ c.orm_error('function calls must return only primitive types and time.Time, but `${expr.name}` returns `${error_type_symbol}${type_symbol.name}`',
206 expr.pos)
207 }
208 } else if expr is ast.ParExpr {
209 c.check_expr_has_no_fn_calls_with_non_orm_return_type(&expr.right)
210 }
211 }
212+
213+fn (_ &Checker) fn_return_type_flag_to_string(typ ast.Type) string {
214+ is_result_type := typ.has_flag(.result)
215+ is_option_type := typ.has_flag(.option)
216+
217+ return if is_result_type {
218+ '!'
219+ } else if is_option_type {
220+ '?'
221+ } else {
222+ ''
223+ }
224+}
225
1-vlib/v/checker/tests/orm_empty_struct.vv:9:15: error: V orm: select: empty fields in `Person`
2+vlib/v/checker/tests/orm_empty_struct.vv:9:15: error: V ORM: select: empty fields in `Person`
3 7 | db := sqlite.connect(':memory:')!
4 8 | _ := sql db {
5 9 | select from Person
6
1new file mode 100644
2+vlib/v/checker/tests/orm_limit_less_than_zero_error.vv:18:26: error: V ORM: `limit` must be greater than or equal to zero
3+ 16 |
4+ 17 | users := sql db {
5+ 18 | select from User limit user_limit
6+ | ~~~~~~~~~~
7+ 19 | }
8+ 20 |
9
1new file mode 100644
2+module main
3+
4+import db.sqlite
5+
6+const (
7+ user_limit = -10
8+)
9+
10+struct User {
11+ id int [primary; sql: serial]
12+ username string
13+}
14+
15+fn main() {
16+ db := sqlite.connect(':memory:') or { panic(err) }
17+
18+ users := sql db {
19+ select from User limit user_limit
20+ }
21+
22+ println(users)
23+}
24
1-vlib/v/checker/tests/orm_no_default_value.vv:11:4: error: or block needs to return a default value
2+vlib/v/checker/tests/orm_no_default_value.vv:11:4: error: V ORM: or block needs to return a default value
3 9 | _ := sql db {
4 10 | select from Person
5 11 | } or {
6
1-vlib/v/checker/tests/orm_not_a_struct.vv:10:15: error: the table symbol `Person` has to be a struct
2+vlib/v/checker/tests/orm_not_a_struct.vv:10:15: error: V ORM: the table symbol `Person` has to be a struct
3 8 | db := sqlite.connect(':memory:')!
4 9 | _ := sql db {
5 10 | select from Person
6
1new file mode 100644
2+vlib/v/checker/tests/orm_using_non_struct_field_in_order_by_error.vv:14:29: error: V ORM: `User` structure has no field with name `database`.
3+2 possibilities: `id`, `username`.
4+ 12 |
5+ 13 | users := sql db {
6+ 14 | select from User order by database
7+ | ~~~~~~~~
8+ 15 | }
9+ 16 |
10+vlib/v/checker/tests/orm_using_non_struct_field_in_order_by_error.vv:17:2: error: `println` can not print void expressions
11+ 15 | }
12+ 16 |
13+ 17 | println(users)
14+ | ~~~~~~~~~~~~~~
15+ 18 | }
16
1new file mode 100644
2+module main
3+
4+import db.sqlite
5+
6+struct User {
7+ id int [primary; sql: serial]
8+ username string
9+}
10+
11+fn main() {
12+ db := sqlite.connect(':memory:') or { panic(err) }
13+
14+ users := sql db {
15+ select from User order by database
16+ }
17+
18+ println(users)
19+}
20
1 }
2 else {
3 eprintln(expr)
4- verror('Unknown expr')
5+ verror('V ORM: ${expr.type_name()} is not supported')
6 }
7 }
8 }
9