From deb09d95b039b8c5902cdf325776d9c6cc23cf23 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 22 Jun 2020 16:52:03 +0200 Subject: [PATCH] orm: fix `column == var`; limit 1; vweb: @footer --- cmd/tools/preludes/tests_assertions.v | 8 ++++- vlib/builtin/builtin.v | 7 ++++- vlib/orm/orm_test.v | 17 ++++++++--- vlib/v/ast/ast.v | 1 - vlib/v/gen/cgen.v | 1 + vlib/v/gen/sql.v | 42 +++++++++++++++++++++++++-- vlib/v/parser/sql.v | 8 +++++ vlib/vweb/tmpl/tmpl.v | 10 +++++++ 8 files changed, 85 insertions(+), 9 deletions(-) diff --git a/cmd/tools/preludes/tests_assertions.v b/cmd/tools/preludes/tests_assertions.v index 7b841a18fd..4d6af61263 100644 --- a/cmd/tools/preludes/tests_assertions.v +++ b/cmd/tools/preludes/tests_assertions.v @@ -9,6 +9,7 @@ import os // / customizing the look & feel of the assertions results easier, // / since it is done in normal V code, instead of in embedded C ... // ////////////////////////////////////////////////////////////////// +// TODO copy pasta builtin.v fn ___print_assert_failure fn cb_assertion_failed(i &VAssertMetaInfo) { // color_on := term.can_show_color_on_stderr() use_relative_paths := match os.getenv('VERROR_PATHS') { @@ -25,7 +26,12 @@ fn cb_assertion_failed(i &VAssertMetaInfo) { eprintln('Source : ${i.src}') if i.op.len > 0 && i.op != 'call' { eprintln(' left value: ${i.llabel} = ${i.lvalue}') - eprintln(' right value: ${i.rlabel} = ${i.rvalue}') + if i.rlabel == i.rvalue { + eprintln(' right value: $i.rlabel') + } + else { + eprintln(' right value: ${i.rlabel} = ${i.rvalue}') + } } } diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index f445262bb1..048ee22c8d 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -239,6 +239,11 @@ fn __print_assert_failure(i &VAssertMetaInfo) { eprintln('${i.fpath}:${i.line_nr+1}: FAIL: fn ${i.fn_name}: assert ${i.src}') if i.op.len > 0 && i.op != 'call' { eprintln(' left value: ${i.llabel} = ${i.lvalue}') - eprintln(' right value: ${i.rlabel} = ${i.rvalue}') + if i.rlabel == i.rvalue { + eprintln(' right value: $i.rlabel') + } + else { + eprintln(' right value: ${i.rlabel} = ${i.rvalue}') + } } } diff --git a/vlib/orm/orm_test.v b/vlib/orm/orm_test.v index a34c41a5df..60d722960d 100644 --- a/vlib/orm/orm_test.v +++ b/vlib/orm/orm_test.v @@ -22,7 +22,7 @@ fn test_orm_sqlite() { db.exec("drop table if exists User") db.exec("create table User (id integer primary key, age int default 0, name text default '');") - name := 'sam' + name := 'Peter' db.exec("insert into User (name, age) values ('Sam', 29)") db.exec("insert into User (name, age) values ('Peter', 31)") @@ -45,10 +45,19 @@ fn test_orm_sqlite() { assert nr_peters == 1 println('nr_peters=$nr_peters') // - nr_sams := sql db { - select count from User where id == 1 && name == name + nr_peters2 := sql db { + select count from User where id == 2 && name == name } - println('nr_sams=$nr_sams') + assert nr_peters2 == 1 + nr_peters3 := sql db { + select count from User where name == name + } + assert nr_peters3 == 1 + peters := sql db { + select from User where name == name // limit 1 + } + assert peters.len == 1 + assert peters[0].name == 'Peter' // user := sql db { select from User where id == 1 diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 4d11a11091..685fffb105 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -356,7 +356,6 @@ pub enum IdentKind { // A single identifier pub struct Ident { pub: - value string language table.Language tok_kind token.Kind mod string diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 4a85c48a31..7ba144ca4b 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -110,6 +110,7 @@ mut: cur_generic_type table.Type // `int`, `string`, etc in `foo()` sql_i int sql_stmt_name string + sql_side SqlExprSide // left or right, to distinguish idents in `name == name` } const ( diff --git a/vlib/v/gen/sql.v b/vlib/v/gen/sql.v index d34ec6312c..fe4556f5a7 100644 --- a/vlib/v/gen/sql.v +++ b/vlib/v/gen/sql.v @@ -12,6 +12,8 @@ const ( dbtype = 'sqlite' ) +enum SqlExprSide { left right } + fn (mut g Gen) sql_insert_expr(node ast.SqlInsertExpr) { sym := g.table.get_type_symbol(node.table_type) info := sym.info as table.Struct @@ -135,7 +137,10 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) { // g.writeln('int _step_res$tmp = sqlite3_step($g.sql_stmt_name);') if node.is_array { + g.writeln('\tprintf("step res=%d\\n", _step_res$tmp);') g.writeln('\tif (_step_res$tmp == SQLITE_DONE) break;') + g.writeln('\tif (_step_res$tmp = SQLITE_ROW) ;') // another row + g.writeln('\telse if (_step_res$tmp != SQLITE_OK) break;') } for i, field in node.fields { mut func := 'sqlite3_column_int' @@ -159,6 +164,16 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) { } } +fn (mut g Gen) sql_bind_int(val string) { + g.sql_buf.writeln('sqlite3_bind_int($g.sql_stmt_name, $g.sql_i, $val);') + +} + +fn (mut g Gen) sql_bind_string(val string, len string) { + g.sql_buf.writeln('sqlite3_bind_text($g.sql_stmt_name, $g.sql_i, $val, $len, 0);') + +} + fn (mut g Gen) expr_to_sql(expr ast.Expr) { // Custom handling for infix exprs (since we need e.g. `and` instead of `&&` in SQL queries), // strings. Everything else (like numbers, a.b) is handled by g.expr() @@ -167,6 +182,7 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr) { // not a V variable. Need to distinguish column names from V variables. match expr { ast.InfixExpr { + g.sql_side = .left g.expr_to_sql(expr.left) match expr.op { .eq { g.write(' = ') } @@ -178,16 +194,38 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr) { .logical_or { g.write(' or ') } else {} } + g.sql_side = .right g.expr_to_sql(it.right) } ast.StringLiteral { // g.write("'$it.val'") g.inc_sql_i() - g.sql_buf.writeln('sqlite3_bind_text($g.sql_stmt_name, $g.sql_i, "$it.val", $it.val.len, 0);') + g.sql_bind_string('"$it.val"', it.val.len.str()) } ast.IntegerLiteral { g.inc_sql_i() - g.sql_buf.writeln('sqlite3_bind_int($g.sql_stmt_name, $g.sql_i, $it.val);') + g.sql_bind_int(it.val) + } + ast.Ident { + // `name == user_name` => `name == ?1` + // for left sides just add a string, for right sides, generate the bindings + if g.sql_side == .left { + println("sql gen left $expr.name") + g.write(expr.name) + } else { + g.inc_sql_i() + info := expr.info as ast.IdentVar + typ := info.typ + if typ == table.string_type { + g.sql_bind_string('${expr.name}.str', '${expr.name}.len') + } + else if typ == table.int_type { + g.sql_bind_int(expr.name) + } + else { + verror('bad sql type $typ') + } + } } else { g.expr(expr) diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index 1316b2ef00..65b76e5831 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -55,6 +55,14 @@ fn (mut p Parser) sql_expr() ast.Expr { // return an array typ = table.new_type(p.table.find_or_register_array(table_type, 1, p.mod)) } + if p.tok.kind ==.name && p.tok.lit == 'limit' { + // `limit 1` means that a single object is returned + p.check_name() // `limit` + if p.tok.kind == .number && p.tok.lit == '1' { + query_one = true + } + p.next() + } p.check(.rcbr) // ///////// // Register this type's fields as variables so they can be used in `where` diff --git a/vlib/vweb/tmpl/tmpl.v b/vlib/vweb/tmpl/tmpl.v index 5d491956f8..5c1ca50b6e 100644 --- a/vlib/vweb/tmpl/tmpl.v +++ b/vlib/vweb/tmpl/tmpl.v @@ -30,6 +30,7 @@ pub fn compile_template(html_, fn_name string) string { // lines := os.read_lines(path) mut html := html_.trim_space() mut header := '' + mut footer := '' if os.exists('templates/header.html') && html.contains('@header') { h := os.read_file('templates/header.html') or { panic('reading file templates/header.html failed') @@ -37,6 +38,13 @@ pub fn compile_template(html_, fn_name string) string { header = h.trim_space().replace("\'", '"') html = header + html } + if os.exists('templates/footer.html') && html.contains('@footer') { + f := os.read_file('templates/footer.html') or { + panic('reading file templates/footer.html failed') + } + footer = f.trim_space().replace("\'", '"') + html += footer + } mut lines := html.split_into_lines() mut s := strings.new_builder(1000) @@ -48,6 +56,8 @@ fn vweb_tmpl_${fn_name}() { mut sb := strings.new_builder(${lines.len * 30})\n header := \' \' // TODO remove _ = header +footer := \' \' // TODO remove +_ = footer ") s.write(str_start)