orm: fix `column == var`; limit 1; vweb: @footer

pull/5460/head
Alexander Medvednikov 2020-06-22 16:52:03 +02:00
parent 73296e486a
commit deb09d95b0
8 changed files with 85 additions and 9 deletions

View File

@ -9,6 +9,7 @@ import os
// / customizing the look & feel of the assertions results easier, // / customizing the look & feel of the assertions results easier,
// / since it is done in normal V code, instead of in embedded C ... // / 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) { fn cb_assertion_failed(i &VAssertMetaInfo) {
// color_on := term.can_show_color_on_stderr() // color_on := term.can_show_color_on_stderr()
use_relative_paths := match os.getenv('VERROR_PATHS') { use_relative_paths := match os.getenv('VERROR_PATHS') {
@ -25,8 +26,13 @@ fn cb_assertion_failed(i &VAssertMetaInfo) {
eprintln('Source : ${i.src}') eprintln('Source : ${i.src}')
if i.op.len > 0 && i.op != 'call' { if i.op.len > 0 && i.op != 'call' {
eprintln(' left value: ${i.llabel} = ${i.lvalue}') eprintln(' left value: ${i.llabel} = ${i.lvalue}')
if i.rlabel == i.rvalue {
eprintln(' right value: $i.rlabel')
}
else {
eprintln(' right value: ${i.rlabel} = ${i.rvalue}') eprintln(' right value: ${i.rlabel} = ${i.rvalue}')
} }
}
} }
fn cb_assertion_ok(i &VAssertMetaInfo) { fn cb_assertion_ok(i &VAssertMetaInfo) {

View File

@ -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}') eprintln('${i.fpath}:${i.line_nr+1}: FAIL: fn ${i.fn_name}: assert ${i.src}')
if i.op.len > 0 && i.op != 'call' { if i.op.len > 0 && i.op != 'call' {
eprintln(' left value: ${i.llabel} = ${i.lvalue}') eprintln(' left value: ${i.llabel} = ${i.lvalue}')
if i.rlabel == i.rvalue {
eprintln(' right value: $i.rlabel')
}
else {
eprintln(' right value: ${i.rlabel} = ${i.rvalue}') eprintln(' right value: ${i.rlabel} = ${i.rvalue}')
} }
}
} }

View File

@ -22,7 +22,7 @@ fn test_orm_sqlite() {
db.exec("drop table if exists User") db.exec("drop table if exists User")
db.exec("create table User (id integer primary key, age int default 0, name text default '');") 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 ('Sam', 29)")
db.exec("insert into User (name, age) values ('Peter', 31)") db.exec("insert into User (name, age) values ('Peter', 31)")
@ -45,10 +45,19 @@ fn test_orm_sqlite() {
assert nr_peters == 1 assert nr_peters == 1
println('nr_peters=$nr_peters') println('nr_peters=$nr_peters')
// //
nr_sams := sql db { nr_peters2 := sql db {
select count from User where id == 1 && name == name 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 { user := sql db {
select from User where id == 1 select from User where id == 1

View File

@ -356,7 +356,6 @@ pub enum IdentKind {
// A single identifier // A single identifier
pub struct Ident { pub struct Ident {
pub: pub:
value string
language table.Language language table.Language
tok_kind token.Kind tok_kind token.Kind
mod string mod string

View File

@ -110,6 +110,7 @@ mut:
cur_generic_type table.Type // `int`, `string`, etc in `foo<T>()` cur_generic_type table.Type // `int`, `string`, etc in `foo<T>()`
sql_i int sql_i int
sql_stmt_name string sql_stmt_name string
sql_side SqlExprSide // left or right, to distinguish idents in `name == name`
} }
const ( const (

View File

@ -12,6 +12,8 @@ const (
dbtype = 'sqlite' dbtype = 'sqlite'
) )
enum SqlExprSide { left right }
fn (mut g Gen) sql_insert_expr(node ast.SqlInsertExpr) { fn (mut g Gen) sql_insert_expr(node ast.SqlInsertExpr) {
sym := g.table.get_type_symbol(node.table_type) sym := g.table.get_type_symbol(node.table_type)
info := sym.info as table.Struct 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);') g.writeln('int _step_res$tmp = sqlite3_step($g.sql_stmt_name);')
if node.is_array { 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_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 { for i, field in node.fields {
mut func := 'sqlite3_column_int' 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) { 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), // 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() // 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. // not a V variable. Need to distinguish column names from V variables.
match expr { match expr {
ast.InfixExpr { ast.InfixExpr {
g.sql_side = .left
g.expr_to_sql(expr.left) g.expr_to_sql(expr.left)
match expr.op { match expr.op {
.eq { g.write(' = ') } .eq { g.write(' = ') }
@ -178,16 +194,38 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr) {
.logical_or { g.write(' or ') } .logical_or { g.write(' or ') }
else {} else {}
} }
g.sql_side = .right
g.expr_to_sql(it.right) g.expr_to_sql(it.right)
} }
ast.StringLiteral { ast.StringLiteral {
// g.write("'$it.val'") // g.write("'$it.val'")
g.inc_sql_i() 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 { ast.IntegerLiteral {
g.inc_sql_i() 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 { else {
g.expr(expr) g.expr(expr)

View File

@ -55,6 +55,14 @@ fn (mut p Parser) sql_expr() ast.Expr {
// return an array // return an array
typ = table.new_type(p.table.find_or_register_array(table_type, 1, p.mod)) 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) p.check(.rcbr)
// ///////// // /////////
// Register this type's fields as variables so they can be used in `where` // Register this type's fields as variables so they can be used in `where`

View File

@ -30,6 +30,7 @@ pub fn compile_template(html_, fn_name string) string {
// lines := os.read_lines(path) // lines := os.read_lines(path)
mut html := html_.trim_space() mut html := html_.trim_space()
mut header := '' mut header := ''
mut footer := ''
if os.exists('templates/header.html') && html.contains('@header') { if os.exists('templates/header.html') && html.contains('@header') {
h := os.read_file('templates/header.html') or { h := os.read_file('templates/header.html') or {
panic('reading file templates/header.html failed') 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("\'", '"') header = h.trim_space().replace("\'", '"')
html = header + html 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 lines := html.split_into_lines()
mut s := strings.new_builder(1000) mut s := strings.new_builder(1000)
@ -48,6 +56,8 @@ fn vweb_tmpl_${fn_name}() {
mut sb := strings.new_builder(${lines.len * 30})\n mut sb := strings.new_builder(${lines.len * 30})\n
header := \' \' // TODO remove header := \' \' // TODO remove
_ = header _ = header
footer := \' \' // TODO remove
_ = footer
") ")
s.write(str_start) s.write(str_start)