orm: support multiline statements (#9888)

pull/9900/head
Louis Schmieder 2021-04-27 14:28:57 +02:00 committed by GitHub
parent 9f1ac39770
commit faf2656335
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 79 deletions

View File

@ -65,6 +65,7 @@ pub fn (mut app App) new_article() vweb.Result {
sql app.db {
insert article into Article
}
return app.redirect('/')
}

View File

@ -28,10 +28,30 @@ fn test_orm_sqlite() {
sql db {
create table User
}
name := 'Peter'
db.exec("insert into userlist (username, age) values ('Sam', 29)")
db.exec("insert into userlist (username, age) values ('Peter', 31)")
db.exec("insert into userlist (username, age, is_customer) values ('Kate', 30, 1)")
sam := User{
age: 29
name: 'Sam'
}
peter := User{
age: 31
name: 'Peter'
}
k := User{
age: 30
name: 'Kate'
is_customer: true
}
sql db {
insert sam into User
insert peter into User
insert k into User
}
c := sql db {
select count from User where id != 1
@ -113,6 +133,7 @@ fn test_orm_sqlite() {
sql db {
insert new_user into User
}
// db.insert<User>(user2)
x := sql db {
select from User where id == 4
@ -136,6 +157,7 @@ fn test_orm_sqlite() {
sql db {
update User set age = 31 where name == 'Kate'
}
kate2 := sql db {
select from User where id == 3
}
@ -167,6 +189,7 @@ fn test_orm_sqlite() {
sql db {
update User set age = new_age, name = 'Kate N' where id == 3
}
kate3 = sql db {
select from User where id == 3
}
@ -177,6 +200,7 @@ fn test_orm_sqlite() {
sql db {
update User set age = foo.age, name = 'Kate N' where id == 3
}
kate3 = sql db {
select from User where id == 3
}
@ -219,6 +243,7 @@ fn test_orm_sqlite() {
sql db {
delete from User where age == 34
}
updated_oldest := sql db {
select from User order by age desc limit 1
}

View File

@ -1439,9 +1439,16 @@ pub enum SqlStmtKind {
}
pub struct SqlStmt {
pub:
pos token.Position
db_expr Expr // `db` in `sql db {`
pub mut:
lines []SqlStmtLine
}
pub struct SqlStmtLine {
pub:
kind SqlStmtKind
db_expr Expr // `db` in `sql db {`
object_var_name string // `user`
pos token.Position
where_expr Expr
@ -1450,7 +1457,7 @@ pub:
pub mut:
table_expr TypeNode
fields []StructField
sub_structs map[int]SqlStmt
sub_structs map[int]SqlStmtLine
}
pub struct SqlExpr {

View File

@ -6488,6 +6488,18 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
}
fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type {
c.expr(node.db_expr)
mut typ := ast.void_type
for mut line in node.lines {
a := c.sql_stmt_line(mut line)
if a != ast.void_type {
typ = a
}
}
return typ
}
fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
c.inside_sql = true
defer {
c.inside_sql = false
@ -6501,11 +6513,10 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type {
}
info := table_sym.info as ast.Struct
fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name)
mut sub_structs := map[int]ast.SqlStmt{}
mut sub_structs := map[int]ast.SqlStmtLine{}
for f in fields.filter(c.table.type_symbols[int(it.typ)].kind == .struct_) {
mut n := ast.SqlStmt{
mut n := ast.SqlStmtLine{
pos: node.pos
db_expr: node.db_expr
kind: node.kind
table_expr: ast.TypeNode{
pos: node.table_expr.pos
@ -6514,13 +6525,12 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type {
object_var_name: '${node.object_var_name}.$f.name'
}
tmp_inside_sql := c.inside_sql
c.sql_stmt(mut n)
c.sql_stmt_line(mut n)
c.inside_sql = tmp_inside_sql
sub_structs[int(f.typ)] = n
}
node.fields = fields
node.sub_structs = sub_structs.move()
c.expr(node.db_expr)
if node.kind == .update {
for expr in node.update_exprs {
c.expr(expr)

View File

@ -1215,6 +1215,15 @@ pub fn (mut f Fmt) sql_stmt(node ast.SqlStmt) {
f.write('sql ')
f.expr(node.db_expr)
f.writeln(' {')
for line in node.lines {
f.sql_stmt_line(line)
}
f.writeln('}')
}
pub fn (mut f Fmt) sql_stmt_line(node ast.SqlStmtLine) {
table_name := util.strip_mod_name(f.table.get_type_symbol(node.table_expr.typ).name)
f.write('\t')
match node.kind {
@ -1249,7 +1258,6 @@ pub fn (mut f Fmt) sql_stmt(node ast.SqlStmt) {
f.writeln('drop table $table_name')
}
}
f.writeln('}')
}
pub fn (mut f Fmt) type_decl(node ast.TypeDecl) {

View File

@ -24,24 +24,30 @@ enum SqlType {
}
fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
for line in node.lines {
g.sql_stmt_line(line, node.db_expr)
}
}
fn (mut g Gen) sql_stmt_line(node ast.SqlStmtLine, expr ast.Expr) {
if node.kind == .create {
g.sql_create_table(node)
g.sql_create_table(node, expr)
return
} else if node.kind == .drop {
g.sql_drop_table(node)
g.sql_drop_table(node, expr)
return
}
g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name
typ := g.parse_db_type(node.db_expr)
typ := g.parse_db_type(expr)
match typ {
.sqlite3 {
g.sqlite3_stmt(node, typ)
g.sqlite3_stmt(node, typ, expr)
}
.mysql {
g.mysql_stmt(node, typ)
g.mysql_stmt(node, typ, expr)
}
.psql {
g.psql_stmt(node, typ)
g.psql_stmt(node, typ, expr)
}
else {
verror('This database type `$typ` is not implemented yet in orm') // TODO add better error
@ -49,17 +55,17 @@ fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
}
}
fn (mut g Gen) sql_create_table(node ast.SqlStmt) {
typ := g.parse_db_type(node.db_expr)
fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr ast.Expr) {
typ := g.parse_db_type(expr)
match typ {
.sqlite3 {
g.sqlite3_create_table(node, typ)
g.sqlite3_create_table(node, typ, expr)
}
.mysql {
g.mysql_create_table(node, typ)
g.mysql_create_table(node, typ, expr)
}
.psql {
g.psql_create_table(node, typ)
g.psql_create_table(node, typ, expr)
}
else {
verror('This database type `$typ` is not implemented yet in orm') // TODO add better error
@ -67,17 +73,17 @@ fn (mut g Gen) sql_create_table(node ast.SqlStmt) {
}
}
fn (mut g Gen) sql_drop_table(node ast.SqlStmt) {
typ := g.parse_db_type(node.db_expr)
fn (mut g Gen) sql_drop_table(node ast.SqlStmtLine, expr ast.Expr) {
typ := g.parse_db_type(expr)
match typ {
.sqlite3 {
g.sqlite3_drop_table(node, typ)
g.sqlite3_drop_table(node, typ, expr)
}
.mysql {
g.mysql_drop_table(node, typ)
g.mysql_drop_table(node, typ, expr)
}
.psql {
g.psql_create_table(node, typ)
g.psql_create_table(node, typ, expr)
}
else {
verror('This database type `$typ` is not implemented yet in orm') // TODO add better error
@ -136,13 +142,13 @@ fn (mut g Gen) sql_type_from_v(typ SqlType, v_typ ast.Type) string {
// sqlite3
fn (mut g Gen) sqlite3_stmt(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) sqlite3_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
g.sql_i = 0
g.writeln('\n\t// sql insert')
db_name := g.new_tmp_var()
g.sql_stmt_name = g.new_tmp_var()
g.write('${c.dbtype}__DB $db_name = ')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(';')
g.write('sqlite3_stmt* $g.sql_stmt_name = ${c.dbtype}__DB_init_stmt($db_name, _SLIT("')
g.sql_defaults(node, typ)
@ -161,7 +167,7 @@ fn (mut g Gen) sqlite3_stmt(node ast.SqlStmt, typ SqlType) {
expr := node.sub_structs[int(field.typ)]
tmp_sql_stmt_name := g.sql_stmt_name
tmp_sql_table_name := g.sql_table_name
g.sql_stmt(expr)
g.sql_stmt_line(expr, db_expr)
g.sql_stmt_name = tmp_sql_stmt_name
g.sql_table_name = tmp_sql_table_name
// get last inserted id
@ -326,20 +332,20 @@ fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_
}
}
fn (mut g Gen) sqlite3_create_table(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) sqlite3_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
g.writeln('// sqlite3 table creator')
create_string := g.table_gen(node, typ)
create_string := g.table_gen(node, typ, db_expr)
g.write('sqlite__DB_exec(')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(', _SLIT("$create_string"));')
}
fn (mut g Gen) sqlite3_drop_table(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) sqlite3_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
table_name := g.get_table_name(node.table_expr)
g.writeln('// sqlite3 table drop')
drop_string := 'DROP TABLE `$table_name`;'
g.write('sqlite__DB_exec(')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(', _SLIT("$drop_string"));')
}
@ -377,13 +383,13 @@ fn (mut g Gen) sqlite3_type_from_v(v_typ ast.Type) string {
// mysql
fn (mut g Gen) mysql_stmt(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) mysql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
g.sql_i = 0
g.writeln('\n\t//mysql insert')
db_name := g.new_tmp_var()
g.sql_stmt_name = g.new_tmp_var()
g.write('mysql__Connection $db_name = ')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(';')
stmt_name := g.new_tmp_var()
g.write('string $stmt_name = _SLIT("')
@ -407,7 +413,7 @@ fn (mut g Gen) mysql_stmt(node ast.SqlStmt, typ SqlType) {
expr := node.sub_structs[int(field.typ)]
tmp_sql_stmt_name := g.sql_stmt_name
tmp_sql_table_name := g.sql_table_name
g.sql_stmt(expr)
g.sql_stmt_line(expr, db_expr)
g.sql_stmt_name = tmp_sql_stmt_name
g.sql_table_name = tmp_sql_table_name
@ -618,23 +624,23 @@ fn (mut g Gen) mysql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sq
}
}
fn (mut g Gen) mysql_create_table(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) mysql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
g.writeln('// mysql table creator')
create_string := g.table_gen(node, typ)
create_string := g.table_gen(node, typ, db_expr)
tmp := g.new_tmp_var()
g.write('Option_mysql__Result $tmp = mysql__Connection_query(&')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(', _SLIT("$create_string"));')
g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }')
}
fn (mut g Gen) mysql_drop_table(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) mysql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
table_name := g.get_table_name(node.table_expr)
g.writeln('// mysql table drop')
drop_string := 'DROP TABLE `$table_name`;'
tmp := g.new_tmp_var()
g.write('Option_mysql__Result $tmp = mysql__Connection_query(&')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(', _SLIT("$drop_string"));')
g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }')
}
@ -735,7 +741,7 @@ fn (mut g Gen) mysql_buffer_typ_from_field(field ast.StructField) (string, strin
// psql
fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
g.sql_i = 0
g.sql_idents = []string{}
param_values := g.new_tmp_var()
@ -745,7 +751,7 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) {
db_name := g.new_tmp_var()
g.sql_stmt_name = g.new_tmp_var()
g.write('pg__DB $db_name = ')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(';')
stmt_name := g.new_tmp_var()
g.write('string $stmt_name = _SLIT("')
@ -769,7 +775,7 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) {
expr := node.sub_structs[int(field.typ)]
tmp_sql_stmt_name := g.sql_stmt_name
tmp_sql_table_name := g.sql_table_name
g.sql_stmt(expr)
g.sql_stmt_line(expr, db_expr)
g.sql_stmt_name = tmp_sql_stmt_name
g.sql_table_name = tmp_sql_table_name
@ -803,23 +809,23 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) {
g.writeln('if (${res}_rows.state != 0) { IError err = ${res}_rows.err; eprintln(_STR("\\000%.*s", 2, IError_str(err))); }')
}
fn (mut g Gen) psql_create_table(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) psql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
g.writeln('// psql table creator')
create_string := g.table_gen(node, typ)
create_string := g.table_gen(node, typ, db_expr)
tmp := g.new_tmp_var()
g.write('Option_Array_pg__Row $tmp = pg__DB_exec(')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(', _SLIT("$create_string"));')
g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }')
}
fn (mut g Gen) psql_drop_table(node ast.SqlStmt, typ SqlType) {
fn (mut g Gen) psql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) {
table_name := g.get_table_name(node.table_expr)
g.writeln('// psql table drop')
drop_string := 'DROP TABLE "$table_name";'
tmp := g.new_tmp_var()
g.write('Option_Array_pg__Row $tmp = pg__DB_exec(&')
g.expr(node.db_expr)
g.expr(db_expr)
g.writeln(', _SLIT("$drop_string"));')
g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }')
}
@ -929,7 +935,7 @@ fn (mut g Gen) get_base_sql_select_query(node ast.SqlExpr) string {
return sql_query
}
fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType, psql_data ...string) {
fn (mut g Gen) sql_defaults(node ast.SqlStmtLine, typ SqlType, psql_data ...string) {
table_name := g.get_table_name(node.table_expr)
mut lit := '`'
if typ == .psql {
@ -981,7 +987,7 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType, psql_data ...string)
g.write(';")')
}
fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string {
fn (mut g Gen) table_gen(node ast.SqlStmtLine, typ SqlType, expr ast.Expr) string {
typ_sym := g.table.get_type_symbol(node.table_expr.typ)
struct_data := typ_sym.struct_info()
table_name := g.get_table_name(node.table_expr)
@ -1032,15 +1038,14 @@ fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string {
if converted_typ == '' {
if g.table.get_type_symbol(field.typ).kind == .struct_ {
converted_typ = g.sql_type_from_v(typ, ast.int_type)
g.sql_create_table(ast.SqlStmt{
db_expr: node.db_expr
g.sql_create_table(ast.SqlStmtLine{
kind: node.kind
pos: node.pos
table_expr: ast.TypeNode{
typ: field.typ
pos: node.table_expr.pos
}
})
}, expr)
} else {
verror('unknown type ($field.typ) for field $field.name in struct $table_name')
continue

View File

@ -97,8 +97,10 @@ pub fn (mut w Walker) stmt(node ast.Stmt) {
}
ast.SqlStmt {
w.expr(node.db_expr)
w.expr(node.where_expr)
w.exprs(node.update_exprs)
for line in node.lines {
w.expr(line.where_expr)
w.exprs(line.update_exprs)
}
}
ast.StructDecl {
w.struct_fields(node.fields)

View File

@ -125,9 +125,25 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
}
// println(typeof(db_expr))
p.check(.lcbr)
// kind := ast.SqlExprKind.select_
//
mut lines := []ast.SqlStmtLine{}
for p.tok.kind != .rcbr {
lines << p.parse_sql_stmt_line()
}
p.next()
pos.last_line = p.prev_tok.line_nr
return ast.SqlStmt{
pos: pos.extend(p.prev_tok.position())
db_expr: db_expr
lines: lines
}
}
fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine {
mut n := p.check_name() // insert
pos := p.tok.position()
mut kind := ast.SqlStmtKind.insert
if n == 'delete' {
kind = .delete
@ -138,13 +154,11 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
table := p.check_name()
if table != 'table' {
p.error('expected `table` got `$table`')
return ast.SqlStmt{}
return ast.SqlStmtLine{}
}
typ := p.parse_type()
typ_pos := p.tok.position()
p.check(.rcbr)
return ast.SqlStmt{
db_expr: db_expr
return ast.SqlStmtLine{
kind: kind
pos: pos.extend(p.prev_tok.position())
table_expr: ast.TypeNode{
@ -157,13 +171,11 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
table := p.check_name()
if table != 'table' {
p.error('expected `table` got `$table`')
return ast.SqlStmt{}
return ast.SqlStmtLine{}
}
typ := p.parse_type()
typ_pos := p.tok.position()
p.check(.rcbr)
return ast.SqlStmt{
db_expr: db_expr
return ast.SqlStmtLine{
kind: kind
pos: pos.extend(p.prev_tok.position())
table_expr: ast.TypeNode{
@ -183,7 +195,7 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
inserted_var_name = expr.name
} else {
p.error('can only insert variables')
return ast.SqlStmt{}
return ast.SqlStmtLine{}
}
}
}
@ -192,11 +204,11 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
mut update_exprs := []ast.Expr{cap: 5}
if kind == .insert && n != 'into' {
p.error('expecting `into`')
return ast.SqlStmt{}
return ast.SqlStmtLine{}
} else if kind == .update {
if n != 'set' {
p.error('expecting `set`')
return ast.SqlStmt{}
return ast.SqlStmtLine{}
}
for {
column := p.check_name()
@ -211,7 +223,7 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
}
} else if kind == .delete && n != 'from' {
p.error('expecting `from`')
return ast.SqlStmt{}
return ast.SqlStmtLine{}
}
mut table_pos := p.tok.position()
@ -220,24 +232,21 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
table_pos = p.tok.position()
table_type = p.parse_type()
} else if kind == .update {
p.check_sql_keyword('where') or { return ast.SqlStmt{} }
p.check_sql_keyword('where') or { return ast.SqlStmtLine{} }
where_expr = p.expr(0)
} else if kind == .delete {
table_pos = p.tok.position()
table_type = p.parse_type()
p.check_sql_keyword('where') or { return ast.SqlStmt{} }
p.check_sql_keyword('where') or { return ast.SqlStmtLine{} }
where_expr = p.expr(0)
}
p.check(.rcbr)
pos.last_line = p.prev_tok.line_nr
return ast.SqlStmt{
db_expr: db_expr
return ast.SqlStmtLine{
table_expr: ast.TypeNode{
typ: table_type
pos: table_pos
}
object_var_name: inserted_var_name
pos: pos.extend(p.prev_tok.position())
pos: pos
updated_columns: updated_columns
update_exprs: update_exprs
kind: kind