v2 / vlib / v / parser / orm.v
850 lines · 791 sloc · 21.06 KB · c262728202262552db2119141035b022a691d4f0
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module parser
5
6import v.ast
7import v.token
8
9struct SqlPrefix {
10 pos token.Pos
11 db_expr ast.Expr
12 tmp_inside_match bool
13 is_dynamic bool
14}
15
16struct SqlQueryDataItemsBlock {
17 items []ast.SqlQueryDataItem
18 end_comments []ast.Comment
19}
20
21fn sql_aggregate_kind_from_name(name string) ast.SqlAggregateKind {
22 return match name {
23 'count' { .count }
24 'sum' { .sum }
25 'avg' { .avg }
26 'min' { .min }
27 'max' { .max }
28 else { .none }
29 }
30}
31
32fn (mut p Parser) parse_sql_select_field_name() ast.SqlSelectField {
33 pos := p.tok.pos()
34 mut parts := []string{}
35 parts << p.check_name()
36 for p.tok.kind == .dot {
37 p.next()
38 parts << p.check_name()
39 }
40 return ast.SqlSelectField{
41 name: parts.join('.')
42 pos: pos.extend(p.prev_tok.pos())
43 }
44}
45
46fn (mut p Parser) parse_sql_select_fields() []ast.SqlSelectField {
47 mut fields := []ast.SqlSelectField{}
48 for {
49 if p.tok.kind != .name {
50 p.error('expected an ORM select field before `from`')
51 return fields
52 }
53 fields << p.parse_sql_select_field_name()
54 if p.tok.kind != .comma {
55 break
56 }
57 p.next()
58 }
59 return fields
60}
61
62// select from User
63// insert user into User returning id
64fn (mut p Parser) sql_expr() ast.Expr {
65 prefix := p.sql_prefix()
66 return p.sql_expr_after_prefix(prefix)
67}
68
69fn (mut p Parser) sql_stmt_or_expr() ast.Stmt {
70 prefix := p.sql_prefix()
71 if p.sql_stmt_can_be_value()
72 && (p.tok.kind == .key_select || (p.tok.kind == .name && p.tok.lit == 'insert')) {
73 expr := p.sql_expr_after_prefix(prefix)
74 return ast.ExprStmt{
75 expr: expr
76 pos: expr.pos()
77 }
78 }
79 return p.sql_stmt_after_prefix(prefix)
80}
81
82fn (p &Parser) sql_stmt_can_be_value() bool {
83 return p.inside_if_expr || p.inside_match_body || p.inside_or_expr
84}
85
86fn (mut p Parser) sql_prefix() SqlPrefix {
87 tmp_inside_match := p.inside_match
88 p.inside_orm = true
89 p.inside_match = true
90 pos := p.tok.pos()
91 p.check_name()
92 db_expr := p.check_expr(0) or {
93 p.unexpected(prepend_msg: 'invalid expression:', expecting: 'database')
94 }
95 p.check(.lcbr)
96 mut is_dynamic := false
97 if p.tok.kind == .name && p.tok.lit == 'dynamic' {
98 is_dynamic = true
99 p.next()
100 }
101 return SqlPrefix{
102 pos: pos
103 db_expr: db_expr
104 tmp_inside_match: tmp_inside_match
105 is_dynamic: is_dynamic
106 }
107}
108
109fn (mut p Parser) sql_expr_after_prefix(prefix SqlPrefix) ast.Expr {
110 is_dynamic := prefix.is_dynamic
111 is_select := p.tok.kind == .key_select
112 is_insert := p.tok.lit == 'insert'
113 if !is_select && !is_insert {
114 p.error('expected "select" or "insert" in an ORM expression')
115 }
116 if is_dynamic && !is_select {
117 p.error('expected "select" after `dynamic` in an ORM expression')
118 }
119 p.next()
120 // kind := if is_select { ast.SqlExprKind.select_ } else { ast.SqlExprKind.insert }
121 mut inserted_var := ''
122 mut aggregate_kind := ast.SqlAggregateKind.none
123 mut aggregate_field := ''
124 mut has_distinct := false
125 mut requested_fields := []ast.SqlSelectField{}
126 if is_insert {
127 inserted_var = p.check_name()
128 p.scope.mark_var_as_used(inserted_var)
129 into := p.check_name()
130 if into != 'into' {
131 p.error('expecting `into`')
132 }
133 } else if is_select {
134 if p.tok.kind == .name && p.tok.lit == 'distinct' {
135 has_distinct = true
136 p.next()
137 }
138 if p.tok.kind == .name {
139 aggregate_kind = sql_aggregate_kind_from_name(p.tok.lit)
140 }
141 if aggregate_kind == .count {
142 p.next()
143 n := p.check_name() // from
144 if n != 'from' {
145 p.error('expecting "from" in a "select count" ORM statement')
146 }
147 } else if aggregate_kind != .none {
148 p.next()
149 if p.tok.kind != .lpar {
150 p.error('expecting `(` after aggregate function in ORM select')
151 }
152 p.next()
153 if p.tok.kind != .name {
154 p.error('ORM aggregate functions only support a single field name argument')
155 }
156 aggregate_field = p.check_name()
157 if p.tok.kind != .rpar {
158 p.error('ORM aggregate functions only support a single field name argument')
159 }
160 p.next()
161 n := p.check_name() // from
162 if n != 'from' {
163 p.error('expecting `from` after ORM aggregate function')
164 }
165 } else if p.tok.kind == .name && p.tok.lit == 'from' {
166 p.next()
167 } else {
168 requested_fields = p.parse_sql_select_fields()
169 n := p.check_name() // from
170 if n != 'from' {
171 p.error('expecting `from` after ORM select fields')
172 }
173 }
174 }
175 mut typ := ast.void_type
176
177 table_pos := p.tok.pos()
178 table_type := p.parse_type() // `User`
179
180 // Parse JOIN clauses (e.g., `join Department on User.dept_id == Department.id`)
181 mut joins := []ast.JoinClause{}
182 for p.tok.kind == .name && p.tok.lit in ['join', 'left', 'right', 'full', 'inner'] {
183 join_clause := p.parse_sql_join_clause()
184 if join_clause.table_expr.typ != ast.void_type {
185 joins << join_clause
186 }
187 }
188
189 mut where_expr := ast.empty_expr
190 has_where := p.tok.kind == .name && p.tok.lit == 'where'
191
192 if has_where {
193 p.next()
194 where_expr = p.expr(0)
195 if !is_dynamic {
196 where_check_result := p.check_sql_where_expr_has_no_undefined_variables(&where_expr, [])
197 if where_check_result is ast.NodeError {
198 return where_check_result
199 }
200 }
201 }
202
203 mut has_limit := false
204 mut limit_expr := ast.empty_expr
205 mut has_offset := false
206 mut offset_expr := ast.empty_expr
207 mut has_order := false
208 mut order_expr := ast.empty_expr
209 mut has_desc := false
210 if p.tok.kind == .name && p.tok.lit == 'order' {
211 p.check_name() // `order`
212 order_pos := p.tok.pos()
213 if p.tok.kind == .name && p.tok.lit == 'by' {
214 p.check_name() // `by`
215 } else {
216 return p.error_with_pos('use `order by` in ORM queries', order_pos)
217 }
218 has_order = true
219 order_expr = p.expr(0)
220 if p.tok.kind == .name && p.tok.lit in ['asc', 'desc'] {
221 has_desc = p.tok.lit == 'desc'
222 p.check_name() // `asc` or `desc`
223 }
224 }
225
226 if p.tok.kind == .name && p.tok.lit == 'limit' {
227 p.check_name() // `limit`
228 has_limit = true
229 limit_expr = p.expr(0)
230 }
231
232 if p.tok.kind == .name && p.tok.lit == 'offset' {
233 p.check_name() // `offset`
234 has_offset = true
235 offset_expr = p.expr(0)
236 }
237
238 if aggregate_kind == .count {
239 typ = ast.int_type
240 } else if aggregate_kind != .none {
241 typ = ast.void_type
242 } else if table_type.has_flag(.generic) {
243 typ = ast.new_type(p.table.find_or_register_array(table_type)).set_flag(.generic)
244 } else {
245 typ = ast.new_type(p.table.find_or_register_array(table_type))
246 }
247
248 p.check(.rcbr)
249 p.inside_match = false
250 p.inside_orm = false
251 or_expr := p.parse_sql_or_block()
252 p.inside_match = prefix.tmp_inside_match
253
254 return ast.SqlExpr{
255 aggregate_kind: aggregate_kind
256 aggregate_field: aggregate_field
257 is_insert: is_insert
258 typ: typ.set_flag(.result)
259 scope: p.scope
260 or_expr: or_expr
261 db_expr: prefix.db_expr
262 where_expr: where_expr
263 has_where: has_where
264 has_limit: has_limit
265 limit_expr: limit_expr
266 has_offset: has_offset
267 offset_expr: offset_expr
268 has_order: has_order
269 order_expr: order_expr
270 has_desc: has_desc
271 has_distinct: has_distinct
272 is_array: aggregate_kind == .none
273 is_generated: false
274 is_dynamic: is_dynamic
275 inserted_var: inserted_var
276 requested_fields: requested_fields
277 pos: prefix.pos.extend(p.prev_tok.pos())
278 table_expr: ast.TypeNode{
279 typ: table_type
280 pos: table_pos
281 }
282 joins: joins
283 }
284}
285
286// parse_sql_join_clause parses a JOIN clause like:
287// `join Department on User.dept_id == Department.id`
288// `left join Department on User.dept_id == Department.id`
289// `inner join Department on User.dept_id == Department.id`
290fn (mut p Parser) parse_sql_join_clause() ast.JoinClause {
291 mut kind := ast.JoinKind.inner
292 join_pos := p.tok.pos()
293
294 // Check for join type prefix (left, right, full, inner)
295 if p.tok.lit == 'left' {
296 kind = .left
297 p.next()
298 } else if p.tok.lit == 'right' {
299 kind = .right
300 p.next()
301 } else if p.tok.lit == 'full' {
302 kind = .full_outer
303 p.next()
304 // Handle optional 'outer' keyword
305 if p.tok.kind == .name && p.tok.lit == 'outer' {
306 p.next()
307 }
308 } else if p.tok.lit == 'inner' {
309 // 'inner' is optional, just skip it
310 p.next()
311 }
312
313 // Now expect 'join'
314 if p.tok.kind != .name || p.tok.lit != 'join' {
315 p.error('expected `join` keyword after join type')
316 return ast.JoinClause{}
317 }
318 p.next() // consume 'join'
319
320 // Parse the joined table type
321 table_pos := p.tok.pos()
322 table_type := p.parse_type()
323
324 // Expect 'on' keyword
325 if p.tok.kind != .name || p.tok.lit != 'on' {
326 p.error('expected `on` keyword after table name in JOIN clause')
327 return ast.JoinClause{}
328 }
329 p.next() // consume 'on'
330
331 // Parse the ON condition expression
332 on_expr := p.expr(0)
333
334 return ast.JoinClause{
335 kind: kind
336 pos: join_pos
337 table_expr: ast.TypeNode{
338 typ: table_type
339 pos: table_pos
340 }
341 on_expr: on_expr
342 }
343}
344
345// insert user into User
346// update User set nr_oders=nr_orders+1 where id == user_id
347// delete
348fn (mut p Parser) sql_stmt() ast.SqlStmt {
349 prefix := p.sql_prefix()
350 return p.sql_stmt_after_prefix(prefix)
351}
352
353fn (mut p Parser) sql_stmt_after_prefix(prefix SqlPrefix) ast.SqlStmt {
354 mut pos := prefix.pos
355
356 mut lines := []ast.SqlStmtLine{}
357
358 for p.tok.kind != .rcbr {
359 if p.tok.kind == .eof {
360 p.unexpected_with_pos(pos, got: 'eof, while parsing an SQL statement')
361 return ast.SqlStmt{}
362 }
363 lines << p.parse_sql_stmt_line(prefix.is_dynamic)
364 }
365
366 p.next()
367
368 p.inside_orm = false
369 p.inside_match = false
370 mut or_expr := p.parse_sql_or_block()
371 p.inside_match = prefix.tmp_inside_match
372
373 pos.last_line = p.prev_tok.line_nr
374 return ast.SqlStmt{
375 pos: pos.extend(p.prev_tok.pos())
376 db_expr: prefix.db_expr
377 lines: lines
378 or_expr: or_expr
379 }
380}
381
382fn (mut p Parser) parse_sql_or_block() ast.OrExpr {
383 mut stmts := []ast.Stmt{}
384 mut kind := ast.OrKind.absent
385 mut pos := p.tok.pos()
386 mut or_scope := ast.empty_scope
387
388 if p.tok.kind == .key_orelse {
389 kind = .block
390 stmts, pos, or_scope = p.or_block(.with_err_var)
391 } else if p.tok.kind == .not {
392 kind = .propagate_result
393 or_scope = p.scope
394 p.next()
395 }
396
397 return ast.OrExpr{
398 stmts: stmts
399 kind: kind
400 pos: pos
401 scope: or_scope
402 }
403}
404
405fn (mut p Parser) parse_sql_stmt_line(is_dynamic bool) ast.SqlStmtLine {
406 pre_comments := p.eat_comments()
407 mut n := p.check_name() // insert
408 pos := p.tok.pos()
409 mut kind := ast.SqlStmtKind.insert
410 if n == 'delete' {
411 kind = .delete
412 } else if n == 'upsert' {
413 kind = .upsert
414 } else if n == 'update' {
415 kind = .update
416 } else if n == 'create' {
417 kind = .create
418 table := p.check_name()
419 if table != 'table' {
420 p.error('expected `table` got `${table}`')
421 return ast.SqlStmtLine{}
422 }
423 typ := p.parse_type()
424 typ_pos := p.tok.pos()
425 end_comments := p.eat_comments()
426 return ast.SqlStmtLine{
427 kind: kind
428 pos: pos.extend(p.prev_tok.pos())
429 table_expr: ast.TypeNode{
430 typ: typ
431 pos: typ_pos
432 }
433 scope: p.scope
434 is_generated: false
435 is_dynamic: is_dynamic
436 pre_comments: pre_comments
437 end_comments: end_comments
438 }
439 } else if n == 'drop' {
440 kind = .drop
441 table := p.check_name()
442 if table != 'table' {
443 p.error('expected `table` got `${table}`')
444 return ast.SqlStmtLine{}
445 }
446 typ := p.parse_type()
447 typ_pos := p.tok.pos()
448 end_comments := p.eat_comments()
449 return ast.SqlStmtLine{
450 kind: kind
451 pos: pos.extend(p.prev_tok.pos())
452 table_expr: ast.TypeNode{
453 typ: typ
454 pos: typ_pos
455 }
456 is_generated: false
457 is_dynamic: is_dynamic
458 scope: p.scope
459 pre_comments: pre_comments
460 end_comments: end_comments
461 }
462 }
463 if is_dynamic && kind != .update {
464 p.error('`dynamic` is only supported for ORM `select` and `update` queries')
465 return ast.SqlStmtLine{}
466 }
467 mut inserted_var := ''
468 mut table_type := ast.no_type
469 if kind != .delete {
470 if kind == .update {
471 table_type = p.parse_type()
472 } else if kind in [.insert, .upsert] {
473 expr := p.expr(0)
474 if expr is ast.Ident {
475 inserted_var = expr.name
476 } else {
477 p.error('can only insert variables')
478 return ast.SqlStmtLine{}
479 }
480 }
481 }
482 n = p.check_name() // into
483 mut updated_columns := []string{}
484 mut update_exprs := []ast.Expr{cap: 5}
485 mut update_data_expr := ast.empty_expr
486 if kind in [.insert, .upsert] && n != 'into' {
487 p.error('expecting `into`')
488 return ast.SqlStmtLine{}
489 } else if kind == .update {
490 if n != 'set' {
491 p.error('expecting `set`')
492 return ast.SqlStmtLine{}
493 }
494 if is_dynamic {
495 update_data_expr = p.expr(0)
496 } else {
497 for {
498 column := p.check_name()
499 updated_columns << column
500 p.check(.assign)
501 update_exprs << p.expr(0)
502 if p.tok.kind == .comma {
503 p.check(.comma)
504 } else {
505 break
506 }
507 }
508 }
509 } else if kind == .delete && n != 'from' {
510 p.error('expecting `from`')
511 return ast.SqlStmtLine{}
512 }
513
514 mut table_pos := p.tok.pos()
515 mut where_expr := ast.empty_expr
516 if kind in [.insert, .upsert] {
517 table_pos = p.tok.pos()
518 table_type = p.parse_type()
519 } else if kind == .update {
520 p.check_sql_keyword('where') or { return ast.SqlStmtLine{} }
521 where_expr = p.expr(0)
522
523 where_expr_result := p.check_sql_where_expr_has_no_undefined_variables(&where_expr, [])
524 if where_expr_result is ast.NodeError {
525 return ast.SqlStmtLine{}
526 }
527 } else if kind == .delete {
528 table_pos = p.tok.pos()
529 table_type = p.parse_type()
530 p.check_sql_keyword('where') or { return ast.SqlStmtLine{} }
531 where_expr = p.expr(0)
532
533 where_expr_result := p.check_sql_where_expr_has_no_undefined_variables(&where_expr, [])
534 if where_expr_result is ast.NodeError {
535 return ast.SqlStmtLine{}
536 }
537 }
538 end_comments := p.eat_comments()
539 return ast.SqlStmtLine{
540 table_expr: ast.TypeNode{
541 typ: table_type
542 pos: table_pos
543 }
544 object_var: inserted_var
545 pos: pos
546 updated_columns: updated_columns
547 update_exprs: update_exprs
548 update_data_expr: update_data_expr
549 kind: kind
550 where_expr: where_expr
551 is_generated: false
552 is_dynamic: is_dynamic
553 scope: p.scope
554 pre_comments: pre_comments
555 end_comments: end_comments
556 }
557}
558
559fn (p &Parser) is_sql_query_data_expr() bool {
560 if p.tok.kind != .lcbr {
561 return false
562 }
563 mut idx := 1
564 for p.peek_token(idx).kind == .comment {
565 idx++
566 }
567 first := p.peek_token(idx)
568 if first.kind == .key_if {
569 return true
570 }
571 if first.kind != .name {
572 return false
573 }
574 idx++
575 for p.peek_token(idx).kind == .dot && p.peek_token(idx + 1).kind == .name {
576 idx += 2
577 }
578 return is_sql_query_data_operator(p.peek_token(idx).kind)
579}
580
581fn is_sql_query_data_operator(kind token.Kind) bool {
582 return kind in [.eq, .ne, .gt, .lt, .ge, .le, .key_like, .key_ilike, .key_in, .not_in, .key_is,
583 .not_is]
584}
585
586fn (mut p Parser) sql_query_data_expr() ast.Expr {
587 start_pos := p.tok.pos()
588 block := p.parse_sql_query_data_items_block()
589 return ast.SqlQueryDataExpr{
590 pos: start_pos.extend(p.prev_tok.pos())
591 items: block.items
592 end_comments: block.end_comments
593 }
594}
595
596fn (mut p Parser) parse_sql_query_data_items_block() SqlQueryDataItemsBlock {
597 p.check(.lcbr)
598 mut items := []ast.SqlQueryDataItem{}
599 mut pending_comments := p.eat_comments()
600 for {
601 if p.tok.kind == .rcbr {
602 break
603 }
604 if p.tok.kind == .eof {
605 p.unexpected(got: 'eof, while parsing a dynamic ORM expression')
606 break
607 }
608 mut item := p.parse_sql_query_data_item()
609 item = sql_query_data_item_with_pre_comments(item, pending_comments)
610 pending_comments = []
611 item = sql_query_data_item_with_end_comments(item, p.eat_comments(same_line: true))
612 if p.tok.kind == .comma {
613 p.next()
614 item = sql_query_data_item_with_end_comments(item, p.eat_comments(same_line: true))
615 items << item
616 pending_comments = p.eat_comments()
617 continue
618 }
619 items << item
620 pending_comments = p.eat_comments()
621 if p.tok.kind != .rcbr {
622 p.error('expected `,` or `}` in a dynamic ORM expression')
623 break
624 }
625 }
626 p.check(.rcbr)
627 return SqlQueryDataItemsBlock{
628 items: items
629 end_comments: pending_comments
630 }
631}
632
633fn (mut p Parser) parse_sql_query_data_item() ast.SqlQueryDataItem {
634 if p.tok.kind == .key_if {
635 return p.parse_sql_query_data_if_item()
636 }
637 expr := p.expr(0)
638 return ast.SqlQueryDataLeaf{
639 pos: expr.pos()
640 expr: expr
641 }
642}
643
644fn sql_query_data_item_with_pre_comments(item ast.SqlQueryDataItem, comments []ast.Comment) ast.SqlQueryDataItem {
645 if comments.len == 0 {
646 return item
647 }
648 return match item {
649 ast.SqlQueryDataLeaf {
650 mut leaf := item
651 leaf.pre_comments << comments
652 leaf
653 }
654 ast.SqlQueryDataIf {
655 mut if_item := item
656 if_item.pre_comments << comments
657 if_item
658 }
659 }
660}
661
662fn sql_query_data_item_with_end_comments(item ast.SqlQueryDataItem, comments []ast.Comment) ast.SqlQueryDataItem {
663 if comments.len == 0 {
664 return item
665 }
666 return match item {
667 ast.SqlQueryDataLeaf {
668 mut leaf := item
669 leaf.end_comments << comments
670 leaf
671 }
672 ast.SqlQueryDataIf {
673 mut if_item := item
674 if_item.end_comments << comments
675 if_item
676 }
677 }
678}
679
680fn (mut p Parser) parse_sql_query_data_if_guard_expr() ast.IfGuardExpr {
681 p.open_scope()
682 mut vars := []ast.IfGuardVar{}
683 mut var_names := []string{}
684 for {
685 mut var := ast.IfGuardVar{}
686 mut is_mut := false
687 if p.tok.kind == .key_mut {
688 is_mut = true
689 p.next()
690 }
691 var.is_mut = p.scope_var_is_mut(is_mut)
692 var.pos = p.tok.pos()
693 var.name = p.check_name()
694 var_names << var.name
695
696 if p.scope.known_var(var.name) {
697 p.error_with_pos('redefinition of `${var.name}`', var.pos)
698 }
699 vars << var
700 if p.tok.kind != .comma {
701 break
702 }
703 p.next()
704 }
705 p.check(.decl_assign)
706 old_assign_rhs := p.inside_assign_rhs
707 p.inside_assign_rhs = true
708 expr := p.expr(0)
709 p.inside_assign_rhs = old_assign_rhs
710 if expr !in [ast.CallExpr, ast.IndexExpr, ast.PrefixExpr, ast.SelectorExpr, ast.Ident] {
711 p.error_with_pos('if guard condition expression is illegal, it should return an Option',
712 expr.pos())
713 }
714 p.check_undefined_variables(var_names, expr) or { p.error_with_pos(err.msg(), expr.pos()) }
715 cond := ast.IfGuardExpr{
716 vars: vars
717 expr: expr
718 }
719 for var in vars {
720 p.scope.register(ast.Var{
721 name: var.name
722 is_mut: var.is_mut
723 expr: cond
724 pos: var.pos
725 })
726 }
727 return cond
728}
729
730fn (mut p Parser) parse_sql_query_data_if_item() ast.SqlQueryDataItem {
731 start_pos := p.tok.pos()
732 mut branches := []ast.SqlQueryDataBranch{}
733 mut has_else := false
734 mut prev_guard := false
735 for {
736 branch_pos := p.tok.pos()
737 p.check(.key_if)
738 mut cond := ast.empty_expr
739 mut has_guard_scope := false
740 if p.peek_token_after_var_list().kind == .decl_assign {
741 cond = p.parse_sql_query_data_if_guard_expr()
742 has_guard_scope = true
743 prev_guard = true
744 } else {
745 cond = p.expr(0)
746 prev_guard = false
747 }
748 p.open_scope()
749 block := p.parse_sql_query_data_items_block()
750 p.close_scope()
751 if has_guard_scope {
752 p.close_scope()
753 }
754 branches << ast.SqlQueryDataBranch{
755 pos: branch_pos.extend(p.prev_tok.pos())
756 cond: cond
757 items: block.items
758 end_comments: block.end_comments
759 }
760 comments_before_else := p.eat_comments()
761 if p.tok.kind != .key_else {
762 return ast.SqlQueryDataIf{
763 pos: start_pos.extend(p.prev_tok.pos())
764 branches: branches
765 has_else: has_else
766 end_comments: comments_before_else
767 }
768 }
769 if comments_before_else.len > 0 {
770 last_idx := branches.len - 1
771 branches[last_idx].end_comments << comments_before_else
772 }
773 has_else = true
774 else_pos := p.tok.pos()
775 p.next()
776 if p.tok.kind == .key_if {
777 continue
778 }
779 p.open_scope()
780 if prev_guard {
781 p.scope.register(ast.Var{
782 name: 'err'
783 typ: ast.error_type
784 pos: p.tok.pos()
785 is_used: false
786 is_stack_obj: true
787 is_special: true
788 })
789 }
790 else_block := p.parse_sql_query_data_items_block()
791 p.close_scope()
792 branches << ast.SqlQueryDataBranch{
793 pos: else_pos.extend(p.prev_tok.pos())
794 cond: ast.empty_expr
795 items: else_block.items
796 end_comments: else_block.end_comments
797 }
798 break
799 }
800 return ast.SqlQueryDataIf{
801 pos: start_pos.extend(p.prev_tok.pos())
802 branches: branches
803 has_else: has_else
804 }
805}
806
807fn (mut p Parser) check_sql_keyword(name string) ?bool {
808 if p.check_name() != name {
809 p.error('ORM: expecting `${name}`')
810 return none
811 }
812 return true
813}
814
815// check_sql_where_expr_has_no_undefined_variables recursively tries to find undefined variables in the right part of infix expressions.
816fn (mut p Parser) check_sql_where_expr_has_no_undefined_variables(expr &ast.Expr, unacceptable_variable_names []string) ast.Expr {
817 if expr is ast.Ident {
818 if !p.scope.known_var(expr.name) {
819 p.check_undefined_variables(unacceptable_variable_names, expr) or {
820 return p.error_with_pos(err.msg(), expr.pos)
821 }
822 }
823 } else if expr is ast.InfixExpr {
824 if expr.left is ast.Ident {
825 if expr.right is ast.Ident {
826 return p.check_sql_where_expr_has_no_undefined_variables(expr.right, [
827 expr.left.name,
828 ])
829 }
830 }
831
832 left_check_result := p.check_sql_where_expr_has_no_undefined_variables(expr.left, [])
833
834 if left_check_result is ast.NodeError {
835 return left_check_result
836 }
837
838 variable_names := if expr.left is ast.Ident { [expr.left.name] } else { []string{} }
839 right_check_result := p.check_sql_where_expr_has_no_undefined_variables(expr.right,
840 variable_names)
841
842 if right_check_result is ast.NodeError {
843 return right_check_result
844 }
845 } else if expr is ast.ParExpr {
846 return p.check_sql_where_expr_has_no_undefined_variables(expr.expr, [])
847 }
848
849 return ast.empty_expr
850}
851