diff --git a/compiler/cgen.v b/compiler/cgen.v index 3395b21903..004e864c9b 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -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 } diff --git a/compiler/parser.v b/compiler/parser.v index 3b84f06346..eadf0cd039 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -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 { diff --git a/compiler/query.v b/compiler/query.v new file mode 100644 index 0000000000..3189329d7d --- /dev/null +++ b/compiler/query.v @@ -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)') +} + diff --git a/compiler/table.v b/compiler/table.v index e0660ca784..1873d27850 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -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' diff --git a/compiler/token.v b/compiler/token.v index dc69a0021c..3408bad969 100644 --- a/compiler/token.v +++ b/compiler/token.v @@ -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 } diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index a923454288..294fb73796 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -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++ diff --git a/vlib/orm/orm_test.v b/vlib/orm/orm_test.v index 6837a12417..c3efc4f6bd 100644 --- a/vlib/orm/orm_test.v +++ b/vlib/orm/orm_test.v @@ -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(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 diff --git a/vlib/pg/pg.v b/vlib/pg/pg.v index f4da925fa9..0a67dce859 100644 --- a/vlib/pg/pg.v +++ b/vlib/pg/pg.v @@ -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) } +