orm: struct field support (#8517)
parent
856246c858
commit
97c0ef3505
|
@ -17,6 +17,7 @@ const (
|
||||||
'vlib/sqlite/sqlite_test.v',
|
'vlib/sqlite/sqlite_test.v',
|
||||||
'vlib/vweb/tests/vweb_test.v',
|
'vlib/vweb/tests/vweb_test.v',
|
||||||
'vlib/v/tests/unsafe_test.v',
|
'vlib/v/tests/unsafe_test.v',
|
||||||
|
'vlib/v/tests/orm_sub_struct_test.v',
|
||||||
'vlib/x/websocket/websocket_test.v',
|
'vlib/x/websocket/websocket_test.v',
|
||||||
'vlib/net/http/http_httpbin_test.v',
|
'vlib/net/http/http_httpbin_test.v',
|
||||||
]
|
]
|
||||||
|
@ -66,6 +67,7 @@ const (
|
||||||
'vlib/net/websocket/ws_test.v',
|
'vlib/net/websocket/ws_test.v',
|
||||||
'vlib/sqlite/sqlite_test.v',
|
'vlib/sqlite/sqlite_test.v',
|
||||||
'vlib/orm/orm_test.v',
|
'vlib/orm/orm_test.v',
|
||||||
|
'vlib/v/tests/orm_sub_struct_test.v',
|
||||||
'vlib/clipboard/clipboard_test.v',
|
'vlib/clipboard/clipboard_test.v',
|
||||||
'vlib/vweb/tests/vweb_test.v',
|
'vlib/vweb/tests/vweb_test.v',
|
||||||
'vlib/x/websocket/websocket_test.v',
|
'vlib/x/websocket/websocket_test.v',
|
||||||
|
@ -77,6 +79,7 @@ const (
|
||||||
]
|
]
|
||||||
skip_on_windows = [
|
skip_on_windows = [
|
||||||
'vlib/orm/orm_test.v',
|
'vlib/orm/orm_test.v',
|
||||||
|
'vlib/v/tests/orm_sub_struct_test.v',
|
||||||
'vlib/net/websocket/ws_test.v',
|
'vlib/net/websocket/ws_test.v',
|
||||||
'vlib/x/websocket/websocket_test.v',
|
'vlib/x/websocket/websocket_test.v',
|
||||||
'vlib/vweb/tests/vweb_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`
|
updated_columns []string // for `update set x=y`
|
||||||
update_exprs []Expr // for `update`
|
update_exprs []Expr // for `update`
|
||||||
pub mut:
|
pub mut:
|
||||||
table_expr Type
|
table_expr Type
|
||||||
fields []table.Field
|
fields []table.Field
|
||||||
|
sub_structs map[int]SqlStmt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SqlExpr {
|
pub struct SqlExpr {
|
||||||
|
@ -1182,7 +1183,6 @@ pub:
|
||||||
typ table.Type
|
typ table.Type
|
||||||
is_count bool
|
is_count bool
|
||||||
db_expr Expr // `db` in `sql db {`
|
db_expr Expr // `db` in `sql db {`
|
||||||
where_expr Expr
|
|
||||||
has_where bool
|
has_where bool
|
||||||
has_offset bool
|
has_offset bool
|
||||||
offset_expr Expr
|
offset_expr Expr
|
||||||
|
@ -1194,8 +1194,10 @@ pub:
|
||||||
has_limit bool
|
has_limit bool
|
||||||
limit_expr Expr
|
limit_expr Expr
|
||||||
pub mut:
|
pub mut:
|
||||||
table_expr Type
|
where_expr Expr
|
||||||
fields []table.Field
|
table_expr Type
|
||||||
|
fields []table.Field
|
||||||
|
sub_structs map[int]SqlExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
|
|
|
@ -5492,7 +5492,56 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) table.Type {
|
||||||
c.cur_orm_ts = sym
|
c.cur_orm_ts = sym
|
||||||
info := sym.info as table.Struct
|
info := sym.info as table.Struct
|
||||||
fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, sym.name)
|
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.fields = fields
|
||||||
|
node.sub_structs = sub_structs
|
||||||
if node.has_where {
|
if node.has_where {
|
||||||
c.expr(node.where_expr)
|
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
|
info := sym.info as table.Struct
|
||||||
table_sym := c.table.get_type_symbol(node.table_expr.typ)
|
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)
|
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.fields = fields
|
||||||
|
node.sub_structs = sub_structs
|
||||||
c.expr(node.db_expr)
|
c.expr(node.db_expr)
|
||||||
if node.kind == .update {
|
if node.kind == .update {
|
||||||
for expr in node.update_exprs {
|
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)
|
c.expr(node.where_expr)
|
||||||
|
|
||||||
return table.void_type
|
return table.void_type
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) fetch_and_verify_orm_fields(info table.Struct, pos token.Position, table_name string) []table.Field {
|
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'))
|
&& !it.attrs.contains('skip'))
|
||||||
if fields.len == 0 {
|
if fields.len == 0 {
|
||||||
c.error('V orm: select: empty fields in `$table_name`', pos)
|
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))')
|
g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))')
|
||||||
}
|
}
|
||||||
ast.SqlExpr {
|
ast.SqlExpr {
|
||||||
g.sql_select_expr(node)
|
g.sql_select_expr(node, false, '')
|
||||||
}
|
}
|
||||||
ast.StringLiteral {
|
ast.StringLiteral {
|
||||||
g.string_literal(node)
|
g.string_literal(node)
|
||||||
|
|
|
@ -80,8 +80,19 @@ fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
|
||||||
x := '${node.object_var_name}.$field.name'
|
x := '${node.object_var_name}.$field.name'
|
||||||
if field.typ == table.string_type {
|
if field.typ == table.string_type {
|
||||||
g.writeln('sqlite3_bind_text($g.sql_stmt_name, ${i + 0}, ${x}.str, ${x}.len, 0);')
|
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 {
|
} 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);')
|
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
|
g.sql_i = 0
|
||||||
/*
|
/*
|
||||||
`nr_users := sql db { ... }` =>
|
`nr_users := sql db { ... }` =>
|
||||||
|
@ -107,7 +118,10 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
|
||||||
int nr_users = get_int(stmt)
|
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 '
|
mut sql_query := 'SELECT '
|
||||||
table_name := util.strip_mod_name(g.table.get_type_symbol(node.table_expr.typ).name)
|
table_name := util.strip_mod_name(g.table.get_type_symbol(node.table_expr.typ).name)
|
||||||
if node.is_count {
|
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('if ($string_data != NULL) {')
|
||||||
g.writeln('\t${tmp}.$field.name = tos_clone($string_data);')
|
g.writeln('\t${tmp}.$field.name = tos_clone($string_data);')
|
||||||
g.writeln('}')
|
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 {
|
} else {
|
||||||
g.writeln('${tmp}.$field.name = ${func}($g.sql_stmt_name, $i);')
|
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