pull/1565/head
Alexander Medvednikov 2019-08-09 18:10:59 +02:00
parent c67783bcd1
commit 8f8e0dfad7
8 changed files with 256 additions and 44 deletions

View File

@ -143,26 +143,6 @@ fn (g mut CGen) set_placeholder(pos int, val string) {
// g.genln('')
}
fn (g mut CGen) add_placeholder2() int {
if g.is_tmp {
println('tmp in addp2')
exit(1)
}
g.lines << ''
return g.lines.len - 1
}
fn (g mut CGen) set_placeholder2(pos int, val string) {
if g.nogen || g.pass != .main {
return
}
if g.is_tmp {
println('tmp in setp2')
exit(1)
}
g.lines[pos] = val
}
fn (g mut CGen) insert_before(val string) {
prev := g.lines[g.lines.len - 1]
g.lines[g.lines.len - 1] = '$prev \n $val \n'
@ -240,7 +220,6 @@ fn (p mut Parser) gen_type_alias(s string) {
}
fn (g mut CGen) add_to_main(s string) {
println('add to main')
g.fn_main = g.fn_main + s
}

View File

@ -82,6 +82,7 @@ mut:
is_alloc bool // Whether current expression resulted in an allocation
cur_gen_type string // "App" to replace "T" in current generic function
is_vweb bool
is_sql bool
}
const (
@ -1360,7 +1361,16 @@ fn (p mut Parser) bool_expression() string {
got_or = true
if got_and { p.error(and_or_error) }
}
p.gen(' ${p.tok.str()} ')
if p.is_sql {
if p.tok == .and {
p.gen(' and ')
}
else if p.tok == .logical_or {
p.gen(' or ')
}
} else {
p.gen(' ${p.tok.str()} ')
}
p.check_space(p.tok)
p.check_types(p.bterm(), typ)
}
@ -1377,21 +1387,24 @@ fn (p mut Parser) bterm() string {
ph := p.cgen.add_placeholder()
mut typ := p.expression()
p.expected_type = typ
is_str := typ=='string'
is_str := typ=='string' && !p.is_sql
tok := p.tok
// if tok in [ .eq, .gt, .lt, .le, .ge, .ne] {
if tok == .eq || tok == .gt || tok == .lt || tok == .le || tok == .ge || tok == .ne {
if tok == .eq || (tok == .assign && p.is_sql) || tok == .gt || tok == .lt || tok == .le || tok == .ge || tok == .ne {
p.fgen(' ${p.tok.str()} ')
if is_str {
p.gen(',')
}
else if p.is_sql && tok == .eq {
p.gen('=')
}
else {
p.gen(tok.str())
}
p.next()
p.check_types(p.expression(), typ)
typ = 'bool'
if is_str {
if is_str { //&& !p.is_sql {
p.gen(')')
switch tok {
case Token.eq: p.cgen.set_placeholder(ph, 'string_eq(')
@ -1679,7 +1692,6 @@ fn (p mut Parser) var_expr(v Var) string {
p.gen(')')
typ = T.func.typ
}
// users[0] before dot so that we can have
// users[0].name
if p.tok == .lsbr {
typ = p.index_expr(typ, fn_ph)
@ -1687,6 +1699,15 @@ fn (p mut Parser) var_expr(v Var) string {
// a.b.c().d chain
// mut dc := 0
for p.tok ==.dot {
if p.peek() == .key_select {
p.next()
return p.select_query(fn_ph)
}
if typ == 'pg__DB' && !p.fileis('pg.v') {
p.next()
p.insert_query(fn_ph)
return 'void'
}
// println('dot #$dc')
typ = p.dot(typ, fn_ph)
p.log('typ after dot=$typ')
@ -1740,6 +1761,9 @@ fn (p &Parser) fileis(s string) bool {
// user.name => `str_typ` is `User`
// user.company.name => `str_typ` is `Company`
fn (p mut Parser) dot(str_typ string, method_ph int) string {
//if p.fileis('orm_test') {
//println('ORM dot $str_typ')
//}
p.check(.dot)
typ := p.find_type(str_typ)
if typ.name.len == 0 {
@ -1949,7 +1973,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
// TODO move this from index_expr()
// TODO if p.tok in ...
// if p.tok in [.assign, .plus_assign, .minus_assign]
if p.tok == .assign || p.tok == .plus_assign || p.tok == .minus_assign ||
if (p.tok == .assign && !p.is_sql) || p.tok == .plus_assign || p.tok == .minus_assign ||
p.tok == .mult_assign || p.tok == .div_assign || p.tok == .xor_assign || p.tok == .mod_assign ||
p.tok == .or_assign || p.tok == .and_assign || p.tok == .righ_shift_assign ||
p.tok == .left_shift_assign {
@ -2285,6 +2309,9 @@ fn (p mut Parser) factor() string {
if p.lit == 'json' && p.peek() == .dot {
return p.js_decode()
}
//if p.fileis('orm_test') {
//println('ORM name: $p.lit')
//}
typ = p.name_expr()
return typ
case Token.key_default:
@ -2414,6 +2441,9 @@ fn (p mut Parser) string_expr() {
if p.calling_c || (p.pref.translated && p.mod == 'main') {
p.gen('"$f"')
}
else if p.is_sql {
p.gen('\'$str\'')
}
else {
p.gen('tos2((byte*)"$f")')
}
@ -2457,6 +2487,9 @@ fn (p mut Parser) string_expr() {
if typ == 'ustring' {
args += '.len, ${val}.s.str'
}
if typ == 'bool' {
//args += '.len, ${val}.str'
}
// Custom format? ${t.hour:02d}
custom := p.tok == .colon
if custom {
@ -2910,9 +2943,9 @@ fn os_name_to_ifdef(name string) string {
fn (p mut Parser) if_st(is_expr bool, elif_depth int) string {
if is_expr {
if p.fileis('if_expr') {
println('IF EXPR')
}
//if p.fileis('if_expr') {
//println('IF EXPR')
//}
p.inside_if_expr = true
p.gen('(')
}
@ -3207,7 +3240,7 @@ fn (p mut Parser) switch_statement() {
if got_comma {
p.gen(') || ')
}
if typ == 'string' {
if typ == 'string' {
p.gen('string_eq($expr, ')
}
else {

152
compiler/query.v 100644
View File

@ -0,0 +1,152 @@
module main
import strings
// `db.select from User where id == 1 && nr_bookings > 0`
fn (p mut Parser) select_query(fn_ph int) string {
mut q := 'select '
p.check(.key_select)
n := p.check_name()
if n == 'count' {
q += 'count(*) from '
p.check_name()
}
table_name := p.check_name()
// Register this type's fields as variables so they can be used in where expressions
typ := p.table.find_type(table_name)
if typ.name == '' {
p.error('unknown type `$table_name`')
}
// 'select id, name, age from...'
if n == 'from' {
for i, field in typ.fields {
q += field.name
if i < typ.fields.len - 1 {
q += ', '
}
}
q += ' from '
}
for field in typ.fields {
//println('registering sql field var $field.name')
p.cur_fn.register_var({ field | is_used:true})
}
q += table_name
// `where` statement
if p.tok == .name && p.lit == 'where' {
p.next()
p.cgen.start_tmp()
p.is_sql = true
p.bool_expression()
p.is_sql = false
q += ' where ' + p.cgen.end_tmp()
}
// limit?
mut query_one := false
if p.tok == .name && p.lit == 'limit' {
p.next()
p.cgen.start_tmp()
p.is_sql = true
p.bool_expression()
p.is_sql = false
limit := p.cgen.end_tmp()
q += ' limit ' + limit
// `limit 1` means we are getting `User`, not `[]User`
if limit.trim_space() == '1' {
query_one = true
}
}
//println('sql query="$q"')
if n == 'count' {
p.cgen.set_placeholder(fn_ph, 'pg__DB_q_int(')
p.gen(', tos2("$q"))')
} else {
// Build an object, assign each field.
tmp := p.get_tmp()
mut obj_gen := strings.new_builder(100)
for i, field in typ.fields {
mut cast := ''
if field.typ == 'int' {
cast = 'string_int'
}
obj_gen.writeln('$tmp . $field.name = $cast( *(string*)array__get(row.vals, $i) );')
}
// One object
if query_one {
p.cgen.insert_before('
pg__Row row = pg__DB_exec_one(db, tos2("$q"));
$table_name $tmp;
${obj_gen.str()}
')
p.cgen.resetln(tmp)
}
// Array
else {
p.cgen.insert_before('
array_pg__Row rows = pg__DB_exec(db, tos2("$q"));
printf("ROWS LEN=%d\\n", rows.len);
// TODO preallocate
array arr_$tmp = new_array(0, 0, sizeof($table_name));
for (int i = 0; i < rows.len; i++) {
pg__Row row = *(pg__Row*)array__get(rows, i);
$table_name $tmp;
${obj_gen.str()}
_PUSH(&arr_$tmp, $tmp, ${tmp}2, $table_name);
}
')
p.cgen.resetln('arr_$tmp')
}
}
if n == 'count' {
return 'int'
} else if query_one {
return table_name
} else {
p.register_array('array_$table_name')
return 'array_$table_name'
}
}
// `db.insert(user)`
fn (p mut Parser) insert_query(fn_ph int) {
p.check_name()
p.check(.lpar)
var_name := p.check_name()
p.check(.rpar)
var := p.cur_fn.find_var(var_name)
typ := p.table.find_type(var.typ)
table_name := var.typ
mut fields := '' // 'name, city, country'
mut params := '' // params[0] = 'bob'; params[1] = 'Vienna';
mut vals := '' // $1, $2, $3...
mut nr_vals := 0
for i, field in typ.fields {
if field.name == 'id' {
continue
}
fields += field.name
vals += '$' + i.str()
nr_vals++
params += 'params[${i-1}] = '
if field.typ == 'string' {
params += '$var_name . $field.name .str;\n'
} else if field.typ == 'int' {
params += 'int_str($var_name . $field.name).str;\n'
} else {
p.error('V ORM: unsupported type `$field.typ`')
}
if i < typ.fields.len - 1 {
fields += ', '
vals += ', '
}
}
p.cgen.insert_before('char* params[$nr_vals];' + params)
p.cgen.set_placeholder(fn_ph, 'PQexecParams( ')
p.genln('.conn, "insert into $table_name ($fields) values ($vals)", $nr_vals,
0, params, 0, 0, 0)')
}

View File

@ -761,8 +761,9 @@ fn (p mut Parser) typ_to_fmt(typ string, level int) string {
}
switch typ {
case 'string': return '%.*s'
//case 'bool': return '%.*s'
case 'ustring': return '%.*s'
case 'byte', 'int', 'char', 'byte', 'bool', 'u32', 'i32', 'i16', 'u16', 'i8', 'u8': return '%d'
case 'byte', 'bool', 'int', 'char', 'byte', 'u32', 'i32', 'i16', 'u16', 'i8', 'u8': return '%d'
case 'f64', 'f32': return '%f'
case 'i64', 'u64': return '%lld'
case 'byte*', 'byteptr': return '%s'

View File

@ -97,6 +97,7 @@ enum Token {
key_module
key_mut
key_return
key_select
key_sizeof
key_struct
key_switch
@ -221,6 +222,7 @@ fn build_token_str() []string {
s[Token.key_as] = 'as'
s[Token.key_defer] = 'defer'
s[Token.key_match] = 'match'
s[Token.key_select] = 'select'
return s
}

View File

@ -533,17 +533,16 @@ pub fn (ar []int) contains(val int) bool {
return false
}
/*
/*
pub fn (a []string) to_c() voidptr {
char ** res = malloc(sizeof(char*) * a.len);
mut res := malloc(sizeof(byteptr) * a.len)
for i := 0; i < a.len; i++ {
val := a[i]
# res[i] = val.str;
res[i] = val.str
}
return res;
return 0
return res
}
*/
*/
fn is_space(c byte) bool {
return C.isspace(c)
@ -557,7 +556,6 @@ pub fn (s string) trim_space() string {
if s == '' {
return ''
}
// println('TRIM SPACE "$s"')
mut i := 0
for i < s.len && is_space(s[i]) {
i++

View File

@ -1,16 +1,46 @@
//import pg
import pg
struct Mod {
struct Modules {
id int
user_id int
name string
url string
nr_downloads int
//nr_downloads int
}
fn test_orm() {
/*
db := pg.connect('vpm', 'alex')
nr_modules := select count from db.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
println(nr_modules)
mod := db.select from Modules where id = 1 limit 1
println(mod)
mods := db.select from Modules limit 10
for mod in mods {
println(mod)
}
*/
/*
mod := db.retrieve<Module>(1)
mod := db.update Module set name = name + '!' where id > 10
nr_modules := db.select count from Modules
where id > 1 && name == ''
println(nr_modules)
nr_modules := db.select count from modules
nr_modules := db.select from modules
nr_modules := db[:modules].select
*/
/*
mod := select from db.modules where id = 1 limit 1
println(mod.name)
top_mods := select from db.modules where nr_downloads > 1000 order by nr_downloads desc limit 10

View File

@ -30,7 +30,8 @@ fn C.PQgetvalue(voidptr, int, int) byteptr
fn C.PQstatus(voidptr) int
pub fn connect(dbname, user string) DB {
conninfo := 'host=localhost user=$user dbname=$dbname'
//conninfo := 'host=localhost user=$user dbname=$dbname'
conninfo := 'host=127.0.0.1 user=$user dbname=$dbname'
conn:=C.PQconnectdb(conninfo.str)
status := C.PQstatus(conn)
if status != CONNECTION_OK {
@ -99,6 +100,21 @@ pub fn (db DB) exec(query string) []pg.Row {
return res_to_rows(res)
}
pub fn (db DB) exec_one(query string) pg.Row {
res := C.PQexec(db.conn, query.str)
e := string(C.PQerrorMessage(db.conn))
if e != '' {
println('pg exec error:')
println(e)
return Row{}
}
rows := res_to_rows(res)
if rows.len == 0 {
return Row{}
}
return rows[0]
}
//
pub fn (db DB) exec_param2(query string, param, param2 string) []pg.Row {
@ -122,3 +138,4 @@ pub fn (db DB) exec_param(query string, param string) []pg.Row {
return res_to_rows(res)
}