v/vlib/v/gen/c/orm.v
2025-09-04 22:00:02 +08:00

1383 lines
44 KiB
V

// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module c
import v.ast
import v.util
enum SqlExprSide {
left
right
}
// All databases that are supported by ORM
// must implement the `Connection` interface from the file `vlib/orm/orm.v`
// The main function of the cgen for ORM is to identify the current database type,
// generate the necessary parameters for invoking its methods,
// and correctly process the results, saving them to a variable that the user can access.
// Essentially, it involves a desugaring of the ORM syntax.
// For example, if you write:
// ```v
// sql db {
// select from User
// }
// ```
// cgen will write calling the function `select` of the needed database.
// If you use sqlite, it calls `select` from `vlib/db/sqlite/orm.v`
// sql_select_expr writes C code that calls ORM functions for selecting objects
// from the database, which is used by the `select` query.
fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
// users :=
left := g.go_before_last_stmt()
connection_var_name := g.new_tmp_var()
g.writeln('')
g.write_orm_connection_init(connection_var_name, &node.db_expr)
result_var := g.new_tmp_var()
result_c_typ := g.styp(node.typ)
g.writeln('${result_c_typ} ${result_var};')
g.write_orm_select(node, connection_var_name, result_var)
unwrapped_c_typ := g.styp(node.typ.clear_flag(.result))
g.write('${left} *(${unwrapped_c_typ}*)${result_var}.data')
}
fn (mut g Gen) sql_insert_expr(node ast.SqlExpr) {
left := g.go_before_last_stmt()
g.writeln('')
connection_var_name := g.new_tmp_var()
g.write_orm_connection_init(connection_var_name, &node.db_expr)
table_name := g.get_table_name_by_struct_type(node.table_expr.typ)
table_attrs := g.get_table_attrs_by_struct_type(node.table_expr.typ)
result_var_name := g.new_tmp_var()
g.sql_table_name = g.table.sym(node.table_expr.typ).name
// orm_insert needs an SqlStmtLine, build it from SqlExpr (most nodes are the same)
hack_stmt_line := ast.SqlStmtLine{
object_var: node.inserted_var
fields: node.fields
table_expr: node.table_expr
// sub_structs: node.sub_structs
}
g.write_orm_insert(hack_stmt_line, table_name, connection_var_name, result_var_name,
node.or_expr, table_attrs)
g.write2(left, 'orm__Connection_name_table[${connection_var_name}._typ]._method_last_id(${connection_var_name}._object)')
}
// sql_stmt writes C code that calls ORM functions for
// performing various database operations such as creating and dropping tables,
// as well as inserting and updating objects.
// Can contain several queries. For example:
// ```v
// sql db {
// create table User
// insert user into User
// }!
// ```
// NOTE: Currently, in ORM only the `select` query is an expression.
// The others are statements.
fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
connection_var_name := g.new_tmp_var()
g.write_orm_connection_init(connection_var_name, &node.db_expr)
for line in node.lines {
g.sql_stmt_line(line, connection_var_name, node.or_expr)
}
}
// sql_stmt_line writes C code that calls ORM functions for
// performing various database operations such as creating and dropping tables,
// as well as inserting and updating objects.
// It is part of a multi-line query. For example, `create table User`
fn (mut g Gen) sql_stmt_line(stmt_line ast.SqlStmtLine, connection_var_name string, or_expr ast.OrExpr) {
g.sql_last_stmt_out_len = g.out.len
mut node := stmt_line
table_name := g.get_table_name_by_struct_type(node.table_expr.typ)
table_attrs := g.get_table_attrs_by_struct_type(node.table_expr.typ)
result_var_name := g.new_tmp_var()
g.sql_table_name = g.table.sym(node.table_expr.typ).name
if node.kind != .create {
node.fields = g.filter_struct_fields_by_orm_attrs(node.fields)
}
if node.kind == .create {
g.write_orm_create_table(node, table_name, connection_var_name, result_var_name,
table_attrs)
} else if node.kind == .drop {
g.write_orm_drop_table(node, table_name, connection_var_name, result_var_name,
table_attrs)
} else if node.kind == .insert {
g.write_orm_insert(node, table_name, connection_var_name, result_var_name, or_expr,
table_attrs)
} else if node.kind == .update {
g.write_orm_update(node, table_name, connection_var_name, result_var_name, table_attrs)
} else if node.kind == .delete {
g.write_orm_delete(node, table_name, connection_var_name, result_var_name, table_attrs)
}
g.or_block(result_var_name, or_expr, ast.int_type.set_flag(.result))
}
// write_orm_connection_init writes C code that saves the database connection
// into a variable for later use in ORM queries.
fn (mut g Gen) write_orm_connection_init(connection_var_name string, db_expr &ast.Expr) {
db_expr_type := g.get_db_expr_type(db_expr) or { verror('ORM: unknown db type for ${db_expr}') }
mut db_ctype_name := g.styp(db_expr_type)
is_pointer := db_ctype_name.ends_with('*')
reference_sign := if is_pointer { '' } else { '&' }
db_ctype_name = db_ctype_name.trim_right('*')
g.writeln('// ORM')
g.write('orm__Connection ${connection_var_name} = ')
if db_ctype_name == 'orm__Connection' {
g.expr(db_expr)
g.writeln(';')
} else {
g.write('(orm__Connection){._${db_ctype_name} = ${reference_sign}')
g.expr(db_expr)
g.writeln(', ._typ = _orm__Connection_${db_ctype_name}_index};')
}
}
// write_orm_table_struct writes C code for the orm.Table struct
fn (mut g Gen) write_orm_table_struct(typ ast.Type) {
table_name := g.get_table_name_by_struct_type(typ)
table_attrs := g.get_table_attrs_by_struct_type(typ)
g.writeln('((orm__Table){')
g.indent++
g.writeln('.name = _S("${table_name}"),')
g.writeln('.attrs = new_array_from_c_array(${table_attrs.len}, ${table_attrs.len}, sizeof(VAttribute),')
g.indent++
if table_attrs.len > 0 {
g.write('_MOV((VAttribute[${table_attrs.len}]){')
g.indent++
for attr in table_attrs {
g.write('(VAttribute){')
g.indent++
name1 := util.smart_quote(attr.name, false)
name := cescape_nonascii(name1)
g.write(' .name = _S("${name}"),')
g.write(' .has_arg = ${attr.has_arg},')
arg1 := util.smart_quote(attr.arg, false)
arg := cescape_nonascii(arg1)
g.write(' .arg = _S("${arg}"),')
g.write(' .kind = ${int(attr.kind)},')
g.indent--
g.write('},')
}
g.indent--
g.writeln('})')
} else {
g.writeln('NULL // No attrs')
}
g.indent--
g.writeln(')')
g.indent--
g.write('})')
}
// write_orm_create_table writes C code that calls ORM functions for creating tables.
fn (mut g Gen) write_orm_create_table(node ast.SqlStmtLine, table_name string, connection_var_name string,
result_var_name string, table_attrs []ast.Attr) {
g.writeln('// sql { create table `${table_name}` }')
g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_create(')
g.indent++
g.writeln('${connection_var_name}._object, // Connection object')
g.write_orm_table_struct(node.table_expr.typ)
g.writeln(',')
g.writeln('new_array_from_c_array(${node.fields.len}, ${node.fields.len}, sizeof(orm__TableField),')
g.indent++
if node.fields.len > 0 {
g.writeln('_MOV((orm__TableField[${node.fields.len}]){')
g.indent++
for field in node.fields {
g.writeln('// `${table_name}`.`${field.name}`')
final_field_typ := g.table.final_type(field.typ)
sym := g.table.sym(final_field_typ)
typ := match true {
sym.name == 'time.Time' { '_const_orm__time_' }
sym.kind == .enum { '_const_orm__enum_' }
else { final_field_typ.idx().str() }
}
g.writeln('(orm__TableField){')
g.indent++
g.writeln('.name = _S("${field.name}"),')
g.writeln('.typ = ${typ}, // `${sym.name}`')
g.writeln('.is_arr = ${sym.kind == .array}, ')
g.writeln('.nullable = ${final_field_typ.has_flag(.option)},')
g.writeln('.default_val = (string){ .str = (byteptr) "${field.default_val}", .is_lit = 1 },')
g.writeln('.attrs = new_array_from_c_array(${field.attrs.len}, ${field.attrs.len}, sizeof(VAttribute),')
g.indent++
if field.attrs.len > 0 {
g.write('_MOV((VAttribute[${field.attrs.len}]){')
g.indent++
for attr in field.attrs {
g.write('(VAttribute){')
g.indent++
name1 := util.smart_quote(attr.name, false)
name := cescape_nonascii(name1)
g.write(' .name = _S("${name}"),')
g.write(' .has_arg = ${attr.has_arg},')
arg1 := util.smart_quote(attr.arg, false)
arg := cescape_nonascii(arg1)
g.write(' .arg = _S("${arg}"),')
g.write(' .kind = ${int(attr.kind)},')
g.indent--
g.write('},')
}
g.indent--
g.writeln('})')
} else {
g.writeln('NULL // No attrs')
}
g.indent--
g.writeln(')')
g.indent--
g.writeln('},')
}
g.indent--
g.writeln('})')
} else {
g.writeln('NULL')
}
g.indent--
g.writeln(')')
g.indent--
g.writeln(');')
}
// write_orm_drop_table writes C code that calls ORM functions for dropping tables.
fn (mut g Gen) write_orm_drop_table(node ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, table_attrs []ast.Attr) {
g.writeln('// sql { drop table `${table_name}` }')
g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_drop(')
g.indent++
g.writeln('${connection_var_name}._object, // Connection object')
g.write_orm_table_struct(node.table_expr.typ)
g.indent--
g.writeln(');')
}
// write_orm_insert writes C code that calls ORM functions for inserting structs into a table.
fn (mut g Gen) write_orm_insert(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string,
or_expr &ast.OrExpr, table_attrs []ast.Attr) {
last_ids_variable_name := g.new_tmp_var()
g.writeln('Array_orm__Primitive ${last_ids_variable_name} = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
g.write_orm_insert_with_last_ids(node, connection_var_name, table_name, last_ids_variable_name,
result_var_name, '', '', or_expr)
}
// write_orm_update writes C code that calls ORM functions for updating rows.
fn (mut g Gen) write_orm_update(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, table_attrs []ast.Attr) {
g.writeln('// sql { update `${table_name}` }')
g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_update(')
g.indent++
g.writeln('${connection_var_name}._object, // Connection object')
g.write_orm_table_struct(node.table_expr.typ)
g.writeln(',')
g.writeln('(orm__QueryData){')
g.indent++
g.writeln('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
g.writeln('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
g.writeln('.types = __new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
g.writeln('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
if node.updated_columns.len > 0 {
g.writeln('.fields = new_array_from_c_array(${node.updated_columns.len}, ${node.updated_columns.len}, sizeof(string),')
g.indent++
g.writeln('_MOV((string[${node.updated_columns.len}]){')
g.indent++
for field in node.updated_columns {
g.writeln('_S("${field}"),')
}
g.indent--
g.writeln('})')
g.indent--
} else {
g.writeln('.fields = __new_array_with_default_noscan(${node.updated_columns.len}, ${node.updated_columns.len}, sizeof(string), 0')
}
g.writeln2('),', '.data = new_array_from_c_array(${node.update_exprs.len}, ${node.update_exprs.len}, sizeof(orm__Primitive),')
if node.update_exprs.len > 0 {
g.indent++
g.writeln('_MOV((orm__Primitive[${node.update_exprs.len}]){')
g.indent++
for e in node.update_exprs {
g.write_orm_expr_to_primitive(e)
}
g.indent--
g.writeln('})')
g.indent--
}
g.writeln('),')
g.indent--
g.writeln('},')
g.write_orm_where(node.where_expr)
g.indent--
g.writeln(');')
}
// write_orm_delete writes C code that calls ORM functions for deleting rows.
fn (mut g Gen) write_orm_delete(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, table_attrs []ast.Attr) {
g.writeln('// sql { delete from `${table_name}` }')
g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method__v_delete(')
g.indent++
g.writeln('${connection_var_name}._object, // Connection object')
g.write_orm_table_struct(node.table_expr.typ)
g.writeln(',')
g.write_orm_where(node.where_expr)
g.indent--
g.writeln(');')
}
// write_orm_insert_with_last_ids writes C code that calls ORM functions for
// inserting a struct into a table, saving inserted `id` into a passed variable.
fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_var_name string, table_name string,
last_ids_arr string, res string, pid string, fkey string, or_expr ast.OrExpr) {
mut subs := []ast.SqlStmtLine{}
mut subs_unwrapped_c_typ := []string{}
mut arrs := []ast.SqlStmtLine{}
mut fkeys := []string{}
mut field_names := []string{}
mut opt_fields := []int{}
for field in node.fields {
final_field_typ := g.table.final_type(field.typ)
sym := g.table.sym(final_field_typ)
if sym.kind == .struct && sym.name != 'time.Time' {
if final_field_typ in node.sub_structs {
subs << unsafe { node.sub_structs[int(final_field_typ)] }
unwrapped_c_typ := g.styp(final_field_typ.clear_flag(.option))
subs_unwrapped_c_typ << if final_field_typ.has_flag(.option) {
unwrapped_c_typ
} else {
''
}
}
} else if sym.kind == .array {
// Handle foreign keys
if attr := field.attrs.find_first('fkey') {
fkeys << attr.arg
} else {
verror('missing fkey attribute')
}
if final_field_typ.has_flag(.option) {
opt_fields << arrs.len
}
if final_field_typ in node.sub_structs {
arrs << unsafe { node.sub_structs[int(final_field_typ)] }
}
field_names << field.name
}
}
fields := node.fields.filter(g.table.sym(it.typ).kind != .array)
auto_fields := get_auto_field_idxs(fields)
primary_field := g.get_orm_struct_primary_field(fields) or { ast.StructField{} }
is_serial := (primary_field.attrs.contains_arg('sql', 'serial')
|| primary_field.attrs.contains('serial')) && primary_field.typ == ast.int_type
mut inserting_object_type := ast.void_type
mut member_access_type := '.'
if node.scope != unsafe { nil } {
inserting_object := node.scope.find(node.object_var) or {
verror('`${node.object_var}` is not found in scope')
}
if inserting_object.typ.is_ptr() {
member_access_type = '->'
}
inserting_object_type = inserting_object.typ
}
inserting_object_sym := g.table.sym(inserting_object_type)
for i, mut sub in subs {
if subs_unwrapped_c_typ[i].len > 0 {
var := '${node.object_var}${member_access_type}${sub.object_var}'
g.writeln('if(${var}.state == 0) {')
g.indent++
sub.object_var = '(*(${subs_unwrapped_c_typ[i]}*)${node.object_var}${member_access_type}${sub.object_var}.data)'
} else {
sub.object_var = '${node.object_var}${member_access_type}${sub.object_var}'
}
g.sql_stmt_line(sub, connection_var_name, or_expr)
g.writeln('array_push(&${last_ids_arr}, _MOV((orm__Primitive[1]){')
g.writeln('\torm__int_to_primitive(orm__Connection_name_table[${connection_var_name}._typ]._method_last_id(${connection_var_name}._object))}));')
if subs_unwrapped_c_typ[i].len > 0 {
g.indent--
g.writeln('} else {')
g.writeln('\tarray_push(&${last_ids_arr}, _MOV((orm__Primitive[1]){ _const_orm__null_primitive }));')
g.writeln('}')
}
}
g.writeln('// sql { insert into `${table_name}` }')
g.writeln('${result_name}_void ${res} = orm__Connection_name_table[${connection_var_name}._typ]._method_insert(')
g.indent++
g.writeln('${connection_var_name}._object, // Connection object')
g.write_orm_table_struct(node.table_expr.typ)
g.writeln(',')
g.writeln('(orm__QueryData){')
g.indent++
g.writeln('.fields = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
g.indent++
if fields.len > 0 {
g.writeln('_MOV((string[${fields.len}]){ ')
g.indent++
for f in fields {
g.writeln('_S("${g.get_orm_column_name_from_struct_field(f)}"),')
}
g.indent--
g.writeln('})')
} else {
g.writeln('NULL')
}
g.indent--
g.writeln('),')
g.writeln('.data = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(orm__Primitive),')
g.indent++
if fields.len > 0 {
g.writeln('_MOV((orm__Primitive[${fields.len}]){')
g.indent++
mut structs := 0
for field in fields {
if field.name == fkey {
g.writeln('${pid}, ')
continue
}
final_field_typ := g.table.final_type(field.typ)
mut sym := g.table.sym(final_field_typ)
mut typ := sym.cname
mut ctyp := sym.cname
if sym.kind == .struct && typ != 'time__Time' {
g.writeln('(*(orm__Primitive*) array_get(${last_ids_arr}, ${structs})),')
structs++
continue
}
// fields processed hereafter can be NULL...
if typ == 'time__Time' {
ctyp = 'time__Time'
typ = 'time'
} else if sym.kind == .enum {
typ = g.table.sym(final_field_typ).cname
}
typ = vint2int(typ)
var := '${node.object_var}${member_access_type}${c_name(field.name)}'
if final_field_typ.has_flag(.option) {
g.writeln('${var}.state == 2? _const_orm__null_primitive : orm__${typ}_to_primitive(*(${ctyp}*)(${var}.data)),')
} else if inserting_object_sym.kind == .sum_type {
table_sym := g.table.sym(node.table_expr.typ)
sum_type_var := '(*${node.object_var}._${table_sym.cname})${member_access_type}${c_name(field.name)}'
g.writeln('orm__${typ}_to_primitive(${sum_type_var}),')
} else {
g.writeln('orm__${typ}_to_primitive(${var}),')
}
}
g.indent--
g.writeln('})')
} else {
g.write('NULL')
}
g.indent--
g.writeln('),')
g.writeln('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
if auto_fields.len > 0 {
g.writeln('.auto_fields = new_array_from_c_array(${auto_fields.len}, ${auto_fields.len}, sizeof(${ast.int_type_name}),')
g.indent++
g.write('_MOV((${ast.int_type_name}[${auto_fields.len}]){')
for i in auto_fields {
g.write(' ${i},')
}
g.writeln(' })),')
g.indent--
} else {
g.writeln('.auto_fields = __new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
}
g.writeln('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
g.writeln('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
g.indent--
g.writeln('}')
g.indent--
g.writeln(');')
// Validate main insertion success otherwise, handled and propagated error.
g.or_block(res, or_expr, ast.int_type.set_flag(.result))
if arrs.len > 0 {
mut id_name := g.new_tmp_var()
if is_serial {
// use last_insert_id if current struct has `int [primary; sql: serial]`
g.writeln('orm__Primitive ${id_name} = orm__int_to_primitive(orm__Connection_name_table[${connection_var_name}._typ]._method_last_id(${connection_var_name}._object));')
} else {
// else use the primary key value
mut sym := g.table.sym(primary_field.typ)
mut typ := sym.cname
if typ == 'time__Time' {
typ = 'time'
}
typ = vint2int(typ)
g.writeln('orm__Primitive ${id_name} = orm__${typ}_to_primitive(${node.object_var}${member_access_type}${c_name(primary_field.name)});')
}
for i, mut arr in arrs {
idx := g.new_tmp_var()
ctyp := g.styp(arr.table_expr.typ)
is_option := opt_fields.contains(i)
if is_option {
g.writeln('for (${ast.int_type_name} ${idx} = 0; ${node.object_var}${member_access_type}${arr.object_var}.state != 2 && ${idx} < (*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data).len; ${idx}++) {')
} else {
g.writeln('for (${ast.int_type_name} ${idx} = 0; ${idx} < ${node.object_var}${member_access_type}${arr.object_var}.len; ${idx}++) {')
}
g.indent++
last_ids := g.new_tmp_var()
res_ := g.new_tmp_var()
tmp_var := g.new_tmp_var()
if is_option {
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data, ${idx}));')
} else {
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(${node.object_var}${member_access_type}${arr.object_var}, ${idx}));')
}
arr.object_var = tmp_var
mut fff := []ast.StructField{}
for f in arr.fields {
mut skip := false
for attr in f.attrs {
if attr.name == 'skip' {
skip = true
}
if attr.name == 'sql' && attr.arg == '-' {
skip = true
}
}
if !skip {
fff << f
}
}
arr.fields = fff.clone()
unsafe { fff.free() }
g.write_orm_insert_with_last_ids(arr, connection_var_name, g.get_table_name_by_struct_type(arr.table_expr.typ),
last_ids, res_, id_name, fkeys[i], or_expr)
// Validates sub insertion success otherwise, handled and propagated error.
g.or_block(res_, or_expr, ast.int_type.set_flag(.result))
g.indent--
g.writeln('}')
}
}
}
// write_orm_expr_to_primitive writes C code for casting expressions into a primitive type
// by checking support expressions and their types.
fn (mut g Gen) write_orm_expr_to_primitive(expr ast.Expr) {
match expr {
ast.InfixExpr {
g.write_orm_primitive(g.table.find_type('orm.InfixType'), expr)
}
ast.StringLiteral {
g.write_orm_primitive(ast.string_type, expr)
}
ast.StringInterLiteral {
g.write_orm_primitive(ast.string_type, expr)
}
ast.IntegerLiteral {
g.write_orm_primitive(ast.int_type, expr)
}
ast.BoolLiteral {
g.write_orm_primitive(ast.bool_type, expr)
}
ast.EnumVal {
g.write_orm_primitive(ast.i64_type, expr)
}
ast.Ident {
info := expr.info as ast.IdentVar
g.write_orm_primitive(info.typ, expr)
}
ast.SelectorExpr {
g.write_orm_primitive(expr.typ, expr)
}
ast.CallExpr {
g.write_orm_primitive(expr.return_type, expr)
}
ast.None {
g.write_orm_primitive(ast.none_type, expr)
}
else {
eprintln(expr)
verror('ORM: ${expr.type_name()} is not supported')
}
}
}
// write_orm_primitive writes C code for casting expressions into a primitive type,
// which will be used in low-level database libs.
fn (mut g Gen) write_orm_primitive(t ast.Type, expr ast.Expr) {
if t == 0 {
verror('${g.file.path}:${expr.pos().line_nr + 1}: ORM: unknown type t == 0\nexpr: ${expr}\nlast SQL stmt:\n${g.out.after(g.sql_last_stmt_out_len)}')
}
final_field_typ := g.table.final_type(t)
mut sym := g.table.sym(final_field_typ)
mut typ := sym.cname
if typ == 'orm__Primitive' {
g.expr(expr)
g.writeln(',')
return
}
if typ == 'none' {
g.writeln('_const_orm__null_primitive,')
return
}
if expr is ast.InfixExpr {
g.writeln('orm__infix_to_primitive((orm__InfixType){')
g.indent++
g.write('.name = _S("${expr.left}"),')
mut kind := match expr.op {
.plus {
'orm__MathOperationKind__add'
}
.minus {
'orm__MathOperationKind__sub'
}
.div {
'orm__MathOperationKind__div'
}
.mul {
'orm__MathOperationKind__mul'
}
else {
''
}
}
g.writeln(' .operator = ${kind},')
g.write(' .right = ')
g.write_orm_expr_to_primitive(expr.right)
g.indent--
g.writeln(' }),')
} else {
if typ == 'time__Time' {
typ = 'time'
}
if t.has_flag(.option) {
typ = 'option_${typ}'
} else if g.table.final_sym(t).kind == .enum {
typ = g.table.sym(g.table.final_type(t)).cname
} else if g.table.final_sym(t).kind == .array {
typ = g.table.sym(g.table.final_type(t)).cname.to_lower()
}
typ = vint2int(typ)
g.write('orm__${typ}_to_primitive(')
if expr is ast.CallExpr {
g.call_expr(expr)
} else {
g.left_is_opt = true
g.expr(expr)
}
g.writeln('),')
}
}
// write_orm_where writes C code that generates
// the `QueryData` structure for passing it into ORM methods.
fn (mut g Gen) write_orm_where(where_expr ast.Expr) {
mut fields := []string{}
mut kinds := []string{}
mut parentheses := [][]int{}
mut data := []ast.Expr{}
mut is_ands := []bool{}
g.writeln('// ORM where')
g.writeln('(orm__QueryData){')
g.indent++
g.write_orm_where_expr(where_expr, mut fields, mut parentheses, mut kinds, mut data, mut
is_ands)
g.writeln('.types = __new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
if fields.len > 0 {
g.writeln('.fields = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
g.indent++
g.writeln('_MOV((string[${fields.len}]){')
g.indent++
for field in fields {
g.writeln('_S("${field}"),')
}
g.indent--
g.writeln('})')
g.indent--
} else {
g.writeln('.fields = __new_array_with_default_noscan(${fields.len}, ${fields.len}, sizeof(string), 0')
}
g.writeln('),')
g.writeln('.data = new_array_from_c_array(${data.len}, ${data.len}, sizeof(orm__Primitive),')
g.indent++
if data.len > 0 {
g.writeln('_MOV((orm__Primitive[${data.len}]){')
g.indent++
for e in data {
g.write_orm_expr_to_primitive(e)
}
g.indent--
g.writeln('})')
} else {
g.writeln('0')
}
g.indent--
g.writeln('),')
g.write('.parentheses = ')
if parentheses.len > 0 {
g.write('new_array_from_c_array(${parentheses.len}, ${parentheses.len}, sizeof(Array_${ast.int_type_name}), _MOV((Array_${ast.int_type_name}[${parentheses.len}]){')
for par in parentheses {
if par.len > 0 {
g.write('new_array_from_c_array(${par.len}, ${par.len}, sizeof(${ast.int_type_name}), _MOV((${ast.int_type_name}[${par.len}]){')
for val in par {
g.write('${val},')
}
g.write('})),')
} else {
g.write('__new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
}
}
g.write('}))')
} else {
g.write('__new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0)')
}
g.writeln(',')
if kinds.len > 0 {
g.writeln('.kinds = new_array_from_c_array(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind),')
g.indent++
g.writeln('_MOV((orm__OperationKind[${kinds.len}]){')
g.indent++
for k in kinds {
g.writeln('${k},')
}
g.indent--
g.writeln('})')
g.indent--
} else {
g.write('.kinds = __new_array_with_default_noscan(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind), 0')
}
g.writeln('),')
if is_ands.len > 0 {
g.write('.is_and = new_array_from_c_array(${is_ands.len}, ${is_ands.len}, sizeof(bool),')
g.indent++
g.writeln('_MOV((bool[${is_ands.len}]){')
g.indent++
for is_and in is_ands {
g.writeln('${is_and},')
}
g.indent--
g.write('})')
g.indent--
} else {
g.write('.is_and = __new_array_with_default_noscan(${is_ands.len}, ${is_ands.len}, sizeof(bool), 0')
}
g.indent--
g.writeln2('),', '}')
}
// write_orm_where_expr writes C code that generates expression which is used in the `QueryData`.
fn (mut g Gen) write_orm_where_expr(expr ast.Expr, mut fields []string, mut parentheses [][]int, mut kinds []string,
mut data []ast.Expr, mut is_and []bool) {
match expr {
ast.InfixExpr {
g.sql_side = .left
g.write_orm_where_expr(expr.left, mut fields, mut parentheses, mut kinds, mut
data, mut is_and)
mut kind := match expr.op {
.ne {
'orm__OperationKind__neq'
}
.eq {
'orm__OperationKind__eq'
}
.lt {
'orm__OperationKind__lt'
}
.gt {
'orm__OperationKind__gt'
}
.ge {
'orm__OperationKind__ge'
}
.le {
'orm__OperationKind__le'
}
.key_like {
'orm__OperationKind__orm_like'
}
.key_ilike {
'orm__OperationKind__orm_ilike'
}
.key_is {
'orm__OperationKind__is_null'
}
.not_is {
'orm__OperationKind__is_not_null'
}
.key_in {
'orm__OperationKind__in'
}
.not_in {
'orm__OperationKind__not_in'
}
else {
''
}
}
if kind == '' {
if expr.op == .logical_or {
is_and << false
} else if expr.op == .and {
is_and << true
} else {
kind = 'orm__OperationKind__eq'
}
}
if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr && kind != '' {
kinds << kind
}
if expr.op !in [.key_is, .not_is] { // ignore rhs for unary ops
g.sql_side = .right
g.write_orm_where_expr(expr.right, mut fields, mut parentheses, mut kinds, mut
data, mut is_and)
}
}
ast.ParExpr {
mut par := [fields.len]
g.write_orm_where_expr(expr.expr, mut fields, mut parentheses, mut kinds, mut
data, mut is_and)
par << fields.len - 1
parentheses << par
}
ast.Ident {
if g.sql_side == .left {
field := g.get_orm_current_table_field(expr.name) or {
verror('field "${expr.name}" does not exist on "${g.sql_table_name}"')
}
fields << g.get_orm_column_name_from_struct_field(field)
} else {
data << expr
}
}
ast.StringLiteral {
data << expr
}
ast.StringInterLiteral {
data << expr
}
ast.IntegerLiteral {
data << expr
}
ast.SelectorExpr {
if g.comptime.is_comptime_selector_field_name(expr, 'name') {
fields << g.comptime.comptime_for_field_value.name
} else {
data << expr
}
}
ast.BoolLiteral {
data << expr
}
ast.EnumVal {
data << expr
}
ast.CallExpr {
data << expr
}
ast.None {
data << expr
}
else {}
}
}
// write_orm_select writes C code that calls ORM functions for selecting rows,
// storing the result in the provided variable name
fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, result_var string) {
mut fields := []ast.StructField{}
mut primary_field := g.get_orm_struct_primary_field(node.fields) or { ast.StructField{} }
for field in node.fields {
mut skip := false
for attr in field.attrs {
if attr.name == 'skip' {
skip = true
}
if attr.name == 'sql' && attr.arg == '-' {
skip = true
}
}
if !skip {
fields << field
}
}
select_result_var_name := g.new_tmp_var()
table_name := g.get_table_name_by_struct_type(node.table_expr.typ)
g.sql_table_name = g.table.sym(node.table_expr.typ).name
g.writeln('// sql { select from `${table_name}` }')
g.writeln('${result_name}_Array_Array_orm__Primitive ${select_result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_select(')
g.indent++
g.writeln('${connection_var_name}._object, // Connection object')
g.writeln('(orm__SelectConfig){')
g.indent++
g.writeln('.table = ')
g.write_orm_table_struct(node.table_expr.typ)
g.writeln(',')
g.writeln('.is_count = ${node.is_count},')
g.writeln('.has_where = ${node.has_where},')
g.writeln('.has_order = ${node.has_order},')
if node.has_order {
g.write('.order = _S("')
if node.order_expr is ast.Ident {
field := g.get_orm_current_table_field(node.order_expr.name) or {
verror('field "${node.order_expr.name}" does not exist on "${g.sql_table_name}"')
}
g.write(g.get_orm_column_name_from_struct_field(field))
} else {
g.expr(node.order_expr)
}
g.writeln('"),')
if node.has_desc {
g.writeln('.order_type = orm__OrderType__desc,')
} else {
g.writeln('.order_type = orm__OrderType__asc,')
}
}
g.writeln('.has_limit = ${node.has_limit},')
g.writeln('.has_offset = ${node.has_offset},')
if primary_field.name != '' {
g.writeln('.primary = _S("${primary_field.name}"),')
}
select_fields := fields.filter(g.table.sym(it.typ).kind != .array)
g.writeln('.fields = new_array_from_c_array(${select_fields.len}, ${select_fields.len}, sizeof(string),')
g.indent++
mut types := []string{}
if select_fields.len > 0 {
g.writeln('_MOV((string[${select_fields.len}]){')
g.indent++
for field in select_fields {
g.writeln('_S("${g.get_orm_column_name_from_struct_field(field)}"),')
final_field_typ := g.table.final_type(field.typ)
sym := g.table.sym(final_field_typ)
if sym.name == 'time.Time' {
types << '_const_orm__time_'
continue
}
if sym.kind == .struct {
types << int(ast.int_type).str()
continue
} else if sym.kind == .enum {
types << '_const_orm__enum_'
continue
}
types << final_field_typ.idx().str()
}
g.indent--
g.writeln('})')
} else {
g.writeln('NULL')
}
g.indent--
g.writeln2('),', '.types = new_array_from_c_array(${types.len}, ${types.len}, sizeof(${ast.int_type_name}),')
g.indent++
if types.len > 0 {
g.write('_MOV((${ast.int_type_name}[${types.len}]){')
for typ in types {
g.write(' ${typ},')
}
g.writeln(' })')
} else {
g.writeln('NULL')
}
g.indent--
g.writeln('),')
g.indent--
g.writeln('},')
mut exprs := []ast.Expr{}
if node.has_limit {
exprs << node.limit_expr
}
if node.has_offset {
exprs << node.offset_expr
}
g.writeln('(orm__QueryData) {')
g.indent++
g.writeln('.types = __new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
g.writeln('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
g.writeln('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
g.writeln('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 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.write_orm_expr_to_primitive(e)
}
g.writeln('})')
} else {
g.writeln('.data = __new_array_with_default_noscan(${exprs.len}, ${exprs.len}, sizeof(orm__Primitive), 0')
}
g.indent--
g.writeln(')},')
if node.has_where {
g.write_orm_where(node.where_expr)
} else {
g.writeln('(orm__QueryData) {')
g.indent++
g.writeln('.types = __new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
g.writeln('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
g.writeln('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
g.writeln('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
g.writeln('.data = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0)')
g.indent--
g.writeln('}')
}
g.indent--
g.writeln(');')
g.writeln('${result_var}.is_error = ${select_result_var_name}.is_error;')
g.writeln('${result_var}.err = ${select_result_var_name}.err;')
g.or_block(result_var, node.or_expr, node.typ)
// or_block could have ended in return (longjump) or could have
// yielded another value, so we must test for on non-error result
g.writeln('if (!${result_var}.is_error) {')
g.indent++
unwrapped_c_typ := g.styp(node.typ.clear_flag(.result))
select_unwrapped_result_var_name := g.new_tmp_var()
g.writeln('Array_Array_orm__Primitive ${select_unwrapped_result_var_name} = (*(Array_Array_orm__Primitive*)${select_result_var_name}.data);')
if node.is_count {
g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get(${select_unwrapped_result_var_name}, 0)), 0))._${ast.int_type_name});')
} else {
tmp := g.new_tmp_var()
idx := g.new_tmp_var()
g.writeln('${ast.int_type_name} ${idx} = 0;')
mut typ_str := ''
if node.is_array {
info := g.table.sym(node.typ).array_info()
typ_str = g.styp(info.elem_type)
base_typ := g.base_type(node.typ)
if node.typ.has_flag(.option) {
g.writeln('${unwrapped_c_typ} ${tmp}_array = { .state = 2, .err = _const_none__, .data = {E_STRUCT} };')
g.writeln('_option_ok(&(${base_typ}[]) { __new_array(0, ${select_unwrapped_result_var_name}.len, sizeof(${typ_str})) }, (_option *)&${tmp}_array, sizeof(${base_typ}));')
} else {
g.writeln('${unwrapped_c_typ} ${tmp}_array = __new_array(0, ${select_unwrapped_result_var_name}.len, sizeof(${typ_str}));')
}
g.writeln('for (; ${idx} < ${select_unwrapped_result_var_name}.len; ${idx}++) {')
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)
if i != inf.fields.len - 1 {
g.write(', ')
}
}
g.writeln('};')
} else {
g.write('${unwrapped_c_typ} ${tmp} = (${unwrapped_c_typ}){')
info := g.table.sym(node.typ).struct_info()
for i, field in info.fields {
g.zero_struct_field(field)
if i != info.fields.len - 1 {
g.write(', ')
}
}
g.writeln('};')
}
g.writeln('if (${select_unwrapped_result_var_name}.len > 0) {')
g.indent++
mut fields_idx := 0
for field in fields {
array_get_call_code := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get(${select_unwrapped_result_var_name}, ${idx})), ${fields_idx}))'
final_field_typ := g.table.final_type(field.typ)
sym := g.table.sym(final_field_typ)
field_var := '${tmp}.${c_name(field.name)}'
field_c_typ := g.styp(final_field_typ)
if sym.kind == .struct && sym.name != 'time.Time' {
mut sub := node.sub_structs[int(final_field_typ)] or { continue }
mut where_expr := sub.where_expr as ast.InfixExpr
mut ident := where_expr.right as ast.Ident
primitive_type_index := g.table.find_type('orm.Primitive')
if primitive_type_index != 0 {
if mut ident.info is ast.IdentVar {
ident.info.typ = primitive_type_index
}
}
ident.name = array_get_call_code
where_expr.right = ident
sub.where_expr = where_expr
sub_result_var := g.new_tmp_var()
sub_result_c_typ := g.styp(sub.typ)
g.writeln('${sub_result_c_typ} ${sub_result_var};')
g.write_orm_select(sub, connection_var_name, sub_result_var)
if final_field_typ.has_flag(.option) {
unwrapped_field_c_typ := g.styp(final_field_typ.clear_flag(.option))
g.writeln('if (!${sub_result_var}.is_error)')
g.writeln('\t_option_ok(${sub_result_var}.data, (_option *)&${field_var}, sizeof(${unwrapped_field_c_typ}));')
g.writeln('else')
g.writeln('\t${field_var} = (${field_c_typ}){ .state = 2, .err = _const_none__, .data = {E_STRUCT} };')
} else {
g.writeln('if (!${sub_result_var}.is_error)')
g.writeln('\t${field_var} = *(${field_c_typ}*)${sub_result_var}.data;')
}
fields_idx++
} else if sym.kind == .array {
mut fkey := ''
if attr := field.attrs.find_first('fkey') {
fkey = attr.arg
} else {
verror('missing fkey attribute')
}
sub := node.sub_structs[final_field_typ] or { continue }
if sub.has_where {
mut where_expr := sub.where_expr as ast.InfixExpr
mut left_where_expr := where_expr.left as ast.Ident
mut right_where_expr := where_expr.right as ast.Ident
left_where_expr.name = fkey
right_where_expr.name = tmp
where_expr.left = left_where_expr
where_expr.right = ast.SelectorExpr{
pos: right_where_expr.pos
field_name: primary_field.name
is_mut: false
expr: right_where_expr
expr_type: (right_where_expr.info as ast.IdentVar).typ
typ: (right_where_expr.info as ast.IdentVar).typ
scope: unsafe { nil }
}
mut sql_expr_select_array := ast.SqlExpr{
typ: final_field_typ.set_flag(.result)
is_count: sub.is_count
db_expr: sub.db_expr
has_where: sub.has_where
has_offset: sub.has_offset
offset_expr: sub.offset_expr
has_order: sub.has_order
order_expr: sub.order_expr
has_desc: sub.has_desc
is_array: true
is_generated: true
pos: sub.pos
has_limit: sub.has_limit
limit_expr: sub.limit_expr
table_expr: sub.table_expr
fields: sub.fields
where_expr: where_expr
}
sub_result_var := g.new_tmp_var()
sub_result_c_typ := g.styp(sub.typ)
g.writeln('${sub_result_c_typ} ${sub_result_var};')
g.write_orm_select(sql_expr_select_array, connection_var_name, sub_result_var)
g.writeln('if (!${sub_result_var}.is_error) {')
if final_field_typ.has_flag(.option) {
g.writeln('\t${field_var}.state = 0;')
g.writeln('\t*(${g.base_type(final_field_typ)}*)${field_var}.data = *(${g.base_type(final_field_typ)}*)${sub_result_var}.data;')
} else {
g.writeln('\t${field_var} = *(${g.base_type(field.typ)}*)${sub_result_var}.data;')
}
g.writeln('}')
}
} else if final_field_typ.has_flag(.option) {
prim_var := g.new_tmp_var()
g.writeln('orm__Primitive *${prim_var} = &${array_get_call_code};')
g.writeln('if (${prim_var}->_typ == ${g.table.find_type_idx('orm.Null')})')
g.writeln('\t${field_var} = (${field_c_typ}){ .state = 2, .err = _const_none__, .data = {E_STRUCT} };')
g.writeln('else')
g.writeln('\t_option_ok(${prim_var}->_${sym.cname}, (_option *)&${field_var}, sizeof(${sym.cname}));')
fields_idx++
} else if sym.kind == .enum {
mut typ := sym.cname
g.writeln('${tmp}.${c_name(field.name)} = (${typ}) (*(${array_get_call_code}._i64));')
fields_idx++
} else {
g.writeln('${field_var} = *(${array_get_call_code}._${sym.cname});')
fields_idx++
}
}
if node.is_array {
if node.typ.has_flag(.option) {
g.writeln('${tmp}_array.state = 0;')
g.writeln('array_push((${g.base_type(node.typ)}*)&${tmp}_array.data, _MOV((${typ_str}[]){ ${tmp} }));')
} else {
g.writeln('array_push(&${tmp}_array, _MOV((${typ_str}[]){ ${tmp} }));')
}
g.indent--
g.writeln('}')
}
g.indent--
if !node.is_array {
g.writeln('} else {')
g.writeln('\t${result_var}.is_error = true;')
}
g.writeln('}')
if node.is_array {
if node.typ.has_flag(.option) {
g.writeln('*(${g.base_type(node.typ)}*) ${result_var}.data = *(${g.base_type(node.typ)}*)${tmp}_array.data;')
} else {
g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp}_array;')
}
} else {
g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp};')
}
}
g.indent--
g.writeln('}')
if node.is_generated {
g.writeln(';')
}
}
// filter_struct_fields_by_orm_attrs filters struct fields taking into its attributes.
// Used by non-create queries for skipping fields.
fn (_ &Gen) filter_struct_fields_by_orm_attrs(fields []ast.StructField) []ast.StructField {
mut ret := []ast.StructField{}
for field in fields {
if field.attrs.contains('skip') || field.attrs.contains_arg('sql', '-') {
continue
}
ret << field
}
return ret
}
// get_db_expr_type returns the database type from the database expression.
fn (g &Gen) get_db_expr_type(expr ast.Expr) ?ast.Type {
if expr is ast.Ident {
if expr.info is ast.IdentVar {
return g.table.unaliased_type(expr.info.typ)
}
} else if expr is ast.SelectorExpr {
return g.table.unaliased_type(expr.typ)
}
return none
}
// get_table_attrs_by_struct_type returns the struct attrs.
fn (g &Gen) get_table_attrs_by_struct_type(typ ast.Type) []ast.Attr {
sym := g.table.sym(typ)
info := sym.struct_info()
return info.attrs
}
// get_table_name_by_struct_type converts the struct type to a table name.
fn (g &Gen) get_table_name_by_struct_type(typ ast.Type) string {
sym := g.table.sym(typ)
info := sym.struct_info()
mut table_name := util.strip_mod_name(sym.name)
if attr := info.attrs.find_first('table') {
table_name = attr.arg
}
escaped_table_name := cescape_nonascii(util.smart_quote(table_name, false))
return escaped_table_name
}
// get_orm_current_table_field returns the current processing table's struct field by name.
fn (g &Gen) get_orm_current_table_field(name string) ?ast.StructField {
info := g.table.sym(ast.idx_to_type(g.table.type_idxs[g.sql_table_name])).struct_info()
for field in info.fields {
if field.name == name {
return field
}
}
return none
}
// get_orm_column_name_from_struct_field converts the struct field to a table column name.
fn (g &Gen) get_orm_column_name_from_struct_field(field ast.StructField) string {
mut name := field.name
if attr := field.attrs.find_first('sql') {
if attr.arg !in ['serial', 'i8', 'i16', 'int', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32',
'f64', 'bool', 'string'] {
name = attr.arg
}
}
final_field_typ := g.table.final_type(field.typ)
sym := g.table.sym(final_field_typ)
if sym.kind == .struct && sym.name != 'time.Time' {
name = '${name}_id'
}
return name
}
// get_orm_struct_primary_field returns the table's primary column field.
fn (_ &Gen) get_orm_struct_primary_field(fields []ast.StructField) ?ast.StructField {
for field in fields {
if _ := field.attrs.find_first('primary') {
return field
}
}
return none
}
// return indexes of any auto-increment fields or fields with default values
fn get_auto_field_idxs(fields []ast.StructField) []int {
mut ret := []int{}
for i, field in fields {
for attr in field.attrs {
if attr.name == 'default' {
ret << i
} else if attr.name == 'sql' && attr.arg == 'serial' {
ret << i
} else if attr.name == 'serial' && attr.kind == .plain && !attr.has_arg {
ret << i
}
}
}
return ret
}