From fb5cae73766830293658493e0d4ae239d3b265d5 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 17 Jun 2020 04:05:13 +0200 Subject: [PATCH] orm: select where id = x --- vlib/orm/orm_test.v | 19 ++++++++++++----- vlib/strings/builder_test.v | 11 ++++++---- vlib/v/ast/ast.v | 16 +++++++------- vlib/v/checker/checker.v | 42 ++++++++++++++++++++++++------------- vlib/v/gen/sql.v | 36 ++++++++++++++++++++++++++++--- vlib/v/parser/sql.v | 11 ++++++++++ 6 files changed, 100 insertions(+), 35 deletions(-) diff --git a/vlib/orm/orm_test.v b/vlib/orm/orm_test.v index a8d5f3527d..f800429e4b 100644 --- a/vlib/orm/orm_test.v +++ b/vlib/orm/orm_test.v @@ -3,27 +3,28 @@ //import term import sqlite -struct Modules { +struct Module { id int user_id int - name string - url string + //name string + //url string //nr_downloads int } struct User { id int + age int name string } fn test_orm_sqlite() { db := sqlite.connect(':memory:') or { panic(err) } db.exec("drop table if exists User") - db.exec("create table User (id integer primary key, name text default '');") + db.exec("create table User (id integer primary key, age int default 0, name text default '');") name := 'sam' - db.exec("insert into User (name) values ('Sam')") + db.exec("insert into User (name, age) values ('Sam', 29)") db.exec("insert into User (name) values ('Peter')") db.exec("insert into User (name) values ('Kate')") nr_all_users := sql db { @@ -48,6 +49,14 @@ fn test_orm_sqlite() { select count from User where id == 1 && name == name } println('nr_sams=$nr_sams') + // + user := sql db { + select from User where id == 1 + } + println(user) + assert user.name == 'Sam' + assert user.id == 1 + assert user.age == 29 } diff --git a/vlib/strings/builder_test.v b/vlib/strings/builder_test.v index bf89f4ef9b..ae6a03a38e 100644 --- a/vlib/strings/builder_test.v +++ b/vlib/strings/builder_test.v @@ -15,10 +15,13 @@ fn test_sb() { 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' + $if !windows { + // TODO msvc bug + sb = strings.new_builder(10) + sb.write('123456') + assert sb.cut_last(2) == '56' + assert sb.str() == '1234' + } /// /* sb = strings.new_builder(10) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 0a3a0b94ee..c0e92ec373 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -9,11 +9,11 @@ import v.errors pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl -pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | BoolLiteral | CallExpr | - CastExpr | CharLiteral | ComptimeCall | ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | - IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | MapInit | MatchExpr | None | - OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectorExpr | SizeOf | SqlExpr | - StringInterLiteral | StringLiteral | StructInit | Type | TypeOf +pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | BoolLiteral | CallExpr | CastExpr | CharLiteral | + ComptimeCall | ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | + InfixExpr | IntegerLiteral | Likely | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | + PrefixExpr | RangeExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | + StructInit | Type | TypeOf pub type Stmt = AssertStmt | AssignStmt | Attr | Block | BranchStmt | Comment | CompIf | ConstDecl | DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt | @@ -44,7 +44,6 @@ pub struct ExprStmt { pub: expr Expr pos token.Position - // treat like expr (dont add trailing `;`) // is used for `x++` in `for x:=1; ; x++` is_expr bool pub mut: @@ -806,8 +805,9 @@ pub: is_count bool db_var_name string // `db` in `sql db {` table_name string - where_expr Expr - has_where bool + where_expr Expr + has_where bool + fields []table.Field } [inline] diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index a6cd4e3d12..18a1fca852 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1307,8 +1307,9 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { if right_type_sym0.kind == .multi_return { assign_stmt.right_types = right_type_sym0.mr_info().types right_len = assign_stmt.right_types.len + } else if right_type0 == table.void_type { + right_len = 0 } - else if right_type0 == table.void_type { right_len=0 } } if assign_stmt.left.len != right_len { if right_first is ast.CallExpr { @@ -1338,7 +1339,9 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { if is_decl { left_type = c.table.mktyp(right_type) // we are unwrapping here instead if check_expr_opt_call currently - if left_type.has_flag(.optional) { left_type = left_type.clear_flag(.optional) } + if left_type.has_flag(.optional) { + left_type = left_type.clear_flag(.optional) + } } else { // Make sure the variable is mutable c.fail_if_immutable(left) @@ -1353,9 +1356,10 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { if assign_stmt.op !in [.assign, .decl_assign] { c.error('cannot modify blank `_` identifier', it.pos) } - } - else { - if is_decl { c.check_valid_snake_case(it.name, 'variable name', it.pos) } + } else { + if is_decl { + c.check_valid_snake_case(it.name, 'variable name', it.pos) + } mut scope := c.file.scope.innermost(assign_stmt.pos.pos) mut ident_var_info := it.var_info() ident_var_info.typ = left_type @@ -1366,12 +1370,12 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { ast.PrefixExpr { // Do now allow `*x = y` outside `unsafe` if it.op == .mul && !c.inside_unsafe { - c.error('modifying variables via deferencing can only be done in `unsafe` blocks', assign_stmt.pos) + c.error('modifying variables via deferencing can only be done in `unsafe` blocks', + assign_stmt.pos) } } else {} } - left_type_unwrapped := c.unwrap_generic(left_type) right_type_unwrapped := c.unwrap_generic(right_type) left_sym := c.table.get_type_symbol(left_type_unwrapped) @@ -1381,9 +1385,11 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { .assign {} // No need to do single side check for =. But here put it first for speed. .plus_assign { if !left_sym.is_number() && left_type != table.string_type && !left_sym.is_pointer() { - c.error('operator += not defined on left operand type `$left_sym.name`', left.position()) + c.error('operator += not defined on left operand type `$left_sym.name`', + left.position()) } else if !right_sym.is_number() && right_type != table.string_type && !right_sym.is_pointer() { - c.error('operator += not defined on right operand type `$right_sym.name`', right.position()) + c.error('operator += not defined on right operand type `$right_sym.name`', + right.position()) } if right is ast.IntegerLiteral && right.str().int() == 1 { c.error('use `++` instead of `+= 1`', assign_stmt.pos) @@ -1391,9 +1397,11 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } .minus_assign { if !left_sym.is_number() && !left_sym.is_pointer() { - c.error('operator -= not defined on left operand type `$left_sym.name`', left.position()) + c.error('operator -= not defined on left operand type `$left_sym.name`', + left.position()) } else if !right_sym.is_number() && !right_sym.is_pointer() { - c.error('operator -= not defined on right operand type `$right_sym.name`', right.position()) + c.error('operator -= not defined on right operand type `$right_sym.name`', + right.position()) } if right is ast.IntegerLiteral && right.str().int() == 1 { c.error('use `--` instead of `-= 1`', assign_stmt.pos) @@ -1973,10 +1981,7 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { return table.u32_type } ast.SqlExpr { - if it.has_where { - c.expr(it.where_expr) - } - return it.typ + return c.sql_expr(it) } ast.StringLiteral { if it.language == .c { @@ -2564,6 +2569,13 @@ fn (c &Checker) fileis(s string) bool { return c.file.path.contains(s) } +fn (mut c Checker) sql_expr(node ast.SqlExpr) table.Type { + if node.has_where { + c.expr(node.where_expr) + } + return node.typ +} + fn (mut c Checker) fn_decl(it ast.FnDecl) { if it.is_generic && c.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion // loop thru each generic type and generate a function diff --git a/vlib/v/gen/sql.v b/vlib/v/gen/sql.v index f620b2541c..5cbb23c710 100644 --- a/vlib/v/gen/sql.v +++ b/vlib/v/gen/sql.v @@ -5,6 +5,7 @@ module gen import v.ast import strings +import v.table // pg,mysql etc const ( @@ -26,8 +27,17 @@ fn (mut g Gen) sql_expr(node ast.SqlExpr) { cur_line := g.go_before_stmt(0) mut q := 'select ' if node.is_count { - // select count(*) from User + // `select count(*) from User` q += 'count(*) from $node.table_name' + } else { + // `select id, name, country from User` + for i, field in node.fields { + q += '$field.name' + if i < node.fields.len - 1 { + q += ', ' + } + } + q += ' from $node.table_name' } if node.has_where { q += ' where ' @@ -37,7 +47,7 @@ fn (mut g Gen) sql_expr(node ast.SqlExpr) { 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('${dbtype}__DB $db_name = ${node.db_var_name};') + g.writeln('${dbtype}__DB $db_name = ${node.db_var_name};') // g.write('sqlite3_stmt* $g.sql_stmt_name = ${dbtype}__DB_init_stmt(*(${dbtype}__DB*)${node.db_var_name}.data, tos_lit("$q') g.write('sqlite3_stmt* $g.sql_stmt_name = ${dbtype}__DB_init_stmt($db_name, tos_lit("$q') if node.has_where && node.where_expr is ast.InfixExpr { @@ -49,7 +59,27 @@ fn (mut g Gen) sql_expr(node ast.SqlExpr) { 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);') + // + if node.is_count { + g.writeln('$cur_line ${dbtype}__get_int_from_stmt($g.sql_stmt_name);') + } else { + // `user := sql db { select from User where id = 1 }` + tmp := g.new_tmp_var() + g.write(g.typ(node.typ)) + g.writeln(' $tmp;') + g.writeln('sqlite3_step($g.sql_stmt_name);') + for i, field in node.fields { + mut func := 'sqlite3_column_int' + if field.typ == table.string_type { + func = 'sqlite3_column_text' + g.writeln('${tmp}.$field.name = tos_clone(${func}($g.sql_stmt_name, $i));') + } else { + g.writeln('${tmp}.$field.name = ${func}($g.sql_stmt_name, $i);') + } + } + g.writeln('sqlite3_finalize($g.sql_stmt_name);') + g.writeln('$cur_line $tmp; ') // `User user = tmp;` + } } fn (mut g Gen) expr_to_sql(expr ast.Expr) { diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index 284f9fa40f..f25116a0d5 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -28,6 +28,16 @@ fn (mut p Parser) sql_expr() ast.SqlExpr { if has_where { p.next() where_expr = p.expr(0) + // `id == x` means that a single object is returned + if !is_count && where_expr is ast.InfixExpr { + e := where_expr as ast.InfixExpr + if e.op == .eq && e.left is ast.Ident { + ident := e.left as ast.Ident + if ident.name == 'id' { + typ = table_type + } + } + } } p.check(.rcbr) // ///////// @@ -75,5 +85,6 @@ fn (mut p Parser) sql_expr() ast.SqlExpr { table_name: table_name where_expr: where_expr has_where: has_where + fields: fields } }