v orm: select
							parent
							
								
									23993d2264
								
							
						
					
					
						commit
						ed58192e4c
					
				| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import os
 | 
			
		||||
import pg
 | 
			
		||||
import term
 | 
			
		||||
//import os
 | 
			
		||||
//import pg
 | 
			
		||||
//import term
 | 
			
		||||
import sqlite
 | 
			
		||||
 | 
			
		||||
struct Modules {
 | 
			
		||||
| 
						 | 
				
			
			@ -11,30 +11,48 @@ struct Modules {
 | 
			
		|||
	//nr_downloads int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct User {
 | 
			
		||||
	id int
 | 
			
		||||
	name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn test_orm_sqlite() {
 | 
			
		||||
	db := sqlite.connect(':memory:') or { panic(err) }
 | 
			
		||||
	/*
 | 
			
		||||
	db.exec("drop table if exists users")
 | 
			
		||||
	db.exec("create table users (id integer primary key, name text default '');")
 | 
			
		||||
	db.exec("drop table if exists User")
 | 
			
		||||
	db.exec("create table User (id integer primary key, name text default '');")
 | 
			
		||||
 | 
			
		||||
	db.exec("insert into users (name) values ('Sam')")
 | 
			
		||||
	db.exec("insert into users (name) values ('Peter')")
 | 
			
		||||
	db.exec("insert into users (name) values ('Kate')")
 | 
			
		||||
	nr_users := sql db {
 | 
			
		||||
		//select count from modules
 | 
			
		||||
	name := 'sam'
 | 
			
		||||
 | 
			
		||||
	db.exec("insert into User (name) values ('Sam')")
 | 
			
		||||
	db.exec("insert into User (name) values ('Peter')")
 | 
			
		||||
	db.exec("insert into User (name) values ('Kate')")
 | 
			
		||||
	nr_all_users := sql db {
 | 
			
		||||
		select count from User
 | 
			
		||||
	}
 | 
			
		||||
	assert nr_users == 3
 | 
			
		||||
	println('nr_users=')
 | 
			
		||||
	println(nr_users)
 | 
			
		||||
	//nr_modules := db.select count from modules
 | 
			
		||||
	//nr_modules := db.select count from Modules where id == 1
 | 
			
		||||
	//nr_modules := db.select count from Modules where
 | 
			
		||||
		//name == 'Bob' && id == 1
 | 
			
		||||
	*/
 | 
			
		||||
	assert nr_all_users == 3
 | 
			
		||||
	println('nr_all_users=$nr_all_users')
 | 
			
		||||
	//
 | 
			
		||||
	nr_users1 := sql db {
 | 
			
		||||
		select count from User where id == 1
 | 
			
		||||
	}
 | 
			
		||||
	assert nr_users1 == 1
 | 
			
		||||
	println('nr_users1=$nr_users1')
 | 
			
		||||
	//
 | 
			
		||||
	nr_peters := sql db {
 | 
			
		||||
		select count from User where id == 2 && name == 'Peter'
 | 
			
		||||
	}
 | 
			
		||||
	assert nr_peters == 1
 | 
			
		||||
	println('nr_peters=$nr_peters')
 | 
			
		||||
	//
 | 
			
		||||
	nr_sams := sql db {
 | 
			
		||||
		select count from User where id == 1 && name == name
 | 
			
		||||
	}
 | 
			
		||||
	println('nr_sams=$nr_sams')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fn test_orm_pg() {
 | 
			
		||||
/*
 | 
			
		||||
	dbname := os.getenv('VDB_NAME')
 | 
			
		||||
	dbuser := os.getenv('VDB_USER')
 | 
			
		||||
	if dbname == '' || dbuser == '' {
 | 
			
		||||
| 
						 | 
				
			
			@ -43,8 +61,7 @@ fn test_orm_pg() {
 | 
			
		|||
	}
 | 
			
		||||
	db := pg.connect(dbname: dbname, user: dbuser) or { panic(err) }
 | 
			
		||||
	_ = db
 | 
			
		||||
/*
 | 
			
		||||
	//nr_modules := db.select count from modules
 | 
			
		||||
	nr_modules := db.select count from modules
 | 
			
		||||
	//nr_modules := db.select count from Modules where id == 1
 | 
			
		||||
	nr_modules := db.select count from Modules where
 | 
			
		||||
		name == 'Bob' && id == 1
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +78,7 @@ fn test_orm_pg() {
 | 
			
		|||
 | 
			
		||||
/*
 | 
			
		||||
	mod := db.retrieve<Module>(1)
 | 
			
		||||
	mod := db.select from Module where id = 1
 | 
			
		||||
 | 
			
		||||
	mod := db.update Module set name = name + '!' where id > 10
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,21 @@ pub fn connect(path string) ?DB {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Only for V ORM
 | 
			
		||||
fn (db DB) init_stmt(query string) &C.sqlite3_stmt {
 | 
			
		||||
	stmt := &C.sqlite3_stmt(0)
 | 
			
		||||
	C.sqlite3_prepare_v2(db.conn, query.str, -1, &stmt, 0)
 | 
			
		||||
	return stmt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Only for V ORM
 | 
			
		||||
fn get_int_from_stmt(stmt &C.sqlite3_stmt) int {
 | 
			
		||||
	C.sqlite3_step(stmt)
 | 
			
		||||
	res := C.sqlite3_column_int(stmt, 0)
 | 
			
		||||
	C.sqlite3_finalize(stmt)
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns a single cell with value int.
 | 
			
		||||
pub fn (db DB) q_int(query string) int {
 | 
			
		||||
	stmt := &C.sqlite3_stmt(0)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,24 @@ pub fn (mut b Builder) go_back(n int) {
 | 
			
		|||
	b.len -= n
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut b Builder) cut_last(n int) string {
 | 
			
		||||
	buf := b.buf[b.len-n..]
 | 
			
		||||
	s := string(buf.clone())
 | 
			
		||||
	b.buf.trim(b.buf.len-n)
 | 
			
		||||
	b.len -= n
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
pub fn (mut b Builder) cut_to(pos int) string {
 | 
			
		||||
	buf := b.buf[pos..]
 | 
			
		||||
	s := string(buf.clone())
 | 
			
		||||
	b.buf.trim(pos)
 | 
			
		||||
	b.len = pos
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pub fn (mut b Builder) go_back_to(pos int) {
 | 
			
		||||
	b.buf.trim(pos)
 | 
			
		||||
	b.len = pos
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,11 +8,25 @@ fn test_sb() {
 | 
			
		|||
	assert sb.len == 8
 | 
			
		||||
	assert sb.str() == 'hi!hello'
 | 
			
		||||
	assert sb.len == 0
 | 
			
		||||
	///
 | 
			
		||||
	sb = strings.new_builder(10)
 | 
			
		||||
	sb.write('a')
 | 
			
		||||
	sb.write('b')
 | 
			
		||||
	assert sb.len == 2
 | 
			
		||||
	assert sb.str() == 'ab'
 | 
			
		||||
	///
 | 
			
		||||
	sb = strings.new_builder(10)
 | 
			
		||||
	sb.write('123456')
 | 
			
		||||
	assert sb.cut_last(2) == '56'
 | 
			
		||||
	assert sb.str() == '1234'
 | 
			
		||||
	///
 | 
			
		||||
	/*
 | 
			
		||||
	sb = strings.new_builder(10)
 | 
			
		||||
	sb.write('123456')
 | 
			
		||||
	x := sb.cut_to(2)
 | 
			
		||||
	assert x == '456'
 | 
			
		||||
	assert sb.str() == '123'
 | 
			
		||||
	*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,6 +61,10 @@ pub fn header(text, divider string) string {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
fn supports_escape_sequences(fd int) bool {
 | 
			
		||||
	//println('TERM=' + os.getenv('TERM'))
 | 
			
		||||
	if os.getenv('TERM') == 'dumb' {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	vcolors_override := os.getenv('VCOLORS')
 | 
			
		||||
	if vcolors_override == 'always' {
 | 
			
		||||
		return true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -801,7 +801,13 @@ pub:
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
pub struct SqlExpr {
 | 
			
		||||
	typ table.Type
 | 
			
		||||
pub:
 | 
			
		||||
	typ         table.Type
 | 
			
		||||
	is_count    bool
 | 
			
		||||
	db_var_name string // `db` in `sql db {`
 | 
			
		||||
	table_name  string
 | 
			
		||||
	where_expr Expr
 | 
			
		||||
	has_where bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[inline]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -556,7 +556,8 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
 | 
			
		|||
				c.error('$infix_expr.op.str(): type `${typ_sym.name}` does not exist', type_expr.pos)
 | 
			
		||||
			}
 | 
			
		||||
			if left.kind != .interface_ && left.kind != .sum_type {
 | 
			
		||||
				c.error('`$infix_expr.op.str()` can only be used with interfaces and sum types', type_expr.pos)
 | 
			
		||||
				c.error('`$infix_expr.op.str()` can only be used with interfaces and sum types',
 | 
			
		||||
					type_expr.pos)
 | 
			
		||||
			}
 | 
			
		||||
			return table.bool_type
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -681,9 +682,12 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e
 | 
			
		|||
		ast.AnonFn {
 | 
			
		||||
			if it.decl.args.len > 1 {
 | 
			
		||||
				c.error('function needs exactly 1 argument', call_expr.pos)
 | 
			
		||||
			} else if is_map && (it.decl.return_type != elem_typ || it.decl.args[0].typ != elem_typ) {
 | 
			
		||||
				c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`', call_expr.pos)
 | 
			
		||||
			} else if !is_map && (it.decl.return_type != table.bool_type || it.decl.args[0].typ != elem_typ) {
 | 
			
		||||
			} else if is_map && (it.decl.return_type != elem_typ || it.decl.args[0].typ !=
 | 
			
		||||
				elem_typ) {
 | 
			
		||||
				c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`',
 | 
			
		||||
					call_expr.pos)
 | 
			
		||||
			} else if !is_map && (it.decl.return_type != table.bool_type || it.decl.args[0].typ !=
 | 
			
		||||
				elem_typ) {
 | 
			
		||||
				c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`', call_expr.pos)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -696,9 +700,12 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e
 | 
			
		|||
				if func.args.len > 1 {
 | 
			
		||||
					c.error('function needs exactly 1 argument', call_expr.pos)
 | 
			
		||||
				} else if is_map && (func.return_type != elem_typ || func.args[0].typ != elem_typ) {
 | 
			
		||||
					c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`', call_expr.pos)
 | 
			
		||||
				} else if !is_map && (func.return_type != table.bool_type || func.args[0].typ != elem_typ) {
 | 
			
		||||
					c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`', call_expr.pos)
 | 
			
		||||
					c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`',
 | 
			
		||||
						call_expr.pos)
 | 
			
		||||
				} else if !is_map && (func.return_type != table.bool_type || func.args[0].typ !=
 | 
			
		||||
					elem_typ) {
 | 
			
		||||
					c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
 | 
			
		||||
						call_expr.pos)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1948,15 +1955,29 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
 | 
			
		|||
		ast.None {
 | 
			
		||||
			return table.none_type
 | 
			
		||||
		}
 | 
			
		||||
		ast.OrExpr {
 | 
			
		||||
			// never happens
 | 
			
		||||
			return table.void_type
 | 
			
		||||
		}
 | 
			
		||||
		ast.ParExpr {
 | 
			
		||||
			return c.expr(it.expr)
 | 
			
		||||
		}
 | 
			
		||||
		ast.RangeExpr {
 | 
			
		||||
			// never happens
 | 
			
		||||
			return table.void_type
 | 
			
		||||
		}
 | 
			
		||||
		ast.SelectorExpr {
 | 
			
		||||
			return c.selector_expr(mut it)
 | 
			
		||||
		}
 | 
			
		||||
		ast.SizeOf {
 | 
			
		||||
			return table.u32_type
 | 
			
		||||
		}
 | 
			
		||||
		ast.SqlExpr {
 | 
			
		||||
			if it.has_where {
 | 
			
		||||
				c.expr(it.where_expr)
 | 
			
		||||
			}
 | 
			
		||||
			return it.typ
 | 
			
		||||
		}
 | 
			
		||||
		ast.StringLiteral {
 | 
			
		||||
			if it.language == .c {
 | 
			
		||||
				return table.byteptr_type
 | 
			
		||||
| 
						 | 
				
			
			@ -1986,12 +2007,6 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
 | 
			
		|||
			}
 | 
			
		||||
			return table.bool_type
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			tnode := typeof(node)
 | 
			
		||||
			if tnode != 'unknown v.ast.Expr' {
 | 
			
		||||
				println('checker.expr(): unhandled node with typeof(`${tnode}`)')
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return table.void_type
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,6 +66,7 @@ mut:
 | 
			
		|||
	options              strings.Builder // `Option_xxxx` types
 | 
			
		||||
	json_forward_decls   strings.Builder // json type forward decls
 | 
			
		||||
	enum_typedefs        strings.Builder // enum types
 | 
			
		||||
	sql_buf              strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc
 | 
			
		||||
	file                 ast.File
 | 
			
		||||
	fn_decl              &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
 | 
			
		||||
	last_fn_c_name       string
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +77,7 @@ mut:
 | 
			
		|||
	is_assign_rhs        bool // inside right part of assign after `=` (val expr)
 | 
			
		||||
	is_array_set         bool
 | 
			
		||||
	is_amp               bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc
 | 
			
		||||
	is_sql               bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc)
 | 
			
		||||
	optionals            []string // to avoid duplicates TODO perf, use map
 | 
			
		||||
	inside_ternary       int // ?: comma separated statements on a single line
 | 
			
		||||
	ternary_names        map[string]string
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +104,8 @@ mut:
 | 
			
		|||
	fn_main              &ast.FnDecl // the FnDecl of the main function. Needed in order to generate the main function code *last*
 | 
			
		||||
	cur_fn               &ast.FnDecl
 | 
			
		||||
	cur_generic_type     table.Type // `int`, `string`, etc in `foo<T>()`
 | 
			
		||||
	sql_i                int
 | 
			
		||||
	sql_stmt_name        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
| 
						 | 
				
			
			@ -131,6 +135,7 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
 | 
			
		|||
		options: strings.new_builder(100)
 | 
			
		||||
		json_forward_decls: strings.new_builder(100)
 | 
			
		||||
		enum_typedefs: strings.new_builder(100)
 | 
			
		||||
		sql_buf: strings.new_builder(100)
 | 
			
		||||
		table: table
 | 
			
		||||
		pref: pref
 | 
			
		||||
		fn_decl: 0
 | 
			
		||||
| 
						 | 
				
			
			@ -1544,7 +1549,7 @@ fn (mut g Gen) expr(node ast.Expr) {
 | 
			
		|||
		}
 | 
			
		||||
		ast.RangeExpr {
 | 
			
		||||
			// Only used in IndexExpr
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ast.SizeOf {
 | 
			
		||||
			mut styp := it.type_name
 | 
			
		||||
			if it.type_name == '' {
 | 
			
		||||
| 
						 | 
				
			
			@ -1566,7 +1571,7 @@ fn (mut g Gen) expr(node ast.Expr) {
 | 
			
		|||
			g.write('sizeof($styp)')
 | 
			
		||||
		}
 | 
			
		||||
		ast.SqlExpr {
 | 
			
		||||
			g.write('// sql expression')
 | 
			
		||||
			g.sql_expr(it)
 | 
			
		||||
		}
 | 
			
		||||
		ast.StringLiteral {
 | 
			
		||||
			if it.is_raw {
 | 
			
		||||
| 
						 | 
				
			
			@ -1630,10 +1635,6 @@ fn (mut g Gen) expr(node ast.Expr) {
 | 
			
		|||
			g.expr(it.expr)
 | 
			
		||||
			g.write(')')
 | 
			
		||||
		}
 | 
			
		||||
		//else {
 | 
			
		||||
			// #printf("node=%d\n", node.typ);
 | 
			
		||||
			//println(term.red('cgen.expr(): bad node ' + typeof(node)))
 | 
			
		||||
		//}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3173,6 +3174,15 @@ fn (mut g Gen) insert_before_stmt(s string) {
 | 
			
		|||
	g.write(cur_line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) write_expr_to_string(expr ast.Expr) string {
 | 
			
		||||
	pos := g.out.buf.len
 | 
			
		||||
	g.expr(expr)
 | 
			
		||||
	return g.out.cut_last(g.out.buf.len - pos)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) start_tmp() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// If user is accessing the return value eg. in assigment, pass the variable name.
 | 
			
		||||
// If the user is not using the optional return value. We need to pass a temp var
 | 
			
		||||
// to access its fields (`.ok`, `.error` etc)
 | 
			
		||||
| 
						 | 
				
			
			@ -3263,7 +3273,6 @@ fn (mut g Gen) in_optimization(left ast.Expr, right ast.ArrayInit) {
 | 
			
		|||
			ptr_typ := g.gen_array_equality_fn(right.elem_type)
 | 
			
		||||
			g.write('${ptr_typ}_arr_eq(')
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		g.expr(left)
 | 
			
		||||
		if is_str || is_array {
 | 
			
		||||
			g.write(', ')
 | 
			
		||||
| 
						 | 
				
			
			@ -4208,7 +4217,6 @@ fn (mut g Gen) array_init(it ast.ArrayInit) {
 | 
			
		|||
	if it.exprs.len == 0 {
 | 
			
		||||
		elem_sym := g.table.get_type_symbol(it.elem_type)
 | 
			
		||||
		is_default_array := elem_sym.kind == .array && it.has_default
 | 
			
		||||
 | 
			
		||||
		if is_default_array {
 | 
			
		||||
			g.write('__new_array_with_array_default(')
 | 
			
		||||
		} else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
// Copyright (c) 2019-2020 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 gen
 | 
			
		||||
 | 
			
		||||
import v.ast
 | 
			
		||||
import strings
 | 
			
		||||
 | 
			
		||||
// pg,mysql etc
 | 
			
		||||
const (
 | 
			
		||||
	dbtype = 'sqlite'
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) sql_expr(node ast.SqlExpr) {
 | 
			
		||||
	g.sql_i = 0
 | 
			
		||||
	/*
 | 
			
		||||
	`nr_users := sql db { ... }` =>
 | 
			
		||||
	```
 | 
			
		||||
	sql_init_stmt()
 | 
			
		||||
	sql_bind_int()
 | 
			
		||||
	sql_bind_string()
 | 
			
		||||
	...
 | 
			
		||||
	int nr_users = get_int(stmt)
 | 
			
		||||
	```
 | 
			
		||||
	*/
 | 
			
		||||
	cur_line := g.go_before_stmt(0)
 | 
			
		||||
	mut q := 'select '
 | 
			
		||||
	if node.is_count {
 | 
			
		||||
		// select count(*) from User
 | 
			
		||||
		q += 'count(*) from $node.table_name'
 | 
			
		||||
	}
 | 
			
		||||
	if node.has_where {
 | 
			
		||||
		q += ' where '
 | 
			
		||||
	}
 | 
			
		||||
	// g.write('${dbtype}__DB_q_int(*(${dbtype}__DB*)${node.db_var_name}.data, tos_lit("$q')
 | 
			
		||||
	g.sql_stmt_name = g.new_tmp_var()
 | 
			
		||||
	db_name := g.new_tmp_var()
 | 
			
		||||
	g.writeln('\n\t// sql')
 | 
			
		||||
	g.write('${dbtype}__DB $db_name = *(${dbtype}__DB*)${node.db_var_name}.data;')
 | 
			
		||||
	g.write('sqlite3_stmt* $g.sql_stmt_name = ${dbtype}__DB_init_stmt(*(${dbtype}__DB*)${node.db_var_name}.data, tos_lit("$q')
 | 
			
		||||
	if node.has_where && node.where_expr is ast.InfixExpr {
 | 
			
		||||
		g.expr_to_sql(node.where_expr)
 | 
			
		||||
	}
 | 
			
		||||
	g.writeln('"));')
 | 
			
		||||
	// Dump all sql parameters generated by our custom expr handler
 | 
			
		||||
	binds := g.sql_buf.str()
 | 
			
		||||
	g.sql_buf = strings.new_builder(100)
 | 
			
		||||
	g.writeln(binds)
 | 
			
		||||
	g.writeln('puts(sqlite3_errmsg(${db_name}.conn));')
 | 
			
		||||
	g.writeln('$cur_line ${dbtype}__get_int_from_stmt($g.sql_stmt_name);')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) expr_to_sql(expr ast.Expr) {
 | 
			
		||||
	// Custom handling for infix exprs (since we need e.g. `and` instead of `&&` in SQL queries),
 | 
			
		||||
	// strings. Everything else (like numbers, a.b) is handled by g.expr()
 | 
			
		||||
	//
 | 
			
		||||
	// TODO `where id = some_column + 1` needs literal generation of `some_column` as a string,
 | 
			
		||||
	// not a V variable. Need to distinguish column names from V variables.
 | 
			
		||||
	match expr {
 | 
			
		||||
		ast.InfixExpr {
 | 
			
		||||
			g.expr_to_sql(it.left)
 | 
			
		||||
			match it.op {
 | 
			
		||||
				.eq { g.write(' = ') }
 | 
			
		||||
				.and { g.write(' and ') }
 | 
			
		||||
				else {}
 | 
			
		||||
			}
 | 
			
		||||
			g.expr_to_sql(it.right)
 | 
			
		||||
		}
 | 
			
		||||
		ast.StringLiteral {
 | 
			
		||||
			// g.write("'$it.val'")
 | 
			
		||||
			g.inc_sql_i()
 | 
			
		||||
			g.sql_buf.writeln('sqlite3_bind_text($g.sql_stmt_name, $g.sql_i, "$it.val", $it.val.len, 0);')
 | 
			
		||||
		}
 | 
			
		||||
		ast.IntegerLiteral {
 | 
			
		||||
			g.inc_sql_i()
 | 
			
		||||
			g.sql_buf.writeln('sqlite3_bind_int($g.sql_stmt_name, $g.sql_i, $it.val);')
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			g.expr(expr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	/*
 | 
			
		||||
	ast.Ident {
 | 
			
		||||
			g.write('$it.name')
 | 
			
		||||
		}
 | 
			
		||||
		else {}
 | 
			
		||||
	*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) inc_sql_i() {
 | 
			
		||||
	g.sql_i++
 | 
			
		||||
	g.write('?$g.sql_i')
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,76 @@
 | 
			
		|||
module parser
 | 
			
		||||
 | 
			
		||||
import v.ast
 | 
			
		||||
import v.table
 | 
			
		||||
 | 
			
		||||
fn (mut p Parser) sql_expr() ast.SqlExpr {
 | 
			
		||||
	return ast.SqlExpr{}
 | 
			
		||||
	// `sql db {`
 | 
			
		||||
	p.check_name()
 | 
			
		||||
	db_var_name := p.check_name()
 | 
			
		||||
	p.check(.lcbr)
 | 
			
		||||
	//
 | 
			
		||||
	p.check(.key_select)
 | 
			
		||||
	n := p.check_name()
 | 
			
		||||
	is_count := n == 'count'
 | 
			
		||||
	mut typ := table.void_type
 | 
			
		||||
	if is_count {
 | 
			
		||||
		p.check_name() // from
 | 
			
		||||
		typ = table.int_type
 | 
			
		||||
	}
 | 
			
		||||
	table_type := p.parse_type() // `User`
 | 
			
		||||
	sym := p.table.get_type_symbol(table_type)
 | 
			
		||||
	table_name := sym.name
 | 
			
		||||
	mut where_expr := ast.Expr{}
 | 
			
		||||
	has_where := p.tok.kind == .name && p.tok.lit == 'where'
 | 
			
		||||
	if has_where {
 | 
			
		||||
		p.next()
 | 
			
		||||
		where_expr = p.expr(0)
 | 
			
		||||
	}
 | 
			
		||||
	p.check(.rcbr)
 | 
			
		||||
	// /////////
 | 
			
		||||
	// Register this type's fields as variables so they can be used in `where`
 | 
			
		||||
	// expressions
 | 
			
		||||
	// fields := typ.fields.filter(typ == 'string' || typ == 'int')
 | 
			
		||||
	// fields := typ.fields
 | 
			
		||||
	// get only string and int fields
 | 
			
		||||
	// mut fields := []Var
 | 
			
		||||
	info := sym.info as table.Struct
 | 
			
		||||
	fields := info.fields.filter(it.typ in [table.string_type, table.int_type, table.bool_type])
 | 
			
		||||
	/*
 | 
			
		||||
	for i, field in info.fields {
 | 
			
		||||
        if !(field.typ in ['string', 'int', 'bool']) {
 | 
			
		||||
                println('orm: skipping $field.name')
 | 
			
		||||
                continue
 | 
			
		||||
        }
 | 
			
		||||
        if field.attr.contains('skip') {
 | 
			
		||||
                continue
 | 
			
		||||
        }
 | 
			
		||||
        fields << field
 | 
			
		||||
}
 | 
			
		||||
	*/
 | 
			
		||||
	if fields.len == 0 {
 | 
			
		||||
		p.error('V orm: select: empty fields in `$table_name`')
 | 
			
		||||
	}
 | 
			
		||||
	if fields[0].name != 'id' {
 | 
			
		||||
		p.error('V orm: `id int` must be the first field in `$table_name`')
 | 
			
		||||
	}
 | 
			
		||||
	for field in fields {
 | 
			
		||||
		// println('registering sql field var $field.name')
 | 
			
		||||
		p.scope.register(field.name, ast.Var{
 | 
			
		||||
			name: field.name
 | 
			
		||||
			typ: field.typ
 | 
			
		||||
			is_mut: true
 | 
			
		||||
			is_used: true
 | 
			
		||||
			is_changed: true
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	// ////////////
 | 
			
		||||
	return ast.SqlExpr{
 | 
			
		||||
		is_count: is_count
 | 
			
		||||
		typ: typ
 | 
			
		||||
		db_var_name: db_var_name
 | 
			
		||||
		table_name: table_name
 | 
			
		||||
		where_expr: where_expr
 | 
			
		||||
		has_where: has_where
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue