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,
// / 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,8 +26,13 @@ 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}')
if i.rlabel == i.rvalue {
eprintln(' right value: $i.rlabel')
}
else {
eprintln(' right value: ${i.rlabel} = ${i.rvalue}')
}
}
}
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}')
if i.op.len > 0 && i.op != 'call' {
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}')
}
}
}

View File

@ -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

View File

@ -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

View File

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

View File

@ -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)

View File

@ -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`

View File

@ -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)