From 96aa23ff3527abf078316dca1a7a1b0f68011027 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 11 Mar 2024 20:04:33 +0300 Subject: [PATCH] orm: insert expressions returning id --- vlib/v/ast/ast.v | 10 +++++++++- vlib/v/checker/orm.v | 7 +++++++ vlib/v/fmt/fmt.v | 12 ++++++++++-- vlib/v/gen/c/cgen.v | 6 +++++- vlib/v/gen/c/orm.v | 28 +++++++++++++++++++++++++++- vlib/v/parser/orm.v | 31 ++++++++++++++++++++++++++++--- 6 files changed, 86 insertions(+), 8 deletions(-) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index d31cf7274b0dec..4492418f894189 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1960,6 +1960,11 @@ pub enum SqlStmtKind { drop } +pub enum SqlExprKind { + insert + select_ +} + pub struct SqlStmt { pub: pos token.Pos @@ -1989,7 +1994,10 @@ pub mut: pub struct SqlExpr { pub: - is_count bool + is_count bool + is_insert bool // for insert expressions + inserted_var string + has_where bool has_order bool has_limit bool diff --git a/vlib/v/checker/orm.v b/vlib/v/checker/orm.v index 3ed8b472e5493f..19ed2a349d90e9 100644 --- a/vlib/v/checker/orm.v +++ b/vlib/v/checker/orm.v @@ -190,9 +190,16 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { 'offset') } c.expr(mut node.db_expr) + if node.is_insert { + node.typ = ast.int_type + } c.check_orm_or_expr(mut node) + if node.is_insert { + return ast.int_type + } + return node.typ.clear_flag(.result) } diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 2535bdba1865f5..ec0c904ef7b855 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2985,7 +2985,11 @@ pub fn (mut f Fmt) sql_expr(node ast.SqlExpr) { f.write('sql ') f.expr(node.db_expr) f.writeln(' {') - f.write('\tselect ') + if node.is_insert { + f.write('\tinsert ') + } else { + f.write('\tselect ') + } sym := f.table.sym(node.table_expr.typ) mut table_name := sym.name if !table_name.starts_with('C.') && !table_name.starts_with('JS.') { @@ -3001,7 +3005,11 @@ pub fn (mut f Fmt) sql_expr(node ast.SqlExpr) { } } } - f.write('from ${table_name}') + if node.is_insert { + f.write('${node.inserted_var} into ${table_name}') + } else { + f.write('from ${table_name}') + } if node.has_where { f.write(' where ') f.expr(node.where_expr) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a4987bd49b6d57..ed0a499c557f95 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3527,7 +3527,11 @@ fn (mut g Gen) expr(node_ ast.Expr) { g.size_of(node) } ast.SqlExpr { - g.sql_select_expr(node) + if node.is_insert { + g.sql_insert_expr(node) + } else { + g.sql_select_expr(node) + } } ast.StringLiteral { g.string_literal(node) diff --git a/vlib/v/gen/c/orm.v b/vlib/v/gen/c/orm.v index 8529ee38576ebf..aa59c7c8d5016e 100644 --- a/vlib/v/gen/c/orm.v +++ b/vlib/v/gen/c/orm.v @@ -42,6 +42,30 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) { g.write('${left} *(${unwrapped_c_typ}*)${result_var}.data') } +fn (mut g Gen) sql_insert_expr(node ast.SqlExpr) { + left := g.go_before_last_stmt() + g.writeln('') + connection_var_name := g.new_tmp_var() + g.write_orm_connection_init(connection_var_name, &node.db_expr) + table_name := g.get_table_name_by_struct_type(node.table_expr.typ) + result_var_name := g.new_tmp_var() + g.sql_table_name = g.table.sym(node.table_expr.typ).name + + // orm_insert needs an SqlStmtLine, build it from SqlExpr (most nodes are the same) + hack_stmt_line := ast.SqlStmtLine{ + object_var: node.inserted_var + fields: node.fields + // sub_structs: node.sub_structs + } + g.write_orm_insert(hack_stmt_line, table_name, connection_var_name, result_var_name, + node.or_expr) + + g.write(left) + g.write('db__pg__DB_last_id(') + g.expr(node.db_expr) + g.write(');') +} + // sql_stmt writes C code that calls ORM functions for // performing various database operations such as creating and dropping tables, // as well as inserting and updating objects. @@ -272,6 +296,7 @@ fn (mut g Gen) write_orm_delete(node &ast.SqlStmtLine, table_name string, connec // inserting a struct into a table, saving inserted `id` into a passed variable. fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_var_name string, table_name string, last_ids_arr string, res string, pid string, fkey string, or_expr ast.OrExpr) { mut subs := []ast.SqlStmtLine{} + mut subs_unwrapped_c_typ := []string{} mut arrs := []ast.SqlStmtLine{} mut fkeys := []string{} @@ -285,6 +310,7 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v unwrapped_c_typ := g.typ(field.typ.clear_flag(.option)) subs_unwrapped_c_typ << if field.typ.has_flag(.option) { unwrapped_c_typ } else { '' } } else if sym.kind == .array { + // Handle foreign keys if attr := field.attrs.find_first('fkey') { fkeys << attr.arg } else { @@ -475,7 +501,7 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v unsafe { fff.free() } g.write_orm_insert_with_last_ids(arr, connection_var_name, g.get_table_name_by_struct_type(arr.table_expr.typ), last_ids, res_, id_name, fkeys[i], or_expr) - // Validates sub insertion success otherwise, handled and propagated error. + // Validates sub insertion success otherwise, handled and propagated error. g.or_block(res_, or_expr, ast.int_type.set_flag(.result)) g.indent-- g.writeln('}') diff --git a/vlib/v/parser/orm.v b/vlib/v/parser/orm.v index e600db979d75c4..ded79dad21ce54 100644 --- a/vlib/v/parser/orm.v +++ b/vlib/v/parser/orm.v @@ -5,6 +5,8 @@ module parser import v.ast +// select from User +// insert user into User returning id fn (mut p Parser) sql_expr() ast.Expr { tmp_inside_match := p.inside_match p.inside_orm = true @@ -16,12 +18,32 @@ fn (mut p Parser) sql_expr() ast.Expr { p.unexpected(prepend_msg: 'invalid expression:', expecting: 'database') } p.check(.lcbr) - p.check(.key_select) - is_count := p.check_name() == 'count' + // p.check(.key_select) + is_select := p.tok.kind == .key_select + is_insert := p.tok.lit == 'insert' + if !is_select && !is_insert { + p.error('expected "select" or "insert" in an ORM expression') + } + p.next() + // kind := if is_select { ast.SqlExprKind.select_ } else { ast.SqlExprKind.insert } + mut inserted_var := '' + mut is_count := false + if is_insert { + inserted_var = p.check_name() + into := p.check_name() + if into != 'into' { + p.error('expecting `into`') + } + } else if is_select { + is_count = p.check_name() == 'count' + } mut typ := ast.void_type if is_count { - p.check_name() // from + n := p.check_name() // from + if n != 'from' { + p.error('expecting "from" in a "select count" ORM statement') + } } table_pos := p.tok.pos() @@ -90,6 +112,7 @@ fn (mut p Parser) sql_expr() ast.Expr { return ast.SqlExpr{ is_count: is_count + is_insert: is_insert typ: typ.set_flag(.result) or_expr: or_expr db_expr: db_expr @@ -104,6 +127,7 @@ fn (mut p Parser) sql_expr() ast.Expr { has_desc: has_desc is_array: if is_count { false } else { true } is_generated: false + inserted_var: inserted_var pos: pos.extend(p.prev_tok.pos()) table_expr: ast.TypeNode{ typ: table_type @@ -114,6 +138,7 @@ fn (mut p Parser) sql_expr() ast.Expr { // insert user into User // update User set nr_oders=nr_orders+1 where id == user_id +// delete fn (mut p Parser) sql_stmt() ast.SqlStmt { mut pos := p.tok.pos() p.inside_orm = true