1// Copyright (c) 2019-2020 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.table
8
9fn (mut p Parser) sql_expr() ast.Expr {
10	// `sql db {`
11	pos := p.tok.position()
12	p.check_name()
13	db_expr := p.expr(0)
14	p.check(.lcbr)
15	p.check(.key_select)
16	n := p.check_name()
17	is_count := n == 'count'
18	mut typ := table.void_type
19	if is_count {
20		p.check_name() // from
21		typ = table.int_type
22	}
23	table_type := p.parse_type() // `User`
24	mut where_expr := ast.Expr{}
25	has_where := p.tok.kind == .name && p.tok.lit == 'where'
26	mut query_one := false // one object is returned, not an array
27	if has_where {
28		p.next()
29		where_expr = p.expr(0)
30		// `id == x` means that a single object is returned
31		if !is_count && where_expr is ast.InfixExpr {
32			e := where_expr as ast.InfixExpr
33			if e.op == .eq && e.left is ast.Ident {
34				ident := e.left as ast.Ident
35				if ident.name == 'id' {
36					query_one = true
37				}
38			}
39		}
40	}
41	mut has_limit := false
42	mut limit_expr := ast.Expr{}
43	mut has_offset := false
44	mut offset_expr := ast.Expr{}
45	mut has_order := false
46	mut order_expr := ast.Expr{}
47	mut has_desc := false
48	if p.tok.kind == .name && p.tok.lit == 'order' {
49		p.check_name() // `order`
50		order_pos := p.tok.position()
51		if p.tok.kind == .name && p.tok.lit == 'by' {
52			p.check_name() // `by`
53		} else {
54			p.error_with_pos('use `order by` in ORM queries', order_pos)
55		}
56		has_order = true
57		order_expr = p.expr(0)
58		if p.tok.kind == .name && p.tok.lit == 'desc' {
59			p.check_name() // `desc`
60			has_desc = true
61		}
62	}
63	if p.tok.kind == .name && p.tok.lit == 'limit' {
64		// `limit 1` means that a single object is returned
65		p.check_name() // `limit`
66		if p.tok.kind == .number && p.tok.lit == '1' {
67			query_one = true
68		}
69		has_limit = true
70		limit_expr = p.expr(0)
71	}
72	if p.tok.kind == .name && p.tok.lit == 'offset' {
73		p.check_name() // `offset`
74		has_offset = true
75		offset_expr = p.expr(0)
76	}
77	if !query_one && !is_count {
78		// return an array
79		typ = table.new_type(p.table.find_or_register_array(table_type, 1, p.mod))
80	} else if !is_count {
81		// return a single object
82		// TODO optional
83		// typ = table_type.set_flag(.optional)
84		typ = table_type
85	}
86	p.check(.rcbr)
87	return ast.SqlExpr{
88		is_count: is_count
89		typ: typ
90		db_expr: db_expr
91		table_type: table_type
92		where_expr: where_expr
93		has_where: has_where
94		has_limit: has_limit
95		limit_expr: limit_expr
96		has_offset: has_offset
97		offset_expr: offset_expr
98		has_order: has_order
99		order_expr: order_expr
100		has_desc: has_desc
101		is_array: !query_one
102		pos: pos
103	}
104}
105
106// insert user into User
107// update User set nr_oders=nr_orders+1 where id == user_id
108fn (mut p Parser) sql_stmt() ast.SqlStmt {
109	pos := p.tok.position()
110	p.inside_match = true
111	defer {
112		p.inside_match = false
113	}
114	// `sql db {`
115	p.check_name()
116	db_expr := p.expr(0)
117	// println(typeof(db_expr))
118	p.check(.lcbr)
119	// kind := ast.SqlExprKind.select_
120	//
121	mut n := p.check_name() // insert
122	mut kind := ast.SqlStmtKind.insert
123	if n == 'delete' {
124		kind = .delete
125	} else if n == 'update' {
126		kind = .update
127	}
128	mut inserted_var_name := ''
129	mut table_name := ''
130	if kind != .delete {
131		expr := p.expr(0)
132		match expr {
133			ast.Ident {
134				if kind == .insert {
135					inserted_var_name = expr.name
136				} else if kind == .update {
137					table_name = expr.name
138				}
139			}
140			else {
141				p.error('can only insert variables')
142			}
143		}
144	}
145	n = p.check_name() // into
146	mut updated_columns := []string{}
147	mut update_exprs := []ast.Expr{cap: 5}
148	if kind == .insert && n != 'into' {
149		p.error('expecting `into`')
150	} else if kind == .update {
151		if n != 'set' {
152			p.error('expecting `set`')
153		}
154		for {
155			column := p.check_name()
156			updated_columns << column
157			p.check(.assign)
158			update_exprs << p.expr(0)
159			if p.tok.kind == .comma {
160				p.check(.comma)
161			} else {
162				break
163			}
164		}
165	} else if kind == .delete && n != 'from' {
166		p.error('expecting `from`')
167	}
168	mut table_type := table.Type(0)
169	mut where_expr := ast.Expr{}
170	if kind == .insert {
171		table_type = p.parse_type() // `User`
172		sym := p.table.get_type_symbol(table_type)
173		// info := sym.info as table.Struct
174		// fields := info.fields.filter(it.typ in [table.string_type, table.int_type, table.bool_type])
175		table_name = sym.name
176	} else if kind == .update {
177		if !p.pref.is_fmt {
178			// NB: in vfmt mode, v parses just a single file and table_name may not have been registered
179			idx := p.table.find_type_idx(p.prepend_mod(table_name))
180			table_type = table.new_type(idx)
181		}
182		p.check_sql_keyword('where')
183		where_expr = p.expr(0)
184	} else if kind == .delete {
185		table_type = p.parse_type()
186		sym := p.table.get_type_symbol(table_type)
187		table_name = sym.name
188		p.check_sql_keyword('where')
189		where_expr = p.expr(0)
190	}
191	p.check(.rcbr)
192	return ast.SqlStmt{
193		db_expr: db_expr
194		table_name: table_name
195		table_type: table_type
196		object_var_name: inserted_var_name
197		pos: pos
198		updated_columns: updated_columns
199		update_exprs: update_exprs
200		kind: kind
201		where_expr: where_expr
202	}
203}
204
205fn (mut p Parser) check_sql_keyword(name string) {
206	if p.check_name() != name {
207		p.error('orm: expecting `$name`')
208	}
209}
210