mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
v orm: select
This commit is contained in:
parent
23993d2264
commit
ed58192e4c
@ -1,6 +1,6 @@
|
||||
import os
|
||||
import pg
|
||||
import term
|
||||
//import os
|
||||
//import pg
|
||||
//import term
|
||||
import sqlite
|
||||
|
||||
struct Modules {
|
||||
@ -11,30 +11,48 @@ struct Modules {
|
||||
//nr_downloads int
|
||||
}
|
||||
|
||||
struct User {
|
||||
id int
|
||||
name string
|
||||
}
|
||||
|
||||
fn test_orm_sqlite() {
|
||||
db := sqlite.connect(':memory:') or { panic(err) }
|
||||
/*
|
||||
db.exec("drop table if exists users")
|
||||
db.exec("create table users (id integer primary key, name text default '');")
|
||||
db.exec("drop table if exists User")
|
||||
db.exec("create table User (id integer primary key, name text default '');")
|
||||
|
||||
db.exec("insert into users (name) values ('Sam')")
|
||||
db.exec("insert into users (name) values ('Peter')")
|
||||
db.exec("insert into users (name) values ('Kate')")
|
||||
nr_users := sql db {
|
||||
//select count from modules
|
||||
name := 'sam'
|
||||
|
||||
db.exec("insert into User (name) values ('Sam')")
|
||||
db.exec("insert into User (name) values ('Peter')")
|
||||
db.exec("insert into User (name) values ('Kate')")
|
||||
nr_all_users := sql db {
|
||||
select count from User
|
||||
}
|
||||
assert nr_users == 3
|
||||
println('nr_users=')
|
||||
println(nr_users)
|
||||
//nr_modules := db.select count from modules
|
||||
//nr_modules := db.select count from Modules where id == 1
|
||||
//nr_modules := db.select count from Modules where
|
||||
//name == 'Bob' && id == 1
|
||||
*/
|
||||
assert nr_all_users == 3
|
||||
println('nr_all_users=$nr_all_users')
|
||||
//
|
||||
nr_users1 := sql db {
|
||||
select count from User where id == 1
|
||||
}
|
||||
assert nr_users1 == 1
|
||||
println('nr_users1=$nr_users1')
|
||||
//
|
||||
nr_peters := sql db {
|
||||
select count from User where id == 2 && name == 'Peter'
|
||||
}
|
||||
assert nr_peters == 1
|
||||
println('nr_peters=$nr_peters')
|
||||
//
|
||||
nr_sams := sql db {
|
||||
select count from User where id == 1 && name == name
|
||||
}
|
||||
println('nr_sams=$nr_sams')
|
||||
}
|
||||
|
||||
|
||||
fn test_orm_pg() {
|
||||
/*
|
||||
dbname := os.getenv('VDB_NAME')
|
||||
dbuser := os.getenv('VDB_USER')
|
||||
if dbname == '' || dbuser == '' {
|
||||
@ -43,8 +61,7 @@ fn test_orm_pg() {
|
||||
}
|
||||
db := pg.connect(dbname: dbname, user: dbuser) or { panic(err) }
|
||||
_ = db
|
||||
/*
|
||||
//nr_modules := db.select count from modules
|
||||
nr_modules := db.select count from modules
|
||||
//nr_modules := db.select count from Modules where id == 1
|
||||
nr_modules := db.select count from Modules where
|
||||
name == 'Bob' && id == 1
|
||||
@ -61,6 +78,7 @@ fn test_orm_pg() {
|
||||
|
||||
/*
|
||||
mod := db.retrieve<Module>(1)
|
||||
mod := db.select from Module where id = 1
|
||||
|
||||
mod := db.update Module set name = name + '!' where id > 10
|
||||
|
||||
|
@ -37,6 +37,21 @@ pub fn connect(path string) ?DB {
|
||||
}
|
||||
}
|
||||
|
||||
// Only for V ORM
|
||||
fn (db DB) init_stmt(query string) &C.sqlite3_stmt {
|
||||
stmt := &C.sqlite3_stmt(0)
|
||||
C.sqlite3_prepare_v2(db.conn, query.str, -1, &stmt, 0)
|
||||
return stmt
|
||||
}
|
||||
|
||||
// Only for V ORM
|
||||
fn get_int_from_stmt(stmt &C.sqlite3_stmt) int {
|
||||
C.sqlite3_step(stmt)
|
||||
res := C.sqlite3_column_int(stmt, 0)
|
||||
C.sqlite3_finalize(stmt)
|
||||
return res
|
||||
}
|
||||
|
||||
// Returns a single cell with value int.
|
||||
pub fn (db DB) q_int(query string) int {
|
||||
stmt := &C.sqlite3_stmt(0)
|
||||
|
@ -49,6 +49,24 @@ pub fn (mut b Builder) go_back(n int) {
|
||||
b.len -= n
|
||||
}
|
||||
|
||||
pub fn (mut b Builder) cut_last(n int) string {
|
||||
buf := b.buf[b.len-n..]
|
||||
s := string(buf.clone())
|
||||
b.buf.trim(b.buf.len-n)
|
||||
b.len -= n
|
||||
return s
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn (mut b Builder) cut_to(pos int) string {
|
||||
buf := b.buf[pos..]
|
||||
s := string(buf.clone())
|
||||
b.buf.trim(pos)
|
||||
b.len = pos
|
||||
return s
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn (mut b Builder) go_back_to(pos int) {
|
||||
b.buf.trim(pos)
|
||||
b.len = pos
|
||||
|
@ -8,11 +8,25 @@ fn test_sb() {
|
||||
assert sb.len == 8
|
||||
assert sb.str() == 'hi!hello'
|
||||
assert sb.len == 0
|
||||
///
|
||||
sb = strings.new_builder(10)
|
||||
sb.write('a')
|
||||
sb.write('b')
|
||||
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'
|
||||
///
|
||||
/*
|
||||
sb = strings.new_builder(10)
|
||||
sb.write('123456')
|
||||
x := sb.cut_to(2)
|
||||
assert x == '456'
|
||||
assert sb.str() == '123'
|
||||
*/
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -61,6 +61,10 @@ pub fn header(text, divider string) string {
|
||||
}
|
||||
|
||||
fn supports_escape_sequences(fd int) bool {
|
||||
//println('TERM=' + os.getenv('TERM'))
|
||||
if os.getenv('TERM') == 'dumb' {
|
||||
return false
|
||||
}
|
||||
vcolors_override := os.getenv('VCOLORS')
|
||||
if vcolors_override == 'always' {
|
||||
return true
|
||||
|
@ -801,7 +801,13 @@ pub:
|
||||
}
|
||||
|
||||
pub struct SqlExpr {
|
||||
typ table.Type
|
||||
pub:
|
||||
typ table.Type
|
||||
is_count bool
|
||||
db_var_name string // `db` in `sql db {`
|
||||
table_name string
|
||||
where_expr Expr
|
||||
has_where bool
|
||||
}
|
||||
|
||||
[inline]
|
||||
|
@ -556,7 +556,8 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
|
||||
c.error('$infix_expr.op.str(): type `${typ_sym.name}` does not exist', type_expr.pos)
|
||||
}
|
||||
if left.kind != .interface_ && left.kind != .sum_type {
|
||||
c.error('`$infix_expr.op.str()` can only be used with interfaces and sum types', type_expr.pos)
|
||||
c.error('`$infix_expr.op.str()` can only be used with interfaces and sum types',
|
||||
type_expr.pos)
|
||||
}
|
||||
return table.bool_type
|
||||
}
|
||||
@ -681,9 +682,12 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e
|
||||
ast.AnonFn {
|
||||
if it.decl.args.len > 1 {
|
||||
c.error('function needs exactly 1 argument', call_expr.pos)
|
||||
} else if is_map && (it.decl.return_type != elem_typ || it.decl.args[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`', call_expr.pos)
|
||||
} else if !is_map && (it.decl.return_type != table.bool_type || it.decl.args[0].typ != elem_typ) {
|
||||
} else if is_map && (it.decl.return_type != elem_typ || it.decl.args[0].typ !=
|
||||
elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`',
|
||||
call_expr.pos)
|
||||
} else if !is_map && (it.decl.return_type != table.bool_type || it.decl.args[0].typ !=
|
||||
elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`', call_expr.pos)
|
||||
}
|
||||
}
|
||||
@ -696,9 +700,12 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e
|
||||
if func.args.len > 1 {
|
||||
c.error('function needs exactly 1 argument', call_expr.pos)
|
||||
} else if is_map && (func.return_type != elem_typ || func.args[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`', call_expr.pos)
|
||||
} else if !is_map && (func.return_type != table.bool_type || func.args[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`', call_expr.pos)
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) $elem_sym.name {...}`',
|
||||
call_expr.pos)
|
||||
} else if !is_map && (func.return_type != table.bool_type || func.args[0].typ !=
|
||||
elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
call_expr.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1948,15 +1955,29 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
|
||||
ast.None {
|
||||
return table.none_type
|
||||
}
|
||||
ast.OrExpr {
|
||||
// never happens
|
||||
return table.void_type
|
||||
}
|
||||
ast.ParExpr {
|
||||
return c.expr(it.expr)
|
||||
}
|
||||
ast.RangeExpr {
|
||||
// never happens
|
||||
return table.void_type
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
return c.selector_expr(mut it)
|
||||
}
|
||||
ast.SizeOf {
|
||||
return table.u32_type
|
||||
}
|
||||
ast.SqlExpr {
|
||||
if it.has_where {
|
||||
c.expr(it.where_expr)
|
||||
}
|
||||
return it.typ
|
||||
}
|
||||
ast.StringLiteral {
|
||||
if it.language == .c {
|
||||
return table.byteptr_type
|
||||
@ -1986,12 +2007,6 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
|
||||
}
|
||||
return table.bool_type
|
||||
}
|
||||
else {
|
||||
tnode := typeof(node)
|
||||
if tnode != 'unknown v.ast.Expr' {
|
||||
println('checker.expr(): unhandled node with typeof(`${tnode}`)')
|
||||
}
|
||||
}
|
||||
}
|
||||
return table.void_type
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ mut:
|
||||
options strings.Builder // `Option_xxxx` types
|
||||
json_forward_decls strings.Builder // json type forward decls
|
||||
enum_typedefs strings.Builder // enum types
|
||||
sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc
|
||||
file ast.File
|
||||
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
|
||||
last_fn_c_name string
|
||||
@ -76,6 +77,7 @@ mut:
|
||||
is_assign_rhs bool // inside right part of assign after `=` (val expr)
|
||||
is_array_set bool
|
||||
is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc
|
||||
is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc)
|
||||
optionals []string // to avoid duplicates TODO perf, use map
|
||||
inside_ternary int // ?: comma separated statements on a single line
|
||||
ternary_names map[string]string
|
||||
@ -102,6 +104,8 @@ mut:
|
||||
fn_main &ast.FnDecl // the FnDecl of the main function. Needed in order to generate the main function code *last*
|
||||
cur_fn &ast.FnDecl
|
||||
cur_generic_type table.Type // `int`, `string`, etc in `foo<T>()`
|
||||
sql_i int
|
||||
sql_stmt_name string
|
||||
}
|
||||
|
||||
const (
|
||||
@ -131,6 +135,7 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
|
||||
options: strings.new_builder(100)
|
||||
json_forward_decls: strings.new_builder(100)
|
||||
enum_typedefs: strings.new_builder(100)
|
||||
sql_buf: strings.new_builder(100)
|
||||
table: table
|
||||
pref: pref
|
||||
fn_decl: 0
|
||||
@ -1544,7 +1549,7 @@ fn (mut g Gen) expr(node ast.Expr) {
|
||||
}
|
||||
ast.RangeExpr {
|
||||
// Only used in IndexExpr
|
||||
}
|
||||
}
|
||||
ast.SizeOf {
|
||||
mut styp := it.type_name
|
||||
if it.type_name == '' {
|
||||
@ -1566,7 +1571,7 @@ fn (mut g Gen) expr(node ast.Expr) {
|
||||
g.write('sizeof($styp)')
|
||||
}
|
||||
ast.SqlExpr {
|
||||
g.write('// sql expression')
|
||||
g.sql_expr(it)
|
||||
}
|
||||
ast.StringLiteral {
|
||||
if it.is_raw {
|
||||
@ -1630,10 +1635,6 @@ fn (mut g Gen) expr(node ast.Expr) {
|
||||
g.expr(it.expr)
|
||||
g.write(')')
|
||||
}
|
||||
//else {
|
||||
// #printf("node=%d\n", node.typ);
|
||||
//println(term.red('cgen.expr(): bad node ' + typeof(node)))
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3173,6 +3174,15 @@ fn (mut g Gen) insert_before_stmt(s string) {
|
||||
g.write(cur_line)
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_expr_to_string(expr ast.Expr) string {
|
||||
pos := g.out.buf.len
|
||||
g.expr(expr)
|
||||
return g.out.cut_last(g.out.buf.len - pos)
|
||||
}
|
||||
|
||||
fn (mut g Gen) start_tmp() {
|
||||
}
|
||||
|
||||
// If user is accessing the return value eg. in assigment, pass the variable name.
|
||||
// If the user is not using the optional return value. We need to pass a temp var
|
||||
// to access its fields (`.ok`, `.error` etc)
|
||||
@ -3263,7 +3273,6 @@ fn (mut g Gen) in_optimization(left ast.Expr, right ast.ArrayInit) {
|
||||
ptr_typ := g.gen_array_equality_fn(right.elem_type)
|
||||
g.write('${ptr_typ}_arr_eq(')
|
||||
}
|
||||
|
||||
g.expr(left)
|
||||
if is_str || is_array {
|
||||
g.write(', ')
|
||||
@ -4208,7 +4217,6 @@ fn (mut g Gen) array_init(it ast.ArrayInit) {
|
||||
if it.exprs.len == 0 {
|
||||
elem_sym := g.table.get_type_symbol(it.elem_type)
|
||||
is_default_array := elem_sym.kind == .array && it.has_default
|
||||
|
||||
if is_default_array {
|
||||
g.write('__new_array_with_array_default(')
|
||||
} else {
|
||||
|
93
vlib/v/gen/sql.v
Normal file
93
vlib/v/gen/sql.v
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module gen
|
||||
|
||||
import v.ast
|
||||
import strings
|
||||
|
||||
// pg,mysql etc
|
||||
const (
|
||||
dbtype = 'sqlite'
|
||||
)
|
||||
|
||||
fn (mut g Gen) sql_expr(node ast.SqlExpr) {
|
||||
g.sql_i = 0
|
||||
/*
|
||||
`nr_users := sql db { ... }` =>
|
||||
```
|
||||
sql_init_stmt()
|
||||
sql_bind_int()
|
||||
sql_bind_string()
|
||||
...
|
||||
int nr_users = get_int(stmt)
|
||||
```
|
||||
*/
|
||||
cur_line := g.go_before_stmt(0)
|
||||
mut q := 'select '
|
||||
if node.is_count {
|
||||
// select count(*) from User
|
||||
q += 'count(*) from $node.table_name'
|
||||
}
|
||||
if node.has_where {
|
||||
q += ' where '
|
||||
}
|
||||
// g.write('${dbtype}__DB_q_int(*(${dbtype}__DB*)${node.db_var_name}.data, tos_lit("$q')
|
||||
g.sql_stmt_name = g.new_tmp_var()
|
||||
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('sqlite3_stmt* $g.sql_stmt_name = ${dbtype}__DB_init_stmt(*(${dbtype}__DB*)${node.db_var_name}.data, tos_lit("$q')
|
||||
if node.has_where && node.where_expr is ast.InfixExpr {
|
||||
g.expr_to_sql(node.where_expr)
|
||||
}
|
||||
g.writeln('"));')
|
||||
// Dump all sql parameters generated by our custom expr handler
|
||||
binds := g.sql_buf.str()
|
||||
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);')
|
||||
}
|
||||
|
||||
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()
|
||||
//
|
||||
// TODO `where id = some_column + 1` needs literal generation of `some_column` as a string,
|
||||
// not a V variable. Need to distinguish column names from V variables.
|
||||
match expr {
|
||||
ast.InfixExpr {
|
||||
g.expr_to_sql(it.left)
|
||||
match it.op {
|
||||
.eq { g.write(' = ') }
|
||||
.and { g.write(' and ') }
|
||||
else {}
|
||||
}
|
||||
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);')
|
||||
}
|
||||
ast.IntegerLiteral {
|
||||
g.inc_sql_i()
|
||||
g.sql_buf.writeln('sqlite3_bind_int($g.sql_stmt_name, $g.sql_i, $it.val);')
|
||||
}
|
||||
else {
|
||||
g.expr(expr)
|
||||
}
|
||||
}
|
||||
/*
|
||||
ast.Ident {
|
||||
g.write('$it.name')
|
||||
}
|
||||
else {}
|
||||
*/
|
||||
}
|
||||
|
||||
fn (mut g Gen) inc_sql_i() {
|
||||
g.sql_i++
|
||||
g.write('?$g.sql_i')
|
||||
}
|
@ -4,7 +4,76 @@
|
||||
module parser
|
||||
|
||||
import v.ast
|
||||
import v.table
|
||||
|
||||
fn (mut p Parser) sql_expr() ast.SqlExpr {
|
||||
return ast.SqlExpr{}
|
||||
// `sql db {`
|
||||
p.check_name()
|
||||
db_var_name := p.check_name()
|
||||
p.check(.lcbr)
|
||||
//
|
||||
p.check(.key_select)
|
||||
n := p.check_name()
|
||||
is_count := n == 'count'
|
||||
mut typ := table.void_type
|
||||
if is_count {
|
||||
p.check_name() // from
|
||||
typ = table.int_type
|
||||
}
|
||||
table_type := p.parse_type() // `User`
|
||||
sym := p.table.get_type_symbol(table_type)
|
||||
table_name := sym.name
|
||||
mut where_expr := ast.Expr{}
|
||||
has_where := p.tok.kind == .name && p.tok.lit == 'where'
|
||||
if has_where {
|
||||
p.next()
|
||||
where_expr = p.expr(0)
|
||||
}
|
||||
p.check(.rcbr)
|
||||
// /////////
|
||||
// Register this type's fields as variables so they can be used in `where`
|
||||
// expressions
|
||||
// fields := typ.fields.filter(typ == 'string' || typ == 'int')
|
||||
// fields := typ.fields
|
||||
// get only string and int fields
|
||||
// mut fields := []Var
|
||||
info := sym.info as table.Struct
|
||||
fields := info.fields.filter(it.typ in [table.string_type, table.int_type, table.bool_type])
|
||||
/*
|
||||
for i, field in info.fields {
|
||||
if !(field.typ in ['string', 'int', 'bool']) {
|
||||
println('orm: skipping $field.name')
|
||||
continue
|
||||
}
|
||||
if field.attr.contains('skip') {
|
||||
continue
|
||||
}
|
||||
fields << field
|
||||
}
|
||||
*/
|
||||
if fields.len == 0 {
|
||||
p.error('V orm: select: empty fields in `$table_name`')
|
||||
}
|
||||
if fields[0].name != 'id' {
|
||||
p.error('V orm: `id int` must be the first field in `$table_name`')
|
||||
}
|
||||
for field in fields {
|
||||
// println('registering sql field var $field.name')
|
||||
p.scope.register(field.name, ast.Var{
|
||||
name: field.name
|
||||
typ: field.typ
|
||||
is_mut: true
|
||||
is_used: true
|
||||
is_changed: true
|
||||
})
|
||||
}
|
||||
// ////////////
|
||||
return ast.SqlExpr{
|
||||
is_count: is_count
|
||||
typ: typ
|
||||
db_var_name: db_var_name
|
||||
table_name: table_name
|
||||
where_expr: where_expr
|
||||
has_where: has_where
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user