// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module parser import v.ast fn (mut p Parser) sql_expr() ast.Expr { // `sql db {` pos := p.tok.pos() p.check_name() db_expr := p.check_expr(0) or { p.error_with_pos('invalid expression: unexpected $p.tok, expecting database', p.tok.pos()) } p.check(.lcbr) p.check(.key_select) n := p.check_name() is_count := n == 'count' mut typ := ast.void_type if is_count { p.check_name() // from typ = ast.int_type } table_pos := p.tok.pos() table_type := p.parse_type() // `User` mut where_expr := ast.empty_expr() has_where := p.tok.kind == .name && p.tok.lit == 'where' mut query_one := false // one object is returned, not an array if has_where { p.next() where_expr = p.expr(0) // `id == x` means that a single object is returned if !is_count && where_expr is ast.InfixExpr { e := where_expr as ast.InfixExpr if e.op == .eq && e.left is ast.Ident { if e.left.name == 'id' { query_one = true } } if e.right is ast.Ident { if !p.scope.known_var(e.right.name) { p.check_undefined_variables([e.left], e.right) or { return p.error_with_pos(err.msg(), e.right.pos) } } } } } mut has_limit := false mut limit_expr := ast.empty_expr() mut has_offset := false mut offset_expr := ast.empty_expr() mut has_order := false mut order_expr := ast.empty_expr() mut has_desc := false if p.tok.kind == .name && p.tok.lit == 'order' { p.check_name() // `order` order_pos := p.tok.pos() if p.tok.kind == .name && p.tok.lit == 'by' { p.check_name() // `by` } else { return p.error_with_pos('use `order by` in ORM queries', order_pos) } has_order = true order_expr = p.expr(0) if p.tok.kind == .name && p.tok.lit == 'desc' { p.check_name() // `desc` has_desc = true } } if p.tok.kind == .name && p.tok.lit == 'limit' { // `limit 1` means that a single object is returned p.check_name() // `limit` if p.tok.kind == .number && p.tok.lit == '1' { query_one = true } has_limit = true limit_expr = p.expr(0) } if p.tok.kind == .name && p.tok.lit == 'offset' { p.check_name() // `offset` has_offset = true offset_expr = p.expr(0) } if !query_one && !is_count { // return an array typ = ast.new_type(p.table.find_or_register_array(table_type)) } else if !is_count { // return a single object // TODO optional // typ = table_type.set_flag(.optional) typ = table_type } p.check(.rcbr) return ast.SqlExpr{ is_count: is_count typ: typ db_expr: db_expr where_expr: where_expr has_where: has_where has_limit: has_limit limit_expr: limit_expr has_offset: has_offset offset_expr: offset_expr has_order: has_order order_expr: order_expr has_desc: has_desc is_array: !query_one pos: pos.extend(p.prev_tok.pos()) table_expr: ast.TypeNode{ typ: table_type pos: table_pos } } } // insert user into User // update User set nr_oders=nr_orders+1 where id == user_id fn (mut p Parser) sql_stmt() ast.SqlStmt { mut pos := p.tok.pos() p.inside_match = true defer { p.inside_match = false } // `sql db {` p.check_name() db_expr := p.check_expr(0) or { p.error_with_pos('invalid expression: unexpected $p.tok, expecting database', p.tok.pos()) } // println(typeof(db_expr)) p.check(.lcbr) mut lines := []ast.SqlStmtLine{} for p.tok.kind != .rcbr { lines << p.parse_sql_stmt_line() } p.next() pos.last_line = p.prev_tok.line_nr return ast.SqlStmt{ pos: pos.extend(p.prev_tok.pos()) db_expr: db_expr lines: lines } } fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine { mut n := p.check_name() // insert pos := p.tok.pos() mut kind := ast.SqlStmtKind.insert if n == 'delete' { kind = .delete } else if n == 'update' { kind = .update } else if n == 'create' { kind = .create table := p.check_name() if table != 'table' { p.error('expected `table` got `$table`') return ast.SqlStmtLine{} } typ := p.parse_type() typ_pos := p.tok.pos() return ast.SqlStmtLine{ kind: kind pos: pos.extend(p.prev_tok.pos()) table_expr: ast.TypeNode{ typ: typ pos: typ_pos } } } else if n == 'drop' { kind = .drop table := p.check_name() if table != 'table' { p.error('expected `table` got `$table`') return ast.SqlStmtLine{} } typ := p.parse_type() typ_pos := p.tok.pos() return ast.SqlStmtLine{ kind: kind pos: pos.extend(p.prev_tok.pos()) table_expr: ast.TypeNode{ typ: typ pos: typ_pos } } } mut inserted_var_name := '' mut table_type := ast.Type(0) if kind != .delete { if kind == .update { table_type = p.parse_type() } else if kind == .insert { expr := p.expr(0) if expr is ast.Ident { inserted_var_name = expr.name } else { p.error('can only insert variables') return ast.SqlStmtLine{} } } } n = p.check_name() // into mut updated_columns := []string{} mut update_exprs := []ast.Expr{cap: 5} if kind == .insert && n != 'into' { p.error('expecting `into`') return ast.SqlStmtLine{} } else if kind == .update { if n != 'set' { p.error('expecting `set`') return ast.SqlStmtLine{} } for { column := p.check_name() updated_columns << column p.check(.assign) update_exprs << p.expr(0) if p.tok.kind == .comma { p.check(.comma) } else { break } } } else if kind == .delete && n != 'from' { p.error('expecting `from`') return ast.SqlStmtLine{} } mut table_pos := p.tok.pos() mut where_expr := ast.empty_expr() if kind == .insert { table_pos = p.tok.pos() table_type = p.parse_type() } else if kind == .update { p.check_sql_keyword('where') or { return ast.SqlStmtLine{} } where_expr = p.expr(0) } else if kind == .delete { table_pos = p.tok.pos() table_type = p.parse_type() p.check_sql_keyword('where') or { return ast.SqlStmtLine{} } where_expr = p.expr(0) } return ast.SqlStmtLine{ table_expr: ast.TypeNode{ typ: table_type pos: table_pos } object_var_name: inserted_var_name pos: pos updated_columns: updated_columns update_exprs: update_exprs kind: kind where_expr: where_expr } } fn (mut p Parser) check_sql_keyword(name string) ?bool { if p.check_name() != name { p.error('orm: expecting `$name`') return none } return true }