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 | |