1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

orm: init or implementation (#14989)

This commit is contained in:
Louis Schmieder 2022-08-31 13:43:20 +02:00 committed by GitHub
parent bc06866b20
commit 6110373519
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 312 additions and 86 deletions

View File

@ -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',

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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 | }

View File

@ -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 {
}
}

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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]

View File

@ -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 {

View File

@ -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}`",

View File

@ -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
}
}