From 6110373519ea470cd4dc0a613a76871fe45041ed Mon Sep 17 00:00:00 2001 From: Louis Schmieder Date: Wed, 31 Aug 2022 13:43:20 +0200 Subject: [PATCH] orm: init or implementation (#14989) --- cmd/tools/vtest-self.v | 2 + vlib/orm/orm_sql_or_blocks_test.v | 83 +++++++++++++ vlib/sqlite/sqlite.v | 55 ++++++--- vlib/sqlite/stmt.v | 30 ++--- vlib/v/ast/ast.v | 4 +- vlib/v/checker/orm.v | 17 +++ vlib/v/checker/tests/orm_no_default_value.out | 7 ++ vlib/v/checker/tests/orm_no_default_value.vv | 13 ++ vlib/v/fmt/fmt.v | 6 +- vlib/v/fmt/tests/orm_or_keep.vv | 19 +++ vlib/v/gen/c/cgen.v | 1 + vlib/v/gen/c/fn.v | 3 +- vlib/v/gen/c/sql.v | 116 +++++++++++------- vlib/v/parser/expr.v | 2 - vlib/v/parser/sql.v | 40 ++++++ 15 files changed, 312 insertions(+), 86 deletions(-) create mode 100644 vlib/orm/orm_sql_or_blocks_test.v create mode 100644 vlib/v/checker/tests/orm_no_default_value.out create mode 100644 vlib/v/checker/tests/orm_no_default_value.vv create mode 100644 vlib/v/fmt/tests/orm_or_keep.vv diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index dd720e3c6..2224cd334 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -119,6 +119,7 @@ const ( 'vlib/net/udp_test.v', 'vlib/net/tcp_test.v', 'vlib/orm/orm_test.v', + 'vlib/orm/orm_sql_or_blocks_test.v', 'vlib/sqlite/sqlite_test.v', 'vlib/sqlite/sqlite_orm_test.v', 'vlib/v/tests/orm_sub_struct_test.v', @@ -163,6 +164,7 @@ const ( 'vlib/sqlite/sqlite_test.v', 'vlib/sqlite/sqlite_orm_test.v', 'vlib/orm/orm_test.v', + 'vlib/orm/orm_sql_or_blocks_test.v', 'vlib/v/tests/orm_sub_struct_test.v', 'vlib/v/tests/orm_sub_array_struct_test.v', 'vlib/v/tests/orm_joined_tables_select_test.v', diff --git a/vlib/orm/orm_sql_or_blocks_test.v b/vlib/orm/orm_sql_or_blocks_test.v new file mode 100644 index 000000000..bb62a70d6 --- /dev/null +++ b/vlib/orm/orm_sql_or_blocks_test.v @@ -0,0 +1,83 @@ +import os +import sqlite + +struct User { + id i64 [primary; sql: serial] + name string [unique] +} + +const db_path = os.join_path(os.temp_dir(), 'sql_statement_or_blocks.db') + +fn test_ensure_db_exists_and_user_table_is_ok() ? { + db := sqlite.connect(db_path)? + assert true + + eprintln('> drop pre-existing User table...') + db.exec('drop table if exists User') + + eprintln('> creating User table...') + sql db { + create table User + } or { panic(err) } + assert true +} + +fn test_sql_or_block_for_insert() ? { + db := sqlite.connect(db_path)? + user := User{1, 'bilbo'} + + eprintln('> inserting user 1 (first try)...') + sql db { + insert user into User + } or { + println('user should have been inserted, but could not, err: $err') + assert false + } + + eprintln('> inserting user 1 (second try)...') + sql db { + insert user into User + } or { + assert true + println('user could not be inserted, err: $err') + } +} + +fn test_sql_or_block_for_select() ? { + db := sqlite.connect(db_path)? + + eprintln('> selecting user with id 1...') + single := sql db { + select from User where id == 1 + } or { + eprintln('could not select user, err: $err') + User{0, ''} + } + + assert single.id == 1 + + failed := sql db { + select from User where id == 0 + } or { + eprintln('could not select user, err: $err') + User{0, ''} + } + + assert failed.id == 0 + assert failed.name == '' + + eprintln('> selecting users...') + multiple := sql db { + select from User + } or { + eprintln('could not users, err: $err') + []User{} + } + + assert multiple.len == 1 +} + +fn test_finish() ? { + eprintln('done') + assert true +} diff --git a/vlib/sqlite/sqlite.v b/vlib/sqlite/sqlite.v index b4a797e0a..16f408302 100644 --- a/vlib/sqlite/sqlite.v +++ b/vlib/sqlite/sqlite.v @@ -41,6 +41,7 @@ struct C.sqlite3 { struct C.sqlite3_stmt { } +[heap] struct Stmt { stmt &C.sqlite3_stmt db &DB @@ -51,6 +52,7 @@ struct SQLError { } // +[heap] pub struct DB { pub mut: is_open bool @@ -58,7 +60,7 @@ mut: conn &C.sqlite3 } -pub fn (db DB) str() string { +pub fn (db &DB) str() string { return 'sqlite.DB{ conn: ' + ptr_str(db.conn) + ' }' } @@ -149,23 +151,25 @@ fn get_int_from_stmt(stmt &C.sqlite3_stmt) int { // Returns last insert rowid // https://www.sqlite.org/c3ref/last_insert_rowid.html -pub fn (db DB) last_insert_rowid() i64 { +pub fn (db &DB) last_insert_rowid() i64 { return C.sqlite3_last_insert_rowid(db.conn) } // Returns a single cell with value int. -pub fn (db DB) q_int(query string) int { +pub fn (db &DB) q_int(query string) int { stmt := &C.sqlite3_stmt(0) + defer { + C.sqlite3_finalize(stmt) + } C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) C.sqlite3_step(stmt) res := C.sqlite3_column_int(stmt, 0) - C.sqlite3_finalize(stmt) return res } // Returns a single cell with value string. -pub fn (db DB) q_string(query string) string { +pub fn (db &DB) q_string(query string) string { stmt := &C.sqlite3_stmt(0) defer { C.sqlite3_finalize(stmt) @@ -179,8 +183,12 @@ pub fn (db DB) q_string(query string) string { // Execute the query on db, return an array of all the results, alongside any result code. // Result codes: https://www.sqlite.org/rescode.html -pub fn (db DB) exec(query string) ([]Row, int) { +[manualfree] +pub fn (db &DB) exec(query string) ([]Row, int) { stmt := &C.sqlite3_stmt(0) + defer { + C.sqlite3_finalize(stmt) + } C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) nr_cols := C.sqlite3_column_count(stmt) mut res := 0 @@ -203,14 +211,17 @@ pub fn (db DB) exec(query string) ([]Row, int) { } rows << row } - C.sqlite3_finalize(stmt) return rows, res } // Execute a query, handle error code // Return the first row from the resulting table -pub fn (db DB) exec_one(query string) ?Row { +[manualfree] +pub fn (db &DB) exec_one(query string) ?Row { rows, code := db.exec(query) + defer { + unsafe { rows.free() } + } if rows.len == 0 { return IError(&SQLError{ msg: 'No rows' @@ -222,21 +233,25 @@ pub fn (db DB) exec_one(query string) ?Row { code: code }) } - return rows[0] + res := rows[0] + return res } -pub fn (db DB) error_message(code int, query string) IError { - msg := unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) } - return IError(&SQLError{ - msg: '$msg ($code) ($query)' +[manualfree] +pub fn (db &DB) error_message(code int, query string) IError { + errmsg := unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) } + msg := '$errmsg ($code) ($query)' + unsafe { errmsg.free() } + return SQLError{ + msg: msg code: code - }) + } } // Execute a query returning only the result code. // In case you don't expect any row results, but still want a result code. // e.g. INSERT INTO ... VALUES (...) -pub fn (db DB) exec_none(query string) int { +pub fn (db &DB) exec_none(query string) int { stmt := &C.sqlite3_stmt(0) C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) code := C.sqlite3_step(stmt) @@ -246,14 +261,14 @@ pub fn (db DB) exec_none(query string) int { /* TODO -pub fn (db DB) exec_param(query string, param string) []Row { +pub fn (db &DB) exec_param(query string, param string) []Row { } */ // Issue a "create table if not exists" command to the db. // Creates table named 'table_name', with columns generated from 'columns' array. // Default columns type will be TEXT. -pub fn (db DB) create_table(table_name string, columns []string) { +pub fn (db &DB) create_table(table_name string, columns []string) { db.exec('create table if not exists $table_name (' + columns.join(',\n') + ')') } @@ -261,7 +276,7 @@ pub fn (db DB) create_table(table_name string, columns []string) { // Sleeps for a specified amount of time when a table is locked. The handler // will sleep multiple times until at least "ms" milliseconds of sleeping have accumulated. // (see https://www.sqlite.org/c3ref/busy_timeout.html) -pub fn (db DB) busy_timeout(ms int) int { +pub fn (db &DB) busy_timeout(ms int) int { return C.sqlite3_busy_timeout(db.conn, ms) } @@ -270,7 +285,7 @@ pub fn (db DB) busy_timeout(ms int) int { // off: No syncs at all. (fastest) // normal: Sync after each sequence of critical disk operations. // full: Sync after each critical disk operation (slowest). -pub fn (db DB) synchronization_mode(sync_mode SyncMode) { +pub fn (db &DB) synchronization_mode(sync_mode SyncMode) { if sync_mode == .off { db.exec('pragma synchronous = OFF;') } else if sync_mode == .full { @@ -286,7 +301,7 @@ pub fn (db DB) synchronization_mode(sync_mode SyncMode) { // delete: At the conclusion of a transaction, journal file is deleted. // truncate: Journal file is truncated to a length of zero bytes. // persist: Journal file is left in place, but the header is overwritten to indicate journal is no longer valid. -pub fn (db DB) journal_mode(journal_mode JournalMode) { +pub fn (db &DB) journal_mode(journal_mode JournalMode) { if journal_mode == .off { db.exec('pragma journal_mode = OFF;') } else if journal_mode == .delete { diff --git a/vlib/sqlite/stmt.v b/vlib/sqlite/stmt.v index cea2605e5..4974823d8 100644 --- a/vlib/sqlite/stmt.v +++ b/vlib/sqlite/stmt.v @@ -6,50 +6,50 @@ fn C.sqlite3_bind_int64(&C.sqlite3_stmt, int, i64) int fn C.sqlite3_bind_text(&C.sqlite3_stmt, int, &char, int, voidptr) int // Only for V ORM -fn (db DB) init_stmt(query string) (&C.sqlite3_stmt, int) { +fn (db &DB) init_stmt(query string) (&C.sqlite3_stmt, int) { // println('init_stmt("$query")') stmt := &C.sqlite3_stmt(0) err := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) return stmt, err } -fn (db DB) new_init_stmt(query string) ?Stmt { +fn (db &DB) new_init_stmt(query string) ?Stmt { stmt, err := db.init_stmt(query) if err != sqlite_ok { return db.error_message(err, query) } - return Stmt{stmt, unsafe { &db }} + return Stmt{stmt, db} } -fn (stmt Stmt) bind_int(idx int, v int) int { +fn (stmt &Stmt) bind_int(idx int, v int) int { return C.sqlite3_bind_int(stmt.stmt, idx, v) } -fn (stmt Stmt) bind_i64(idx int, v i64) int { +fn (stmt &Stmt) bind_i64(idx int, v i64) int { return C.sqlite3_bind_int64(stmt.stmt, idx, v) } -fn (stmt Stmt) bind_f64(idx int, v f64) int { +fn (stmt &Stmt) bind_f64(idx int, v f64) int { return C.sqlite3_bind_double(stmt.stmt, idx, v) } -fn (stmt Stmt) bind_text(idx int, s string) int { +fn (stmt &Stmt) bind_text(idx int, s string) int { return C.sqlite3_bind_text(stmt.stmt, idx, voidptr(s.str), s.len, 0) } -fn (stmt Stmt) get_int(idx int) int { +fn (stmt &Stmt) get_int(idx int) int { return C.sqlite3_column_int(stmt.stmt, idx) } -fn (stmt Stmt) get_i64(idx int) i64 { +fn (stmt &Stmt) get_i64(idx int) i64 { return C.sqlite3_column_int64(stmt.stmt, idx) } -fn (stmt Stmt) get_f64(idx int) f64 { +fn (stmt &Stmt) get_f64(idx int) f64 { return C.sqlite3_column_double(stmt.stmt, idx) } -fn (stmt Stmt) get_text(idx int) string { +fn (stmt &Stmt) get_text(idx int) string { b := &char(C.sqlite3_column_text(stmt.stmt, idx)) if b == &char(0) { @@ -58,21 +58,21 @@ fn (stmt Stmt) get_text(idx int) string { return unsafe { b.vstring() } } -fn (stmt Stmt) get_count() int { +fn (stmt &Stmt) get_count() int { return C.sqlite3_column_count(stmt.stmt) } -fn (stmt Stmt) step() int { +fn (stmt &Stmt) step() int { return C.sqlite3_step(stmt.stmt) } -fn (stmt Stmt) orm_step(query string) ? { +fn (stmt &Stmt) orm_step(query string) ? { res := stmt.step() if res != sqlite_ok && res != sqlite_done && res != sqlite_row { return stmt.db.error_message(res, query) } } -fn (stmt Stmt) finalize() { +fn (stmt &Stmt) finalize() { C.sqlite3_finalize(stmt.stmt) } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 6784712b7..61381bfb6 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1711,6 +1711,7 @@ pub struct SqlStmt { pub: pos token.Pos db_expr Expr // `db` in `sql db {` + or_expr OrExpr pub mut: lines []SqlStmtLine } @@ -1731,7 +1732,6 @@ pub mut: pub struct SqlExpr { pub: - typ Type is_count bool has_where bool has_order bool @@ -1739,8 +1739,10 @@ pub: has_offset bool has_desc bool is_array bool + or_expr OrExpr pos token.Pos pub mut: + typ Type db_expr Expr // `db` in `sql db {` where_expr Expr order_expr Expr diff --git a/vlib/v/checker/orm.v b/vlib/v/checker/orm.v index 2cf26f5d5..0392d2499 100644 --- a/vlib/v/checker/orm.v +++ b/vlib/v/checker/orm.v @@ -103,6 +103,18 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { c.expr(node.order_expr) } c.expr(node.db_expr) + + if node.or_expr.kind == .block { + if node.or_expr.stmts.len == 0 { + c.error('Or block needs to return a default value', node.or_expr.pos) + } + if node.or_expr.stmts.len > 0 && node.or_expr.stmts.last() is ast.ExprStmt { + c.expected_or_type = node.typ + } + c.stmts_ending_with_expression(node.or_expr.stmts) + c.check_expr_opt_call(node, node.typ) + c.expected_or_type = ast.void_type + } return node.typ } @@ -115,6 +127,11 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type { typ = a } } + if node.or_expr.kind == .block { + for s in node.or_expr.stmts { + c.stmt(s) + } + } return typ } diff --git a/vlib/v/checker/tests/orm_no_default_value.out b/vlib/v/checker/tests/orm_no_default_value.out new file mode 100644 index 000000000..de33096b3 --- /dev/null +++ b/vlib/v/checker/tests/orm_no_default_value.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/orm_no_default_value.vv:11:4: error: Or block needs to return a default value + 9 | _ := sql db { + 10 | select from Person + 11 | } or { + | ~~~~ + 12 | } + 13 | } diff --git a/vlib/v/checker/tests/orm_no_default_value.vv b/vlib/v/checker/tests/orm_no_default_value.vv new file mode 100644 index 000000000..d7557d7af --- /dev/null +++ b/vlib/v/checker/tests/orm_no_default_value.vv @@ -0,0 +1,13 @@ +import sqlite + +struct Person { + id int [primary; sql: serial] +} + +fn main() { + db := sqlite.connect(':memory:')? + _ := sql db { + select from Person + } or { + } +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 5192b36e7..68b839e38 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1256,8 +1256,9 @@ pub fn (mut f Fmt) sql_stmt(node ast.SqlStmt) { for line in node.lines { f.sql_stmt_line(line) } - - f.writeln('}') + f.write('}') + f.or_expr(node.or_expr) + f.writeln('') } pub fn (mut f Fmt) sql_stmt_line(node ast.SqlStmtLine) { @@ -2562,6 +2563,7 @@ pub fn (mut f Fmt) sql_expr(node ast.SqlExpr) { } f.writeln('') f.write('}') + f.or_expr(node.or_expr) } pub fn (mut f Fmt) char_literal(node ast.CharLiteral) { diff --git a/vlib/v/fmt/tests/orm_or_keep.vv b/vlib/v/fmt/tests/orm_or_keep.vv new file mode 100644 index 000000000..a0f60c3f0 --- /dev/null +++ b/vlib/v/fmt/tests/orm_or_keep.vv @@ -0,0 +1,19 @@ +import sqlite + +struct User { + id i64 [primary; sql: serial] + name string [unique] +} + +fn main() { + db := sqlite.connect(':memory:')? + sql db { + create table User + } or { panic(err) } + sql db { + insert user into User + } or { + println('user should have been inserted, but could not, err: $err') + exit(1) + } +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 2919ba0e1..b481945fc 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1671,6 +1671,7 @@ fn (mut g Gen) write_v_source_line_info(pos token.Pos) { } fn (mut g Gen) stmt(node ast.Stmt) { + g.inside_call = false if !g.skip_stmt_pos { g.set_current_pos_as_last_stmt_pos() } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index b53af8918..ccfd871ab 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -626,9 +626,10 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { if node.should_be_skipped { return } + old_inside_call := g.inside_call g.inside_call = true defer { - g.inside_call = false + g.inside_call = old_inside_call } gen_keep_alive := node.is_keep_alive && node.return_type != ast.void_type && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt] diff --git a/vlib/v/gen/c/sql.v b/vlib/v/gen/c/sql.v index 8c84343c7..4861578a6 100644 --- a/vlib/v/gen/c/sql.v +++ b/vlib/v/gen/c/sql.v @@ -43,17 +43,16 @@ fn (mut g Gen) sql_stmt(node ast.SqlStmt) { g.expr(node.db_expr) g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') for line in node.lines { - g.sql_stmt_line(line, conn) + g.sql_stmt_line(line, conn, node.or_expr) } } -fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string) { +fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string, or_expr ast.OrExpr) { mut node := nd table_name := g.get_table_name(node.table_expr) g.sql_table_name = g.table.sym(node.table_expr.typ).name res := g.new_tmp_var() mut subs := false - mut dcheck := false if node.kind != .create { mut fields := []ast.StructField{} @@ -75,7 +74,6 @@ fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string) { node.fields = fields.clone() unsafe { fields.free() } } - if node.kind == .create { g.write('${option_name}_void $res = orm__Connection_name_table[${expr}._typ]._method_') g.sql_create_table(node, expr, table_name) @@ -86,9 +84,8 @@ fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string) { subs = true } else if node.kind == .insert { arr := g.new_tmp_var() - g.writeln('Array_orm__Primitive $arr = new_array_from_c_array(0, 0, sizeof(orm__Primitive), NULL);') - g.sql_insert(node, expr, table_name, arr, res, '', false, '') - dcheck = true + g.writeln('Array_orm__Primitive $arr = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);') + g.sql_insert(node, expr, table_name, arr, res, '', false, '', or_expr) } else if node.kind == .update { g.write('${option_name}_void $res = orm__Connection_name_table[${expr}._typ]._method_') g.sql_update(node, expr, table_name) @@ -96,12 +93,12 @@ fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string) { g.write('${option_name}_void $res = orm__Connection_name_table[${expr}._typ]._method_') g.sql_delete(node, expr, table_name) } - if !dcheck { - g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') + if or_expr.kind == .block { + g.or_block(res, or_expr, ast.int_type) } if subs { for _, sub in node.sub_structs { - g.sql_stmt_line(sub, expr) + g.sql_stmt_line(sub, expr, or_expr) } } } @@ -147,7 +144,7 @@ fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr string, table_name st g.writeln('));') } -fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, is_array bool, fkey string) { +fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, is_array bool, fkey string, or_expr ast.OrExpr) { mut subs := []ast.SqlStmtLine{} mut arrs := []ast.SqlStmtLine{} mut fkeys := []string{} @@ -181,7 +178,7 @@ fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, fields := node.fields.filter(g.table.sym(it.typ).kind != .array) for sub in subs { - g.sql_stmt_line(sub, expr) + g.sql_stmt_line(sub, expr, or_expr) g.writeln('array_push(&$last_ids_arr, _MOV((orm__Primitive[]){orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object)}));') } @@ -226,12 +223,11 @@ fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, g.write('NULL') } g.write('),') - g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') - g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') - g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),') + g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),') + g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),') g.writeln('});') - g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') if arrs.len > 0 { mut id_name := g.new_tmp_var() g.writeln('orm__Primitive $id_name = orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object);') @@ -263,7 +259,7 @@ fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, arr.fields = fff.clone() unsafe { fff.free() } g.sql_insert(arr, expr, g.get_table_name(arr.table_expr), last_ids, res_, - id_name, true, fkeys[i]) + id_name, true, fkeys[i], or_expr) g.writeln('}') } } @@ -274,18 +270,18 @@ fn (mut g Gen) sql_update(node ast.SqlStmtLine, expr string, table_name string) // println(expr) // println(node) g.write('update(${expr}._object, _SLIT("$table_name"), (orm__QueryData){') - g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') - g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') - g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') - g.write('.fields = new_array_from_c_array($node.updated_columns.len, $node.updated_columns.len, sizeof(string),') + g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),') + g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),') + g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),') if node.updated_columns.len > 0 { + g.write('.fields = new_array_from_c_array($node.updated_columns.len, $node.updated_columns.len, sizeof(string),') g.write(' _MOV((string[$node.updated_columns.len]){') for field in node.updated_columns { g.write('_SLIT("$field"),') } g.write('})') } else { - g.write('NULL') + g.write('.fields = __new_array_with_default_noscan($node.updated_columns.len, $node.updated_columns.len, sizeof(string), 0') } g.write('),') g.write('.data = new_array_from_c_array($node.update_exprs.len, $node.update_exprs.len, sizeof(orm__Primitive),') @@ -453,16 +449,16 @@ fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) { mut data := []ast.Expr{} mut is_and := []bool{} g.sql_where_data(where_expr, mut fields, mut kinds, mut data, mut is_and) - g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') - g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') + g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),') if fields.len > 0 { + g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') g.write(' _MOV((string[$fields.len]){') for field in fields { g.write('_SLIT("$field"),') } g.write('})') } else { - g.write('NULL') + g.write('.fields = __new_array_with_default_noscan($fields.len, $fields.len, sizeof(string), 0') } g.write('),') @@ -476,27 +472,27 @@ fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) { } g.write('),') - g.write('.kinds = new_array_from_c_array($kinds.len, $kinds.len, sizeof(orm__OperationKind),') if kinds.len > 0 { + g.write('.kinds = new_array_from_c_array($kinds.len, $kinds.len, sizeof(orm__OperationKind),') g.write(' _MOV((orm__OperationKind[$kinds.len]){') for k in kinds { g.write('$k,') } g.write('})') } else { - g.write('NULL') + g.write('.kinds = __new_array_with_default_noscan($kinds.len, $kinds.len, sizeof(orm__OperationKind), 0') } g.write('),') - g.write('.is_and = new_array_from_c_array($is_and.len, $is_and.len, sizeof(bool),') if is_and.len > 0 { + g.write('.is_and = new_array_from_c_array($is_and.len, $is_and.len, sizeof(bool),') g.write(' _MOV((bool[$is_and.len]){') for b in is_and { g.write('$b, ') } g.write('})') } else { - g.write('NULL') + g.write('.is_and = __new_array_with_default_noscan($is_and.len, $is_and.len, sizeof(bool), 0') } g.write('),}') } @@ -527,10 +523,10 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) { g.write('$fn_prefix = &') g.expr(node.db_expr) g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') - g.sql_select(node, conn, left) + g.sql_select(node, conn, left, node.or_expr) } -fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { +fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string, or_expr ast.OrExpr) { mut fields := []ast.StructField{} mut prim := '' for f in node.fields { @@ -615,32 +611,52 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { exprs << node.offset_expr } g.write('(orm__QueryData) {') - g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') - g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') - g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') - g.write('.data = new_array_from_c_array($exprs.len, $exprs.len, sizeof(orm__Primitive),') + g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),') + g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),') + g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),') if exprs.len > 0 { + g.write('.data = new_array_from_c_array($exprs.len, $exprs.len, sizeof(orm__Primitive),') g.write(' _MOV((orm__Primitive[$exprs.len]){') for e in exprs { g.sql_expr_to_orm_primitive(e) } g.write('})') } else { - g.write('NULL') + g.write('.data = __new_array_with_default_noscan($exprs.len, $exprs.len, sizeof(orm__Primitive), 0') } g.write(')},') if node.has_where { g.sql_gen_where_data(node.where_expr) } else { - g.write('(orm__QueryData) {}') + g.write('(orm__QueryData) {') + g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),') + g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),') + g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),') + g.write('.data = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0)') + g.write('}') } g.writeln(');') - g.writeln('if (_o${res}.state != 0 && _o${res}.err._typ != _IError_None___index) { _v_panic(IError_str(_o${res}.err)); }') + + mut tmp_left := g.new_tmp_var() + g.writeln('${g.typ(node.typ.set_flag(.optional))} $tmp_left;') + + if node.or_expr.kind == .block { + g.writeln('${tmp_left}.state = _o${res}.state;') + g.writeln('${tmp_left}.err = _o${res}.err;') + g.or_block(tmp_left, node.or_expr, node.typ) + g.writeln('else {') + g.indent++ + } + g.writeln('Array_Array_orm__Primitive $res = (*(Array_Array_orm__Primitive*)_o${res}.data);') if node.is_count { - g.writeln('$left *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get($res, 0)), 0))._int);') + g.writeln('*(${g.typ(node.typ)}*) ${tmp_left}.data = *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get($res, 0)), 0))._int);') + if node.or_expr.kind == .block { + g.indent-- + g.writeln('}') + } } else { tmp := g.new_tmp_var() styp := g.typ(node.typ) @@ -652,7 +668,8 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { typ_str = g.typ(info.elem_type) g.writeln('$styp ${tmp}_array = __new_array(0, ${res}.len, sizeof($typ_str));') g.writeln('for (; $idx < ${res}.len; $idx++) {') - g.write('\t$typ_str $tmp = ($typ_str) {') + g.indent++ + g.write('$typ_str $tmp = ($typ_str) {') inf := g.table.sym(info.elem_type).struct_info() for i, field in inf.fields { g.zero_struct_field(field) @@ -674,6 +691,7 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { } g.writeln('if (${res}.len > 0) {') + g.indent++ for i, field in fields { sel := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get($res, $idx)), $i))' sym := g.table.sym(field.typ) @@ -692,7 +710,7 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { where_expr.right = ident sub.where_expr = where_expr - g.sql_select(sub, expr, '${tmp}.$field.name = ') + g.sql_select(sub, expr, '${tmp}.$field.name = ', or_expr) } else if sym.kind == .array { mut fkey := '' for attr in field.attrs { @@ -740,27 +758,35 @@ fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { where_expr: where_expr } - g.sql_select(arr, expr, '${tmp}.$field.name = ') + g.sql_select(arr, expr, '${tmp}.$field.name = ', or_expr) } else { mut typ := sym.cname g.writeln('${tmp}.$field.name = *(${sel}._$typ);') } } + g.indent-- g.writeln('}') if node.is_array { g.writeln('array_push(&${tmp}_array, _MOV(($typ_str[]){ $tmp }));') + g.indent-- g.writeln('}') } - g.write('$left $tmp') + g.write('*(${g.typ(node.typ)}*) ${tmp_left}.data = $tmp') if node.is_array { g.write('_array') } - if !g.inside_call { - g.writeln(';') + g.writeln(';') + if node.or_expr.kind == .block { + g.indent-- + g.writeln('}') } } + g.write('$left *(${g.typ(node.typ)}*) ${tmp_left}.data') + if !g.inside_call { + g.writeln(';') + } } fn (mut g Gen) parse_db_type(expr ast.Expr) SqlType { diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 5fd29abbf..463fe2395 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -47,9 +47,7 @@ pub fn (mut p Parser) check_expr(precedence int) ?ast.Expr { } .name, .question { if p.tok.lit == 'sql' && p.peek_tok.kind == .name { - p.inside_match = true // reuse the same var for perf instead of inside_sql TODO rename node = p.sql_expr() - p.inside_match = false } else if p.tok.lit == 'map' && p.peek_tok.kind == .lcbr && !(p.builtin_mod && p.file_base in ['map.v', 'map_d_gcboehm_opt.v']) { p.error_with_pos("deprecated map syntax, use syntax like `{'age': 20}`", diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index a4fbf9f17..fb135f4f4 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -6,6 +6,8 @@ module parser import v.ast fn (mut p Parser) sql_expr() ast.Expr { + tmp_inside_match := p.inside_match + p.inside_match = true // `sql db {` pos := p.tok.pos() p.check_name() @@ -91,9 +93,13 @@ fn (mut p Parser) sql_expr() ast.Expr { typ = table_type } p.check(.rcbr) + p.inside_match = false + or_expr := p.parse_sql_or_block() + p.inside_match = tmp_inside_match return ast.SqlExpr{ is_count: is_count typ: typ + or_expr: or_expr db_expr: db_expr where_expr: where_expr has_where: has_where @@ -136,11 +142,45 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { } p.next() + + mut or_expr := p.parse_sql_or_block() + pos.last_line = p.prev_tok.line_nr return ast.SqlStmt{ pos: pos.extend(p.prev_tok.pos()) db_expr: db_expr lines: lines + or_expr: or_expr + } +} + +fn (mut p Parser) parse_sql_or_block() ast.OrExpr { + mut stmts := []ast.Stmt{} + mut kind := ast.OrKind.absent + mut pos := p.tok.pos() + + if p.tok.kind == .key_orelse { + was_inside_or_expr := p.inside_or_expr + p.inside_or_expr = true + p.next() + p.open_scope() + p.scope.register(ast.Var{ + name: 'err' + typ: ast.error_type + pos: p.tok.pos() + is_used: true + }) + kind = .block + stmts = p.parse_block_no_scope(false) + pos = pos.extend(p.prev_tok.pos()) + p.close_scope() + p.inside_or_expr = was_inside_or_expr + } + + return ast.OrExpr{ + stmts: stmts + kind: kind + pos: pos } } -- 2.30.2