orm: struct field support (#8517)
parent
856246c858
commit
97c0ef3505
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);')
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue