From faf2656335446ffc1c2b2ca93b0b9adb1db932e6 Mon Sep 17 00:00:00 2001 From: Louis Schmieder Date: Tue, 27 Apr 2021 14:28:57 +0200 Subject: [PATCH] orm: support multiline statements (#9888) --- .../code/blog/blog.v | 1 + vlib/orm/orm_test.v | 31 +++++- vlib/v/ast/ast.v | 11 ++- vlib/v/checker/checker.v | 20 +++- vlib/v/fmt/fmt.v | 10 +- vlib/v/gen/c/sql.v | 95 ++++++++++--------- vlib/v/markused/walker.v | 6 +- vlib/v/parser/sql.v | 51 ++++++---- 8 files changed, 146 insertions(+), 79 deletions(-) diff --git a/tutorials/building_a_simple_web_blog_with_vweb/code/blog/blog.v b/tutorials/building_a_simple_web_blog_with_vweb/code/blog/blog.v index 6e56e59b81..fa96c19afe 100644 --- a/tutorials/building_a_simple_web_blog_with_vweb/code/blog/blog.v +++ b/tutorials/building_a_simple_web_blog_with_vweb/code/blog/blog.v @@ -65,6 +65,7 @@ pub fn (mut app App) new_article() vweb.Result { sql app.db { insert article into Article } + return app.redirect('/') } diff --git a/vlib/orm/orm_test.v b/vlib/orm/orm_test.v index 9077f9cc42..d33da8ccbf 100644 --- a/vlib/orm/orm_test.v +++ b/vlib/orm/orm_test.v @@ -28,10 +28,30 @@ fn test_orm_sqlite() { sql db { create table User } + name := 'Peter' - db.exec("insert into userlist (username, age) values ('Sam', 29)") - db.exec("insert into userlist (username, age) values ('Peter', 31)") - db.exec("insert into userlist (username, age, is_customer) values ('Kate', 30, 1)") + + sam := User{ + age: 29 + name: 'Sam' + } + + peter := User{ + age: 31 + name: 'Peter' + } + + k := User{ + age: 30 + name: 'Kate' + is_customer: true + } + + sql db { + insert sam into User + insert peter into User + insert k into User + } c := sql db { select count from User where id != 1 @@ -113,6 +133,7 @@ fn test_orm_sqlite() { sql db { insert new_user into User } + // db.insert(user2) x := sql db { select from User where id == 4 @@ -136,6 +157,7 @@ fn test_orm_sqlite() { sql db { update User set age = 31 where name == 'Kate' } + kate2 := sql db { select from User where id == 3 } @@ -167,6 +189,7 @@ fn test_orm_sqlite() { sql db { update User set age = new_age, name = 'Kate N' where id == 3 } + kate3 = sql db { select from User where id == 3 } @@ -177,6 +200,7 @@ fn test_orm_sqlite() { sql db { update User set age = foo.age, name = 'Kate N' where id == 3 } + kate3 = sql db { select from User where id == 3 } @@ -219,6 +243,7 @@ fn test_orm_sqlite() { sql db { delete from User where age == 34 } + updated_oldest := sql db { select from User order by age desc limit 1 } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 29ad8f8918..02adc4f34d 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1439,9 +1439,16 @@ pub enum SqlStmtKind { } pub struct SqlStmt { +pub: + pos token.Position + db_expr Expr // `db` in `sql db {` +pub mut: + lines []SqlStmtLine +} + +pub struct SqlStmtLine { pub: kind SqlStmtKind - db_expr Expr // `db` in `sql db {` object_var_name string // `user` pos token.Position where_expr Expr @@ -1450,7 +1457,7 @@ pub: pub mut: table_expr TypeNode fields []StructField - sub_structs map[int]SqlStmt + sub_structs map[int]SqlStmtLine } pub struct SqlExpr { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f9d2e14c3f..5e4aaf18db 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -6488,6 +6488,18 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { } fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type { + c.expr(node.db_expr) + mut typ := ast.void_type + for mut line in node.lines { + a := c.sql_stmt_line(mut line) + if a != ast.void_type { + typ = a + } + } + return typ +} + +fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { c.inside_sql = true defer { c.inside_sql = false @@ -6501,11 +6513,10 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type { } info := table_sym.info as ast.Struct fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name) - mut sub_structs := map[int]ast.SqlStmt{} + mut sub_structs := map[int]ast.SqlStmtLine{} for f in fields.filter(c.table.type_symbols[int(it.typ)].kind == .struct_) { - mut n := ast.SqlStmt{ + mut n := ast.SqlStmtLine{ pos: node.pos - db_expr: node.db_expr kind: node.kind table_expr: ast.TypeNode{ pos: node.table_expr.pos @@ -6514,13 +6525,12 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type { object_var_name: '${node.object_var_name}.$f.name' } tmp_inside_sql := c.inside_sql - c.sql_stmt(mut n) + c.sql_stmt_line(mut n) c.inside_sql = tmp_inside_sql sub_structs[int(f.typ)] = n } node.fields = fields node.sub_structs = sub_structs.move() - c.expr(node.db_expr) if node.kind == .update { for expr in node.update_exprs { c.expr(expr) diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 8d84b63233..11580ac793 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1215,6 +1215,15 @@ pub fn (mut f Fmt) sql_stmt(node ast.SqlStmt) { f.write('sql ') f.expr(node.db_expr) f.writeln(' {') + + for line in node.lines { + f.sql_stmt_line(line) + } + + f.writeln('}') +} + +pub fn (mut f Fmt) sql_stmt_line(node ast.SqlStmtLine) { table_name := util.strip_mod_name(f.table.get_type_symbol(node.table_expr.typ).name) f.write('\t') match node.kind { @@ -1249,7 +1258,6 @@ pub fn (mut f Fmt) sql_stmt(node ast.SqlStmt) { f.writeln('drop table $table_name') } } - f.writeln('}') } pub fn (mut f Fmt) type_decl(node ast.TypeDecl) { diff --git a/vlib/v/gen/c/sql.v b/vlib/v/gen/c/sql.v index ca877a5a7d..de12f6557f 100644 --- a/vlib/v/gen/c/sql.v +++ b/vlib/v/gen/c/sql.v @@ -24,24 +24,30 @@ enum SqlType { } fn (mut g Gen) sql_stmt(node ast.SqlStmt) { + for line in node.lines { + g.sql_stmt_line(line, node.db_expr) + } +} + +fn (mut g Gen) sql_stmt_line(node ast.SqlStmtLine, expr ast.Expr) { if node.kind == .create { - g.sql_create_table(node) + g.sql_create_table(node, expr) return } else if node.kind == .drop { - g.sql_drop_table(node) + g.sql_drop_table(node, expr) return } g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name - typ := g.parse_db_type(node.db_expr) + typ := g.parse_db_type(expr) match typ { .sqlite3 { - g.sqlite3_stmt(node, typ) + g.sqlite3_stmt(node, typ, expr) } .mysql { - g.mysql_stmt(node, typ) + g.mysql_stmt(node, typ, expr) } .psql { - g.psql_stmt(node, typ) + g.psql_stmt(node, typ, expr) } else { verror('This database type `$typ` is not implemented yet in orm') // TODO add better error @@ -49,17 +55,17 @@ fn (mut g Gen) sql_stmt(node ast.SqlStmt) { } } -fn (mut g Gen) sql_create_table(node ast.SqlStmt) { - typ := g.parse_db_type(node.db_expr) +fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr ast.Expr) { + typ := g.parse_db_type(expr) match typ { .sqlite3 { - g.sqlite3_create_table(node, typ) + g.sqlite3_create_table(node, typ, expr) } .mysql { - g.mysql_create_table(node, typ) + g.mysql_create_table(node, typ, expr) } .psql { - g.psql_create_table(node, typ) + g.psql_create_table(node, typ, expr) } else { verror('This database type `$typ` is not implemented yet in orm') // TODO add better error @@ -67,17 +73,17 @@ fn (mut g Gen) sql_create_table(node ast.SqlStmt) { } } -fn (mut g Gen) sql_drop_table(node ast.SqlStmt) { - typ := g.parse_db_type(node.db_expr) +fn (mut g Gen) sql_drop_table(node ast.SqlStmtLine, expr ast.Expr) { + typ := g.parse_db_type(expr) match typ { .sqlite3 { - g.sqlite3_drop_table(node, typ) + g.sqlite3_drop_table(node, typ, expr) } .mysql { - g.mysql_drop_table(node, typ) + g.mysql_drop_table(node, typ, expr) } .psql { - g.psql_create_table(node, typ) + g.psql_create_table(node, typ, expr) } else { verror('This database type `$typ` is not implemented yet in orm') // TODO add better error @@ -136,13 +142,13 @@ fn (mut g Gen) sql_type_from_v(typ SqlType, v_typ ast.Type) string { // sqlite3 -fn (mut g Gen) sqlite3_stmt(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) sqlite3_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.sql_i = 0 g.writeln('\n\t// sql insert') db_name := g.new_tmp_var() g.sql_stmt_name = g.new_tmp_var() g.write('${c.dbtype}__DB $db_name = ') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(';') g.write('sqlite3_stmt* $g.sql_stmt_name = ${c.dbtype}__DB_init_stmt($db_name, _SLIT("') g.sql_defaults(node, typ) @@ -161,7 +167,7 @@ fn (mut g Gen) sqlite3_stmt(node ast.SqlStmt, typ SqlType) { expr := node.sub_structs[int(field.typ)] tmp_sql_stmt_name := g.sql_stmt_name tmp_sql_table_name := g.sql_table_name - g.sql_stmt(expr) + g.sql_stmt_line(expr, db_expr) g.sql_stmt_name = tmp_sql_stmt_name g.sql_table_name = tmp_sql_table_name // get last inserted id @@ -326,20 +332,20 @@ fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_ } } -fn (mut g Gen) sqlite3_create_table(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) sqlite3_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.writeln('// sqlite3 table creator') - create_string := g.table_gen(node, typ) + create_string := g.table_gen(node, typ, db_expr) g.write('sqlite__DB_exec(') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(', _SLIT("$create_string"));') } -fn (mut g Gen) sqlite3_drop_table(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) sqlite3_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { table_name := g.get_table_name(node.table_expr) g.writeln('// sqlite3 table drop') drop_string := 'DROP TABLE `$table_name`;' g.write('sqlite__DB_exec(') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(', _SLIT("$drop_string"));') } @@ -377,13 +383,13 @@ fn (mut g Gen) sqlite3_type_from_v(v_typ ast.Type) string { // mysql -fn (mut g Gen) mysql_stmt(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) mysql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.sql_i = 0 g.writeln('\n\t//mysql insert') db_name := g.new_tmp_var() g.sql_stmt_name = g.new_tmp_var() g.write('mysql__Connection $db_name = ') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(';') stmt_name := g.new_tmp_var() g.write('string $stmt_name = _SLIT("') @@ -407,7 +413,7 @@ fn (mut g Gen) mysql_stmt(node ast.SqlStmt, typ SqlType) { expr := node.sub_structs[int(field.typ)] tmp_sql_stmt_name := g.sql_stmt_name tmp_sql_table_name := g.sql_table_name - g.sql_stmt(expr) + g.sql_stmt_line(expr, db_expr) g.sql_stmt_name = tmp_sql_stmt_name g.sql_table_name = tmp_sql_table_name @@ -618,23 +624,23 @@ fn (mut g Gen) mysql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sq } } -fn (mut g Gen) mysql_create_table(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) mysql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.writeln('// mysql table creator') - create_string := g.table_gen(node, typ) + create_string := g.table_gen(node, typ, db_expr) tmp := g.new_tmp_var() g.write('Option_mysql__Result $tmp = mysql__Connection_query(&') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(', _SLIT("$create_string"));') g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') } -fn (mut g Gen) mysql_drop_table(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) mysql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { table_name := g.get_table_name(node.table_expr) g.writeln('// mysql table drop') drop_string := 'DROP TABLE `$table_name`;' tmp := g.new_tmp_var() g.write('Option_mysql__Result $tmp = mysql__Connection_query(&') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(', _SLIT("$drop_string"));') g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') } @@ -735,7 +741,7 @@ fn (mut g Gen) mysql_buffer_typ_from_field(field ast.StructField) (string, strin // psql -fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.sql_i = 0 g.sql_idents = []string{} param_values := g.new_tmp_var() @@ -745,7 +751,7 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) { db_name := g.new_tmp_var() g.sql_stmt_name = g.new_tmp_var() g.write('pg__DB $db_name = ') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(';') stmt_name := g.new_tmp_var() g.write('string $stmt_name = _SLIT("') @@ -769,7 +775,7 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) { expr := node.sub_structs[int(field.typ)] tmp_sql_stmt_name := g.sql_stmt_name tmp_sql_table_name := g.sql_table_name - g.sql_stmt(expr) + g.sql_stmt_line(expr, db_expr) g.sql_stmt_name = tmp_sql_stmt_name g.sql_table_name = tmp_sql_table_name @@ -803,23 +809,23 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) { g.writeln('if (${res}_rows.state != 0) { IError err = ${res}_rows.err; eprintln(_STR("\\000%.*s", 2, IError_str(err))); }') } -fn (mut g Gen) psql_create_table(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) psql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.writeln('// psql table creator') - create_string := g.table_gen(node, typ) + create_string := g.table_gen(node, typ, db_expr) tmp := g.new_tmp_var() g.write('Option_Array_pg__Row $tmp = pg__DB_exec(') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(', _SLIT("$create_string"));') g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') } -fn (mut g Gen) psql_drop_table(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) psql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { table_name := g.get_table_name(node.table_expr) g.writeln('// psql table drop') drop_string := 'DROP TABLE "$table_name";' tmp := g.new_tmp_var() g.write('Option_Array_pg__Row $tmp = pg__DB_exec(&') - g.expr(node.db_expr) + g.expr(db_expr) g.writeln(', _SLIT("$drop_string"));') g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') } @@ -929,7 +935,7 @@ fn (mut g Gen) get_base_sql_select_query(node ast.SqlExpr) string { return sql_query } -fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType, psql_data ...string) { +fn (mut g Gen) sql_defaults(node ast.SqlStmtLine, typ SqlType, psql_data ...string) { table_name := g.get_table_name(node.table_expr) mut lit := '`' if typ == .psql { @@ -981,7 +987,7 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType, psql_data ...string) g.write(';")') } -fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { +fn (mut g Gen) table_gen(node ast.SqlStmtLine, typ SqlType, expr ast.Expr) string { typ_sym := g.table.get_type_symbol(node.table_expr.typ) struct_data := typ_sym.struct_info() table_name := g.get_table_name(node.table_expr) @@ -1032,15 +1038,14 @@ fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { if converted_typ == '' { if g.table.get_type_symbol(field.typ).kind == .struct_ { converted_typ = g.sql_type_from_v(typ, ast.int_type) - g.sql_create_table(ast.SqlStmt{ - db_expr: node.db_expr + g.sql_create_table(ast.SqlStmtLine{ kind: node.kind pos: node.pos table_expr: ast.TypeNode{ typ: field.typ pos: node.table_expr.pos } - }) + }, expr) } else { verror('unknown type ($field.typ) for field $field.name in struct $table_name') continue diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index ae5023c9a2..6eed09a7bc 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -97,8 +97,10 @@ pub fn (mut w Walker) stmt(node ast.Stmt) { } ast.SqlStmt { w.expr(node.db_expr) - w.expr(node.where_expr) - w.exprs(node.update_exprs) + for line in node.lines { + w.expr(line.where_expr) + w.exprs(line.update_exprs) + } } ast.StructDecl { w.struct_fields(node.fields) diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index 930ed14635..6587614565 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -125,9 +125,25 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { } // println(typeof(db_expr)) p.check(.lcbr) - // kind := ast.SqlExprKind.select_ - // + + 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.position()) + 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.position() mut kind := ast.SqlStmtKind.insert if n == 'delete' { kind = .delete @@ -138,13 +154,11 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { table := p.check_name() if table != 'table' { p.error('expected `table` got `$table`') - return ast.SqlStmt{} + return ast.SqlStmtLine{} } typ := p.parse_type() typ_pos := p.tok.position() - p.check(.rcbr) - return ast.SqlStmt{ - db_expr: db_expr + return ast.SqlStmtLine{ kind: kind pos: pos.extend(p.prev_tok.position()) table_expr: ast.TypeNode{ @@ -157,13 +171,11 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { table := p.check_name() if table != 'table' { p.error('expected `table` got `$table`') - return ast.SqlStmt{} + return ast.SqlStmtLine{} } typ := p.parse_type() typ_pos := p.tok.position() - p.check(.rcbr) - return ast.SqlStmt{ - db_expr: db_expr + return ast.SqlStmtLine{ kind: kind pos: pos.extend(p.prev_tok.position()) table_expr: ast.TypeNode{ @@ -183,7 +195,7 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { inserted_var_name = expr.name } else { p.error('can only insert variables') - return ast.SqlStmt{} + return ast.SqlStmtLine{} } } } @@ -192,11 +204,11 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { mut update_exprs := []ast.Expr{cap: 5} if kind == .insert && n != 'into' { p.error('expecting `into`') - return ast.SqlStmt{} + return ast.SqlStmtLine{} } else if kind == .update { if n != 'set' { p.error('expecting `set`') - return ast.SqlStmt{} + return ast.SqlStmtLine{} } for { column := p.check_name() @@ -211,7 +223,7 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { } } else if kind == .delete && n != 'from' { p.error('expecting `from`') - return ast.SqlStmt{} + return ast.SqlStmtLine{} } mut table_pos := p.tok.position() @@ -220,24 +232,21 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { table_pos = p.tok.position() table_type = p.parse_type() } else if kind == .update { - p.check_sql_keyword('where') or { return ast.SqlStmt{} } + p.check_sql_keyword('where') or { return ast.SqlStmtLine{} } where_expr = p.expr(0) } else if kind == .delete { table_pos = p.tok.position() table_type = p.parse_type() - p.check_sql_keyword('where') or { return ast.SqlStmt{} } + p.check_sql_keyword('where') or { return ast.SqlStmtLine{} } where_expr = p.expr(0) } - p.check(.rcbr) - pos.last_line = p.prev_tok.line_nr - return ast.SqlStmt{ - db_expr: db_expr + return ast.SqlStmtLine{ table_expr: ast.TypeNode{ typ: table_type pos: table_pos } object_var_name: inserted_var_name - pos: pos.extend(p.prev_tok.position()) + pos: pos updated_columns: updated_columns update_exprs: update_exprs kind: kind