orm: struct field support (#8517)

pull/8561/head
Louis Schmieder 2021-02-04 20:28:33 +01:00 committed by GitHub
parent 856246c858
commit 97c0ef3505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 196 additions and 10 deletions

View File

@ -17,6 +17,7 @@ const (
'vlib/sqlite/sqlite_test.v',
'vlib/vweb/tests/vweb_test.v',
'vlib/v/tests/unsafe_test.v',
'vlib/v/tests/orm_sub_struct_test.v',
'vlib/x/websocket/websocket_test.v',
'vlib/net/http/http_httpbin_test.v',
]
@ -66,6 +67,7 @@ const (
'vlib/net/websocket/ws_test.v',
'vlib/sqlite/sqlite_test.v',
'vlib/orm/orm_test.v',
'vlib/v/tests/orm_sub_struct_test.v',
'vlib/clipboard/clipboard_test.v',
'vlib/vweb/tests/vweb_test.v',
'vlib/x/websocket/websocket_test.v',
@ -77,6 +79,7 @@ const (
]
skip_on_windows = [
'vlib/orm/orm_test.v',
'vlib/v/tests/orm_sub_struct_test.v',
'vlib/net/websocket/ws_test.v',
'vlib/x/websocket/websocket_test.v',
'vlib/vweb/tests/vweb_test.v',

View File

@ -0,0 +1,44 @@
import sqlite
struct Module {
id int
name string
nr_downloads int
creator User
}
struct User {
id int
age int
name string
is_customer bool
skipped_string string [skip]
}
fn main() {
db := sqlite.connect(':memory:') or { panic(err) }
db.exec('drop table if exists User')
db.exec("create table Module (id integer primary key, name text default '', nr_downloads int default 0, creator int default 0);")
db.exec("create table User (id integer primary key, age int default 0, name text default '', is_customer int default 0);")
mod := Module{
name: 'test'
nr_downloads: 10
creator: User{
age: 21
name: 'VUser'
is_customer: true
}
}
sql db {
insert mod into Module
}
modul := sql db {
select from Module where id == 1
}
println(modul.name)
println(modul.creator.name)
}

View File

@ -1173,8 +1173,9 @@ pub:
updated_columns []string // for `update set x=y`
update_exprs []Expr // for `update`
pub mut:
table_expr Type
fields []table.Field
table_expr Type
fields []table.Field
sub_structs map[int]SqlStmt
}
pub struct SqlExpr {
@ -1182,7 +1183,6 @@ pub:
typ table.Type
is_count bool
db_expr Expr // `db` in `sql db {`
where_expr Expr
has_where bool
has_offset bool
offset_expr Expr
@ -1194,8 +1194,10 @@ pub:
has_limit bool
limit_expr Expr
pub mut:
table_expr Type
fields []table.Field
where_expr Expr
table_expr Type
fields []table.Field
sub_structs map[int]SqlExpr
}
[inline]

View File

@ -5492,7 +5492,56 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) table.Type {
c.cur_orm_ts = sym
info := sym.info as table.Struct
fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, sym.name)
mut sub_structs := map[int]ast.SqlExpr{}
for f in fields.filter(c.table.types[int(it.typ)].kind == .struct_) {
mut n := ast.SqlExpr{
pos: node.pos
has_where: true
typ: f.typ
db_expr: node.db_expr
table_expr: ast.Type{
pos: node.table_expr.pos
typ: f.typ
}
}
tmp_inside_sql := c.inside_sql
c.sql_expr(mut n)
c.inside_sql = tmp_inside_sql
n.where_expr = ast.InfixExpr{
op: .eq
pos: n.pos
left: ast.Ident{
language: .v
tok_kind: .eq
scope: c.fn_scope
obj: ast.Var{}
mod: 'main'
name: 'id'
is_mut: false
kind: .unresolved
info: ast.IdentVar{}
}
right: ast.Ident{
language: .c
mod: 'main'
tok_kind: .eq
obj: ast.Var{}
is_mut: false
scope: c.fn_scope
info: ast.IdentVar{
typ: table.int_type
}
}
left_type: table.int_type
right_type: table.int_type
auto_locked: ''
or_block: ast.OrExpr{}
}
sub_structs[int(f.typ)] = n
}
node.fields = fields
node.sub_structs = sub_structs
if node.has_where {
c.expr(node.where_expr)
}
@ -5526,7 +5575,25 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) table.Type {
info := sym.info as table.Struct
table_sym := c.table.get_type_symbol(node.table_expr.typ)
fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name)
mut sub_structs := map[int]ast.SqlStmt{}
for f in fields.filter(c.table.types[int(it.typ)].kind == .struct_) {
mut n := ast.SqlStmt{
pos: node.pos
db_expr: node.db_expr
kind: node.kind
table_expr: ast.Type{
pos: node.table_expr.pos
typ: f.typ
}
object_var_name: '${node.object_var_name}.$f.name'
}
tmp_inside_sql := c.inside_sql
c.sql_stmt(mut n)
c.inside_sql = tmp_inside_sql
sub_structs[int(f.typ)] = n
}
node.fields = fields
node.sub_structs = sub_structs
c.expr(node.db_expr)
if node.kind == .update {
for expr in node.update_exprs {
@ -5534,11 +5601,13 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) table.Type {
}
}
c.expr(node.where_expr)
return table.void_type
}
fn (mut c Checker) fetch_and_verify_orm_fields(info table.Struct, pos token.Position, table_name string) []table.Field {
fields := info.fields.filter(it.typ in [table.string_type, table.int_type, table.bool_type]
fields := info.fields.filter(
(it.typ in [table.string_type, table.int_type, table.bool_type] || c.table.types[int(it.typ)].kind == .struct_)
&& !it.attrs.contains('skip'))
if fields.len == 0 {
c.error('V orm: select: empty fields in `$table_name`', pos)

View File

@ -2807,7 +2807,7 @@ fn (mut g Gen) expr(node ast.Expr) {
g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))')
}
ast.SqlExpr {
g.sql_select_expr(node)
g.sql_select_expr(node, false, '')
}
ast.StringLiteral {
g.string_literal(node)

View File

@ -80,8 +80,19 @@ fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
x := '${node.object_var_name}.$field.name'
if field.typ == table.string_type {
g.writeln('sqlite3_bind_text($g.sql_stmt_name, ${i + 0}, ${x}.str, ${x}.len, 0);')
} else if g.table.types[int(field.typ)].kind == .struct_ {
// insert again
expr := node.sub_structs[int(field.typ)]
tmp_sql_stmt_name := g.sql_stmt_name
g.sql_stmt(expr)
g.sql_stmt_name = tmp_sql_stmt_name
// get last inserted id
g.writeln('array_sqlite__Row rows = sqlite__DB_exec($db_name, _SLIT("SELECT last_insert_rowid()")).arg0;')
id_name := g.new_tmp_var()
g.writeln('int $id_name = string_int((*(string*)array_get((*(sqlite__Row*)array_get(rows, 0)).vals, 0)));')
g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $id_name); // id')
} else {
g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0}, $x); // stmt')
g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $x); // stmt')
}
}
}
@ -95,7 +106,7 @@ fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
g.writeln('\tsqlite3_finalize($g.sql_stmt_name);')
}
fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
fn (mut g Gen) sql_select_expr(node ast.SqlExpr, sub bool, line string) {
g.sql_i = 0
/*
`nr_users := sql db { ... }` =>
@ -107,7 +118,10 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
int nr_users = get_int(stmt)
```
*/
cur_line := g.go_before_stmt(0)
mut cur_line := line
if !sub {
cur_line = g.go_before_stmt(0)
}
mut sql_query := 'SELECT '
table_name := util.strip_mod_name(g.table.get_type_symbol(node.table_expr.typ).name)
if node.is_count {
@ -231,6 +245,27 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
g.writeln('if ($string_data != NULL) {')
g.writeln('\t${tmp}.$field.name = tos_clone($string_data);')
g.writeln('}')
} else if g.table.types[int(field.typ)].kind == .struct_ {
id_name := g.new_tmp_var()
g.writeln('//parse struct start')
g.writeln('int $id_name = ${func}($g.sql_stmt_name, $i);')
mut expr := node.sub_structs[int(field.typ)]
mut where_expr := expr.where_expr as ast.InfixExpr
mut ident := where_expr.right as ast.Ident
ident.name = id_name
where_expr.right = ident
expr.where_expr = where_expr
tmp_sql_i := g.sql_i
tmp_sql_stmt_name := g.sql_stmt_name
tmp_sql_buf := g.sql_buf
g.sql_select_expr(expr, true, '\t${tmp}.$field.name =')
g.writeln('//parse struct end')
g.sql_stmt_name = tmp_sql_stmt_name
g.sql_buf = tmp_sql_buf
g.sql_i = tmp_sql_i
} else {
g.writeln('${tmp}.$field.name = ${func}($g.sql_stmt_name, $i);')
}

View File

@ -0,0 +1,33 @@
import sqlite
struct Upper {
id int
sub SubStruct
}
struct SubStruct {
id int
name string
}
fn test_orm_sub_structs() {
db := sqlite.connect(':memory:') or { panic(err) }
db.exec('create table Upper (id integer primary key, sub int default 0)')
db.exec('create table SubStruct (id integer primary key, name string default "")')
upper_1 := Upper{
sub: SubStruct{
name: 'test123'
}
}
sql db {
insert upper_1 into Upper
}
upper_s := sql db {
select from Upper where id == 1
}
assert upper_s.sub.name == upper_1.sub.name
}