v4 / vlib / v / gen / c / orm.v
2890 lines · 2722 sloc · 95.17 KB · 7b724e98ac187e233bd03716901c29f5e00f815f
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
3module c
4
5import v.ast
6import v.util
7
8// orm_field_access_name converts an ORM field name to proper C struct member access.
9// For embedded struct fields like "Payload.some_field", this returns "Payload.some_field"
10// (keeping the dot for C member access). For regular fields, it uses c_name().
11fn orm_field_access_name(field_name string) string {
12 if field_name.contains('.') {
13 // Embedded struct field - keep the dots for proper C struct member access
14 // e.g., "Payload.some_field" -> "Payload.some_field"
15 parts := field_name.split('.')
16 mut result := []string{}
17 for part in parts {
18 result << c_name(part)
19 }
20 return result.join('.')
21 }
22 return c_name(field_name)
23}
24
25fn (g &Gen) orm_inserting_object_type(obj ast.ScopeObject) ast.Type {
26 return match obj {
27 ast.Var {
28 if obj.smartcasts.len > 0 && obj.orig_type != 0
29 && g.table.final_sym(g.table.final_type(obj.orig_type)).kind == .sum_type {
30 obj.orig_type
31 } else {
32 obj.typ
33 }
34 }
35 else {
36 ast.void_type
37 }
38 }
39}
40
41fn (g &Gen) orm_primitive_field_name(typ ast.Type) string {
42 final_typ := g.table.final_type(typ.clear_flag(.option))
43 sym := g.table.sym(final_typ)
44 if sym.name == 'time.Time' {
45 return 'time'
46 }
47 if sym.kind == .enum {
48 return 'i64'
49 }
50 if sym.kind == .array {
51 return vint2int(sym.cname.to_lower())
52 }
53 return vint2int(sym.cname)
54}
55
56fn (g &Gen) orm_non_array_fields(fields []ast.StructField) []ast.StructField {
57 mut non_array_fields := []ast.StructField{cap: fields.len}
58 for field in fields {
59 final_typ := g.table.final_type(field.typ.clear_flag(.option))
60 if g.table.sym(final_typ).kind != .array {
61 non_array_fields << field
62 }
63 }
64 return non_array_fields
65}
66
67fn (g &Gen) orm_primitive_variant_field_name(typ ast.Type) string {
68 final_typ := g.table.final_type(typ.clear_flag(.option))
69 sym := g.table.sym(final_typ)
70 if sym.kind == .enum {
71 return 'i64'
72 }
73 return vint2int(sym.cname)
74}
75
76enum SqlExprSide {
77 left
78 right
79}
80
81fn sql_query_data_field_name(expr ast.Expr) string {
82 return match expr {
83 ast.Ident { expr.name }
84 ast.SelectorExpr { '${sql_query_data_field_name(expr.expr)}.${expr.field_name}' }
85 ast.ParExpr { sql_query_data_field_name(expr.expr) }
86 else { '' }
87 }
88}
89
90fn sql_query_data_op_kind(expr ast.InfixExpr) string {
91 is_nil_comparison := expr.right is ast.Nil && expr.op in [.eq, .ne]
92 return match expr.op {
93 .ne {
94 if is_nil_comparison {
95 'orm__OperationKind__is_not_null'
96 } else {
97 'orm__OperationKind__neq'
98 }
99 }
100 .eq {
101 if is_nil_comparison {
102 'orm__OperationKind__is_null'
103 } else {
104 'orm__OperationKind__eq'
105 }
106 }
107 .lt {
108 'orm__OperationKind__lt'
109 }
110 .gt {
111 'orm__OperationKind__gt'
112 }
113 .ge {
114 'orm__OperationKind__ge'
115 }
116 .le {
117 'orm__OperationKind__le'
118 }
119 .key_like {
120 'orm__OperationKind__orm_like'
121 }
122 .key_ilike {
123 'orm__OperationKind__orm_ilike'
124 }
125 .key_is {
126 'orm__OperationKind__is_null'
127 }
128 .not_is {
129 'orm__OperationKind__is_not_null'
130 }
131 .key_in {
132 'orm__OperationKind__in'
133 }
134 .not_in {
135 'orm__OperationKind__not_in'
136 }
137 else {
138 'orm__OperationKind__eq'
139 }
140 }
141}
142
143fn (g &Gen) resolve_sql_query_data_expr(expr ast.Expr) ?ast.SqlQueryDataExpr {
144 mut current := expr
145 for {
146 current = current.remove_par()
147 mut next_expr := current
148 mut has_next_expr := false
149 match current {
150 ast.SqlQueryDataExpr {
151 return current as ast.SqlQueryDataExpr
152 }
153 ast.Ident {
154 obj := current.obj
155 match obj {
156 ast.Var {
157 if obj.is_mut {
158 return none
159 }
160 next_expr = obj.expr
161 has_next_expr = true
162 }
163 ast.ConstField {
164 next_expr = obj.expr
165 has_next_expr = true
166 }
167 else {}
168 }
169 }
170 else {
171 return none
172 }
173 }
174
175 if has_next_expr {
176 current = next_expr
177 continue
178 }
179 return none
180 }
181 return none
182}
183
184fn (mut g Gen) emit_sql_query_data(node ast.SqlQueryDataExpr, resolve_columns bool) string {
185 query_var := g.new_tmp_var()
186 g.writeln('orm__QueryData ${query_var} = (orm__QueryData){')
187 g.indent++
188 g.writeln('.fields = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
189 g.writeln('.data = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0),')
190 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
191 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
192 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
193 g.writeln('.auto_fields = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
194 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
195 g.indent--
196 g.writeln('};')
197 g.emit_sql_query_data_items(query_var, node.items, resolve_columns)
198 return query_var
199}
200
201fn (mut g Gen) emit_dynamic_sql_query_data(expr ast.Expr) string {
202 node := g.resolve_sql_query_data_expr(expr) or {
203 verror('ORM: expected a dynamic query-data block or immutable alias')
204 }
205 return g.emit_sql_query_data(node, true)
206}
207
208struct SqlQueryDataGuardState {
209 temp_var string
210 expr_type ast.Type
211}
212
213fn (mut g Gen) sql_query_data_guard_expr_type(cond ast.IfGuardExpr) ast.Type {
214 mut expr_type := cond.expr_type
215 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
216 if cond.expr is ast.CallExpr {
217 resolved := g.resolve_return_type(cond.expr)
218 if resolved != ast.void_type && !resolved.has_flag(.generic) {
219 expr_type = if cond.expr_type.has_flag(.option) && !resolved.has_flag(.option) {
220 resolved.set_flag(.option)
221 } else if cond.expr_type.has_flag(.result) && !resolved.has_flag(.result) {
222 resolved.set_flag(.result)
223 } else {
224 resolved
225 }
226 }
227 } else {
228 resolved := g.unwrap_generic(g.recheck_concrete_type(expr_type))
229 if resolved != ast.void_type {
230 expr_type = resolved
231 }
232 }
233 }
234 return expr_type
235}
236
237fn (mut g Gen) emit_sql_query_data_guard_open(cond ast.IfGuardExpr) SqlQueryDataGuardState {
238 expr_type := g.sql_query_data_guard_expr_type(cond)
239 temp_var := g.new_tmp_var()
240 g.writeln('${g.styp(g.unwrap_generic(expr_type))} ${temp_var} = {0};')
241 g.write('if ((${temp_var} = ')
242 g.expr(cond.expr)
243 if expr_type.has_flag(.option) {
244 dot_or_ptr := if !expr_type.has_flag(.option_mut_param_t) {
245 '.'
246 } else {
247 '-> '
248 }
249 g.writeln('), ${temp_var}${dot_or_ptr}state == 0) {')
250 } else if expr_type.has_flag(.result) {
251 g.writeln('), !${temp_var}.is_error) {')
252 } else {
253 g.writeln(')) {')
254 }
255 g.indent++
256 return SqlQueryDataGuardState{
257 temp_var: temp_var
258 expr_type: expr_type
259 }
260}
261
262fn (mut g Gen) emit_sql_query_data_guard_value_vars(cond ast.IfGuardExpr, state SqlQueryDataGuardState) {
263 if cond.vars.len == 0 || cond.vars.all(it.name == '_') {
264 return
265 }
266 base_type := g.base_type(state.expr_type)
267 if cond.vars.len == 1 {
268 if cond.vars[0].name == '_' {
269 return
270 }
271 left_var_name := c_name(cond.vars[0].name)
272 dot_or_ptr := if !state.expr_type.has_flag(.option_mut_param_t) {
273 '.'
274 } else {
275 '-> '
276 }
277 guard_typ := g.unwrap_generic(state.expr_type.clear_option_and_result())
278 guard_is_heap_obj := g.table.final_sym(guard_typ).is_heap() && !guard_typ.is_ptr()
279 if guard_is_heap_obj {
280 g.writeln('${base_type}* ${left_var_name} = (${base_type}*)${state.temp_var}${dot_or_ptr}data;')
281 } else if base_type.starts_with('Array_fixed') {
282 g.writeln('${base_type} ${left_var_name} = {0};')
283 g.writeln('memcpy(${left_var_name}, (${base_type}*)${state.temp_var}.data, sizeof(${base_type}));')
284 } else {
285 expr_sym := g.table.sym(state.expr_type)
286 if expr_sym.info is ast.FnType {
287 g.write_fntype_decl(left_var_name, expr_sym.info, state.expr_type.nr_muls())
288 if state.expr_type.nr_muls() == 0 {
289 g.writeln(' = *(${base_type}*)${state.temp_var}${dot_or_ptr}data;')
290 } else {
291 g.writeln(' = (${base_type}*)${state.temp_var}${dot_or_ptr}data;')
292 }
293 } else {
294 g.writeln('${base_type} ${left_var_name} = *(${base_type}*)${state.temp_var}${dot_or_ptr}data;')
295 }
296 }
297 return
298 }
299 sym := g.table.sym(state.expr_type)
300 if sym.info is ast.MultiReturn && sym.info.types.len == cond.vars.len {
301 for vi, var in cond.vars {
302 if var.name == '_' {
303 continue
304 }
305 var_typ := g.styp(sym.info.types[vi])
306 left_var_name := c_name(var.name)
307 g.writeln('${var_typ} ${left_var_name} = (*(${base_type}*)${state.temp_var}.data).arg${vi};')
308 }
309 }
310}
311
312fn (mut g Gen) emit_sql_query_data_guard_else_vars(state SqlQueryDataGuardState) {
313 if state.expr_type.has_flag(.result) {
314 g.writeln('IError err = ${state.temp_var}.err;')
315 }
316}
317
318fn (g &Gen) sql_query_data_guard_var_types(cond ast.IfGuardExpr, state SqlQueryDataGuardState) map[int]ast.Type {
319 mut guard_var_types := map[int]ast.Type{}
320 if state.expr_type == 0 {
321 return guard_var_types
322 }
323 sym := g.table.sym(state.expr_type)
324 if sym.kind == .multi_return {
325 mr_info := sym.info as ast.MultiReturn
326 if mr_info.types.len == cond.vars.len {
327 for vi, var in cond.vars {
328 if var.name == '_' {
329 continue
330 }
331 guard_var_types[int(var.pos.pos)] = mr_info.types[vi]
332 }
333 }
334 } else {
335 unwrapped_type := state.expr_type.clear_option_and_result()
336 for var in cond.vars {
337 if var.name == '_' {
338 continue
339 }
340 guard_var_types[int(var.pos.pos)] = unwrapped_type
341 }
342 }
343 return guard_var_types
344}
345
346fn (g &Gen) annotate_sql_query_data_guard_expr(mut expr ast.Expr, guard_var_types map[int]ast.Type) {
347 match mut expr {
348 ast.ArrayInit {
349 for mut item in expr.exprs {
350 g.annotate_sql_query_data_guard_expr(mut item, guard_var_types)
351 }
352 if expr.has_cap {
353 g.annotate_sql_query_data_guard_expr(mut expr.cap_expr, guard_var_types)
354 }
355 if expr.has_len {
356 g.annotate_sql_query_data_guard_expr(mut expr.len_expr, guard_var_types)
357 }
358 if expr.has_init {
359 g.annotate_sql_query_data_guard_expr(mut expr.init_expr, guard_var_types)
360 }
361 }
362 ast.CallExpr {
363 g.annotate_sql_query_data_guard_expr(mut expr.left, guard_var_types)
364 for mut arg in expr.args {
365 g.annotate_sql_query_data_guard_expr(mut arg.expr, guard_var_types)
366 }
367 }
368 ast.Ident {
369 if mut expr.info is ast.IdentVar {
370 if expr.obj is ast.Var {
371 if typ := guard_var_types[int(expr.obj.pos.pos)] {
372 expr.info.typ = typ
373 expr.obj.typ = typ
374 }
375 }
376 }
377 }
378 ast.IfExpr {
379 g.annotate_sql_query_data_guard_expr(mut expr.left, guard_var_types)
380 for mut branch in expr.branches {
381 g.annotate_sql_query_data_guard_expr(mut branch.cond, guard_var_types)
382 for mut stmt in branch.stmts {
383 if mut stmt is ast.ExprStmt {
384 g.annotate_sql_query_data_guard_expr(mut stmt.expr, guard_var_types)
385 }
386 }
387 }
388 }
389 ast.InfixExpr {
390 g.annotate_sql_query_data_guard_expr(mut expr.left, guard_var_types)
391 g.annotate_sql_query_data_guard_expr(mut expr.right, guard_var_types)
392 }
393 ast.ParExpr {
394 g.annotate_sql_query_data_guard_expr(mut expr.expr, guard_var_types)
395 }
396 ast.PrefixExpr {
397 g.annotate_sql_query_data_guard_expr(mut expr.right, guard_var_types)
398 }
399 ast.SelectorExpr {
400 g.annotate_sql_query_data_guard_expr(mut expr.expr, guard_var_types)
401 }
402 ast.StringInterLiteral {
403 for mut item in expr.exprs {
404 g.annotate_sql_query_data_guard_expr(mut item, guard_var_types)
405 }
406 }
407 ast.StructInit {
408 for mut init_field in expr.init_fields {
409 g.annotate_sql_query_data_guard_expr(mut init_field.expr, guard_var_types)
410 }
411 }
412 else {}
413 }
414}
415
416fn (g &Gen) annotate_sql_query_data_guard_items(mut items []ast.SqlQueryDataItem, cond ast.IfGuardExpr, state SqlQueryDataGuardState) {
417 guard_var_types := g.sql_query_data_guard_var_types(cond, state)
418 if guard_var_types.len == 0 {
419 return
420 }
421 for mut item in items {
422 match mut item {
423 ast.SqlQueryDataLeaf {
424 g.annotate_sql_query_data_guard_expr(mut item.expr, guard_var_types)
425 }
426 ast.SqlQueryDataIf {
427 for mut branch in item.branches {
428 if branch.cond !is ast.EmptyExpr {
429 g.annotate_sql_query_data_guard_expr(mut branch.cond, guard_var_types)
430 }
431 g.annotate_sql_query_data_guard_items(mut branch.items, cond, state)
432 }
433 }
434 }
435 }
436}
437
438fn (g &Gen) orm_if_guard_ident_type(node ast.Ident, typ ast.Type) ast.Type {
439 if typ != 0 {
440 return typ
441 }
442 if node.obj is ast.Var {
443 if node.obj.expr is ast.IfGuardExpr {
444 if node.obj.expr.expr_type != 0 {
445 sym := g.table.sym(node.obj.expr.expr_type)
446 if sym.kind == .multi_return {
447 mr_info := sym.info as ast.MultiReturn
448 if mr_info.types.len == node.obj.expr.vars.len {
449 for vi, var in node.obj.expr.vars {
450 if var.name == node.name {
451 return mr_info.types[vi]
452 }
453 }
454 }
455 } else {
456 return node.obj.expr.expr_type.clear_option_and_result()
457 }
458 }
459 }
460 }
461 return typ
462}
463
464fn (mut g Gen) emit_sql_query_data_items(query_var string, items []ast.SqlQueryDataItem, resolve_columns bool) {
465 for item in items {
466 match item {
467 ast.SqlQueryDataLeaf {
468 if is_sql_query_data_top_level_or(item.expr) {
469 start_var := g.new_tmp_var()
470 g.writeln('${ast.int_type_name} ${start_var} = ${query_var}.fields.len;')
471 g.emit_sql_query_data_expr(query_var, item.expr, resolve_columns, true)
472 g.emit_sql_query_data_parentheses(query_var, start_var)
473 continue
474 }
475 g.emit_sql_query_data_expr(query_var, item.expr, resolve_columns, true)
476 }
477 ast.SqlQueryDataIf {
478 mut prev_guard := SqlQueryDataGuardState{}
479 mut has_prev_guard := false
480 for idx, branch in item.branches {
481 is_else := branch.cond is ast.EmptyExpr
482 if idx == 0 {
483 if is_else {
484 g.writeln('{')
485 g.indent++
486 if has_prev_guard {
487 g.emit_sql_query_data_guard_else_vars(prev_guard)
488 }
489 } else {
490 if branch.cond is ast.IfGuardExpr {
491 prev_guard = g.emit_sql_query_data_guard_open(branch.cond)
492 has_prev_guard = true
493 g.emit_sql_query_data_guard_value_vars(branch.cond, prev_guard)
494 } else {
495 g.write('if (')
496 g.expr(branch.cond)
497 g.writeln(') {')
498 g.indent++
499 has_prev_guard = false
500 }
501 }
502 } else {
503 if is_else {
504 g.writeln('else {')
505 g.indent++
506 if has_prev_guard {
507 g.emit_sql_query_data_guard_else_vars(prev_guard)
508 }
509 } else if branch.cond is ast.IfGuardExpr {
510 g.write('else ')
511 prev_guard = g.emit_sql_query_data_guard_open(branch.cond)
512 has_prev_guard = true
513 g.emit_sql_query_data_guard_value_vars(branch.cond, prev_guard)
514 } else {
515 g.write('else if (')
516 g.expr(branch.cond)
517 g.writeln(') {')
518 g.indent++
519 has_prev_guard = false
520 }
521 }
522 mut branch_items := branch.items.clone()
523 if branch.cond is ast.IfGuardExpr {
524 g.annotate_sql_query_data_guard_items(mut branch_items, branch.cond,
525 prev_guard)
526 }
527 g.emit_sql_query_data_items(query_var, branch_items, resolve_columns)
528 g.indent--
529 g.writeln('}')
530 }
531 }
532 }
533 }
534}
535
536fn is_sql_query_data_top_level_or(expr ast.Expr) bool {
537 return match expr {
538 ast.InfixExpr { expr.op == .logical_or }
539 else { false }
540 }
541}
542
543fn (mut g Gen) emit_sql_query_data_parentheses(query_var string, start_var string) {
544 g.writeln('if (${query_var}.fields.len > ${start_var}) {')
545 g.indent++
546 g.write('builtin__array_push(&${query_var}.parentheses, _MOV((Array_${ast.int_type_name}[1]){')
547 g.write('builtin__new_array_from_c_array(2, 2, sizeof(${ast.int_type_name}),')
548 g.write(' _MOV((${ast.int_type_name}[2]){${start_var}, ${query_var}.fields.len - 1}))')
549 g.writeln('}));')
550 g.indent--
551 g.writeln('}')
552}
553
554fn (mut g Gen) emit_sql_query_data_expr(query_var string, expr ast.Expr, resolve_columns bool, is_and bool) {
555 match expr {
556 ast.ParExpr {
557 start_var := g.new_tmp_var()
558 g.writeln('${ast.int_type_name} ${start_var} = ${query_var}.fields.len;')
559 g.emit_sql_query_data_expr(query_var, expr.expr, resolve_columns, is_and)
560 g.emit_sql_query_data_parentheses(query_var, start_var)
561 }
562 ast.InfixExpr {
563 if expr.op in [.and, .logical_or] {
564 g.emit_sql_query_data_expr(query_var, expr.left, resolve_columns, is_and)
565 g.emit_sql_query_data_expr(query_var, expr.right, resolve_columns, expr.op == .and)
566 } else {
567 g.emit_sql_query_data_leaf(query_var, expr, resolve_columns, is_and)
568 }
569 }
570 else {
571 verror('ORM: dynamic query-data leaf must be an infix expression')
572 }
573 }
574}
575
576fn (mut g Gen) emit_sql_query_data_leaf(query_var string, expr ast.InfixExpr, resolve_columns bool, is_and bool) {
577 mut field_name := sql_query_data_field_name(expr.left)
578 if resolve_columns {
579 field := g.get_orm_current_table_field(field_name) or {
580 verror('field "${field_name}" does not exist on "${g.sql_table_name}"')
581 }
582 field_name = g.get_orm_column_name_from_struct_field(field)
583 }
584 is_nil_comparison := expr.right is ast.Nil && expr.op in [.eq, .ne]
585 ignore_rhs := expr.op in [.key_is, .not_is] || is_nil_comparison
586 g.writeln('if (${query_var}.fields.len > 0) {')
587 g.indent++
588 g.writeln('builtin__array_push(&${query_var}.is_and, _MOV((bool[1]){ ${is_and} }));')
589 g.indent--
590 g.writeln('}')
591 g.writeln('builtin__array_push(&${query_var}.fields, _MOV((string[1]){ _S("${field_name}") }));')
592 g.writeln('builtin__array_push(&${query_var}.kinds, _MOV((orm__OperationKind[1]){ ${sql_query_data_op_kind(expr)} }));')
593 if !ignore_rhs {
594 g.write('builtin__array_push(&${query_var}.data, _MOV((orm__Primitive[1]){')
595 g.write_orm_expr_to_primitive(expr.right)
596 g.writeln('}));')
597 }
598}
599
600// All databases that are supported by ORM
601// must implement the `Connection` interface from the file `vlib/orm/orm.v`
602// The main function of the cgen for ORM is to identify the current database type,
603// generate the necessary parameters for invoking its methods,
604// and correctly process the results, saving them to a variable that the user can access.
605// Essentially, it involves a desugaring of the ORM syntax.
606// For example, if you write:
607// ```v
608// sql db {
609// select from User
610// }
611// ```
612// cgen will write calling the function `select` of the needed database.
613// If you use sqlite, it calls `select` from `vlib/db/sqlite/orm.v`
614
615// sql_select_expr writes C code that calls ORM functions for selecting objects
616// from the database, which is used by the `select` query.
617fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
618 // users :=
619 left := g.go_before_last_stmt()
620 connection_var_name := g.new_tmp_var()
621
622 g.writeln('')
623 g.write_orm_connection_init(connection_var_name, &node.db_expr)
624 result_var := g.new_tmp_var()
625 result_c_typ := g.styp(node.typ)
626 g.writeln('${result_c_typ} ${result_var};')
627 g.write_orm_select(node, connection_var_name, result_var)
628 unwrapped_c_typ := g.styp(node.typ.clear_flag(.result))
629 g.write('${left} *(${unwrapped_c_typ}*)${result_var}.data')
630}
631
632fn (mut g Gen) sql_insert_expr(node ast.SqlExpr) {
633 left := g.go_before_last_stmt()
634 g.writeln('')
635 connection_var_name := g.new_tmp_var()
636 g.write_orm_connection_init(connection_var_name, &node.db_expr)
637 table_name := g.get_table_name_by_struct_type(node.table_expr.typ)
638 table_attrs := g.get_table_attrs_by_struct_type(node.table_expr.typ)
639 result_var_name := g.new_tmp_var()
640 g.sql_table_name = g.table.sym(node.table_expr.typ).name
641 g.sql_table_typ = node.table_expr.typ
642
643 // orm_insert needs an SqlStmtLine, build it from SqlExpr (most nodes are the same)
644 hack_stmt_line := g.build_sql_stmt_line_from_sql_expr(node)
645 g.write_orm_insert(hack_stmt_line, table_name, connection_var_name, result_var_name,
646 node.or_expr, table_attrs)
647
648 g.write2(left,
649 'orm__Connection_name_table[${connection_var_name}._typ]._method_last_id(${connection_var_name}._object)')
650}
651
652fn (mut g Gen) build_sql_stmt_line_from_sql_expr(node ast.SqlExpr) ast.SqlStmtLine {
653 mut sub_structs := map[string]ast.SqlStmtLine{}
654 for key, sub in node.sub_structs {
655 sub_structs[key] = g.build_sql_stmt_line_from_sql_expr(sub)
656 }
657 return ast.SqlStmtLine{
658 object_var: node.inserted_var
659 fields: node.fields
660 table_expr: node.table_expr
661 sub_structs: sub_structs
662 scope: node.scope
663 }
664}
665
666// sql_stmt writes C code that calls ORM functions for
667// performing various database operations such as creating and dropping tables,
668// as well as inserting and updating objects.
669// Can contain several queries. For example:
670// ```v
671// sql db {
672// create table User
673// insert user into User
674// }!
675// ```
676// NOTE: Currently, in ORM only the `select` query is an expression.
677// The others are statements.
678fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
679 connection_var_name := g.new_tmp_var()
680
681 g.write_orm_connection_init(connection_var_name, &node.db_expr)
682
683 for line in node.lines {
684 g.sql_stmt_line(line, connection_var_name, node.or_expr)
685 }
686}
687
688// sql_stmt_line writes C code that calls ORM functions for
689// performing various database operations such as creating and dropping tables,
690// as well as inserting and updating objects.
691// It is part of a multi-line query. For example, `create table User`
692fn (mut g Gen) sql_stmt_line(stmt_line ast.SqlStmtLine, connection_var_name string, or_expr ast.OrExpr) {
693 g.sql_last_stmt_out_len = g.out.len
694 mut node := stmt_line
695 table_name := g.get_table_name_by_struct_type(node.table_expr.typ)
696 table_attrs := g.get_table_attrs_by_struct_type(node.table_expr.typ)
697 result_var_name := g.new_tmp_var()
698 g.sql_table_name = g.table.sym(node.table_expr.typ).name
699 g.sql_table_typ = node.table_expr.typ
700
701 if node.kind != .create {
702 node.fields = g.filter_struct_fields_by_orm_attrs(node.fields)
703 }
704
705 if node.kind == .create {
706 g.write_orm_create_table(node, table_name, connection_var_name, result_var_name,
707 table_attrs)
708 } else if node.kind == .drop {
709 g.write_orm_drop_table(node, table_name, connection_var_name, result_var_name, table_attrs)
710 } else if node.kind == .insert {
711 g.write_orm_insert(node, table_name, connection_var_name, result_var_name, or_expr,
712 table_attrs)
713 } else if node.kind == .upsert {
714 g.write_orm_upsert(node, table_name, connection_var_name, result_var_name, or_expr,
715 table_attrs)
716 } else if node.kind == .update {
717 g.write_orm_update(node, table_name, connection_var_name, result_var_name, table_attrs)
718 } else if node.kind == .delete {
719 g.write_orm_delete(node, table_name, connection_var_name, result_var_name, table_attrs)
720 }
721
722 g.or_block(result_var_name, or_expr, ast.int_type.set_flag(.result))
723}
724
725// write_orm_connection_init writes C code that saves the database connection
726// into a variable for later use in ORM queries.
727fn (mut g Gen) write_orm_connection_init(connection_var_name string, db_expr &ast.Expr) {
728 db_expr_type := g.get_db_expr_type(db_expr) or { verror('ORM: unknown db type for ${db_expr}') }
729
730 mut db_ctype_name := g.styp(db_expr_type.clear_flag(.shared_f))
731 is_pointer := db_ctype_name.ends_with('*')
732 reference_sign := if is_pointer { '' } else { '&' }
733 db_ctype_name = db_ctype_name.trim_right('*')
734
735 g.writeln('// ORM')
736 g.write('orm__Connection ${connection_var_name} = ')
737
738 if db_ctype_name == 'orm__Connection' {
739 g.expr(db_expr)
740 g.writeln(';')
741 } else {
742 g.write('(orm__Connection){._${db_ctype_name} = ${reference_sign}')
743 if db_expr_type.has_flag(.shared_f) {
744 g.write('&')
745 }
746 g.expr(db_expr)
747 if db_expr_type.has_flag(.shared_f) {
748 g.write('->val')
749 }
750 g.writeln(', ._typ = _orm__Connection_${db_ctype_name}_index};')
751 }
752}
753
754// write_orm_table_struct writes C code for the orm.Table struct
755fn (mut g Gen) write_orm_table_struct(typ ast.Type) {
756 table_name := g.get_table_name_by_struct_type(typ)
757 table_attrs := g.get_table_attrs_by_struct_type(typ)
758
759 mut fields := g.orm_table_field_names(typ)
760 mut columns := g.orm_table_column_names(typ)
761
762 g.writeln('((orm__Table){')
763 g.indent++
764 g.writeln('.name = _S("${table_name}"),')
765 g.writeln('.fields = builtin__new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
766 g.indent++
767 if fields.len > 0 {
768 g.write('_MOV((string[${fields.len}]){')
769 g.indent++
770 for field_name in fields {
771 g.write('_S("${field_name}"),')
772 }
773 g.indent--
774 g.writeln('})')
775 } else {
776 g.writeln('NULL // No fields')
777 }
778 g.indent--
779 g.writeln('),')
780 g.writeln('.columns = builtin__new_array_from_c_array(${columns.len}, ${columns.len}, sizeof(string),')
781 g.indent++
782 if columns.len > 0 {
783 g.write('_MOV((string[${columns.len}]){')
784 g.indent++
785 for column_name in columns {
786 g.write('_S("${column_name}"),')
787 }
788 g.indent--
789 g.writeln('})')
790 } else {
791 g.writeln('NULL // No columns')
792 }
793 g.indent--
794 g.writeln('),')
795 g.writeln('.attrs = builtin__new_array_from_c_array(${table_attrs.len}, ${table_attrs.len}, sizeof(VAttribute),')
796 g.indent++
797
798 if table_attrs.len > 0 {
799 g.write('_MOV((VAttribute[${table_attrs.len}]){')
800 g.indent++
801 for attr in table_attrs {
802 g.write('(VAttribute){')
803 g.indent++
804 name1 := util.smart_quote(attr.name, false)
805 name := cescape_nonascii(name1)
806 g.write(' .name = _S("${name}"),')
807 g.write(' .has_arg = ${attr.has_arg},')
808 arg1 := util.smart_quote(attr.arg, false)
809 arg := cescape_nonascii(arg1)
810 g.write(' .arg = _S("${arg}"),')
811 g.write(' .kind = ${int(attr.kind)},')
812 g.indent--
813 g.write('},')
814 }
815 g.indent--
816 g.writeln('})')
817 } else {
818 g.writeln('NULL // No attrs')
819 }
820 g.indent--
821 g.writeln(')')
822 g.indent--
823 g.write('})')
824}
825
826// write_orm_create_table writes C code that calls ORM functions for creating tables.
827fn (mut g Gen) write_orm_create_table(node ast.SqlStmtLine, table_name string, connection_var_name string,
828 result_var_name string, _ []ast.Attr) {
829 g.writeln('// sql { create table `${table_name}` }')
830 g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_create(')
831 g.indent++
832 g.writeln('${connection_var_name}._object, // Connection object')
833 g.write_orm_table_struct(node.table_expr.typ)
834 g.writeln(',')
835 g.writeln('builtin__new_array_from_c_array(${node.fields.len}, ${node.fields.len}, sizeof(orm__TableField),')
836 g.indent++
837
838 if node.fields.len > 0 {
839 g.writeln('_MOV((orm__TableField[${node.fields.len}]){')
840 g.indent++
841
842 for field in node.fields {
843 g.writeln('// `${table_name}`.`${field.name}`')
844 // Safety check: ensure field type is valid (not 0 and not still generic)
845 if field.typ == 0 || field.typ.has_flag(.generic) {
846 verror('ORM: field `${field.name}` in table `${table_name}` has unresolved type - this may be due to using a generic struct that was not properly instantiated')
847 }
848 final_field_typ := g.table.final_type(field.typ)
849 sym := g.table.sym(final_field_typ)
850 typ := match true {
851 sym.name == 'time.Time' { '_const_orm__time_' }
852 sym.kind == .enum { '_const_orm__enum_' }
853 else { final_field_typ.idx().str() }
854 }
855
856 g.writeln('(orm__TableField){')
857 g.indent++
858 g.writeln('.name = _S("${field.name}"),')
859 g.writeln('.typ = ${typ}, // `${sym.name}`')
860 g.writeln('.is_arr = ${sym.kind == .array}, ')
861 g.writeln('.nullable = ${final_field_typ.has_flag(.option)},')
862 g.writeln('.default_val = (string){ .str = (byteptr) "${field.default_val}", .is_lit = 1 },')
863 g.writeln('.attrs = builtin__new_array_from_c_array(${field.attrs.len}, ${field.attrs.len}, sizeof(VAttribute),')
864 g.indent++
865
866 if field.attrs.len > 0 {
867 g.write('_MOV((VAttribute[${field.attrs.len}]){')
868 g.indent++
869 for attr in field.attrs {
870 g.write('(VAttribute){')
871 g.indent++
872 name1 := util.smart_quote(attr.name, false)
873 name := cescape_nonascii(name1)
874 g.write(' .name = _S("${name}"),')
875 g.write(' .has_arg = ${attr.has_arg},')
876 arg1 := util.smart_quote(attr.arg, false)
877 arg := cescape_nonascii(arg1)
878 g.write(' .arg = _S("${arg}"),')
879 g.write(' .kind = ${int(attr.kind)},')
880 g.indent--
881 g.write('},')
882 }
883 g.indent--
884 g.writeln('})')
885 } else {
886 g.writeln('NULL // No attrs')
887 }
888 g.indent--
889 g.writeln(')')
890 g.indent--
891 g.writeln('},')
892 }
893
894 g.indent--
895 g.writeln('})')
896 } else {
897 g.writeln('NULL')
898 }
899
900 g.indent--
901 g.writeln(')')
902 g.indent--
903 g.writeln(');')
904}
905
906// write_orm_drop_table writes C code that calls ORM functions for dropping tables.
907fn (mut g Gen) write_orm_drop_table(node ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, _ []ast.Attr) {
908 g.writeln('// sql { drop table `${table_name}` }')
909 g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_drop(')
910 g.indent++
911 g.writeln('${connection_var_name}._object, // Connection object')
912 g.write_orm_table_struct(node.table_expr.typ)
913 g.indent--
914 g.writeln(');')
915}
916
917// write_orm_insert writes C code that calls ORM functions for inserting structs into a table.
918fn (mut g Gen) write_orm_insert(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string,
919 or_expr &ast.OrExpr, _ []ast.Attr) {
920 if node.is_array_insert {
921 g.write_orm_bulk_insert(node, table_name, connection_var_name, result_var_name, or_expr)
922 return
923 }
924 last_ids_variable_name := g.new_tmp_var()
925
926 g.writeln('Array_orm__Primitive ${last_ids_variable_name} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
927 g.write_orm_insert_with_last_ids(node, connection_var_name, table_name, last_ids_variable_name,
928 result_var_name, '', '', or_expr)
929}
930
931fn (mut g Gen) write_orm_bulk_insert(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, or_expr &ast.OrExpr) {
932 fields := g.orm_non_array_fields(node.fields)
933 auto_fields := get_auto_field_idxs(fields)
934 row_type := g.styp(node.table_expr.typ)
935 row_var := g.new_tmp_var()
936 idx_var := g.new_tmp_var()
937 data_var := g.new_tmp_var()
938 g.writeln('${result_name}_void ${result_var_name};')
939 g.writeln('if (${node.object_var}.len == 0) {')
940 g.indent++
941 g.writeln('${result_var_name} = (${result_name}_void){0};')
942 g.indent--
943 g.writeln('} else {')
944 g.indent++
945 if auto_fields.len > 0 {
946 g.writeln('${result_var_name} = (${result_name}_void){0};')
947 g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${node.object_var}.len; ${idx_var}++) {')
948 g.indent++
949 g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${node.object_var}, ${idx_var}));')
950 row_result_var := g.new_tmp_var()
951 mut row_node := *node
952 row_node.object_var = row_var
953 row_node.is_array_insert = false
954 g.write_orm_insert(&row_node, table_name, connection_var_name, row_result_var, or_expr,
955 []ast.Attr{})
956 g.or_block(row_result_var, *or_expr, ast.int_type.set_flag(.result))
957 g.writeln('${result_var_name} = ${row_result_var};')
958 g.indent--
959 g.writeln('}')
960 g.indent--
961 g.writeln('}')
962 return
963 }
964 g.writeln('Array_orm__Primitive ${data_var} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
965 g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${node.object_var}.len; ${idx_var}++) {')
966 g.indent++
967 g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${node.object_var}, ${idx_var}));')
968 for field in fields {
969 g.write('builtin__array_push(&${data_var}, _MOV((orm__Primitive[1]){')
970 g.write_orm_field_access_to_primitive(field, row_var, node.table_expr.typ,
971 g.table.sym(node.table_expr.typ))
972 g.writeln('}));')
973 }
974 g.indent--
975 g.writeln('}')
976 g.writeln('// sql { insert into `${table_name}` }')
977 g.writeln('${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_insert(')
978 g.indent++
979 g.writeln('${connection_var_name}._object, // Connection object')
980 g.write_orm_table_struct(node.table_expr.typ)
981 g.writeln(',')
982 g.writeln('(orm__QueryData){')
983 g.indent++
984 g.writeln('.fields = builtin__new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
985 g.indent++
986 if fields.len > 0 {
987 g.writeln('_MOV((string[${fields.len}]){')
988 g.indent++
989 for f in fields {
990 g.writeln('_S("${g.get_orm_column_name_from_struct_field(f)}"),')
991 }
992 g.indent--
993 g.writeln('})')
994 } else {
995 g.writeln('NULL')
996 }
997 g.indent--
998 g.writeln('),')
999 g.writeln('.data = ${data_var},')
1000 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1001 if auto_fields.len > 0 {
1002 g.writeln('.auto_fields = builtin__new_array_from_c_array(${auto_fields.len}, ${auto_fields.len}, sizeof(${ast.int_type_name}),')
1003 g.indent++
1004 g.write('_MOV((${ast.int_type_name}[${auto_fields.len}]){')
1005 for i in auto_fields {
1006 g.write(' ${i},')
1007 }
1008 g.writeln(' })),')
1009 g.indent--
1010 } else {
1011 g.writeln('.auto_fields = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1012 }
1013 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
1014 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
1015 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
1016 g.writeln('.batch_rows = ${node.object_var}.len,')
1017 g.indent--
1018 g.writeln('}')
1019 g.indent--
1020 g.writeln(');')
1021 g.indent--
1022 g.writeln('}')
1023}
1024
1025fn (mut g Gen) write_orm_upsert(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string,
1026 _ ast.OrExpr, table_attrs []ast.Attr) {
1027 g.writeln('// sql { upsert into `${table_name}` }')
1028 fields := node.fields
1029 auto_fields := get_auto_field_idxs(fields)
1030 mut inserting_object_type := ast.void_type
1031 mut member_access_type := '.'
1032 if node.scope != unsafe { nil } {
1033 if inserting_object := node.scope.find(node.object_var) {
1034 if inserting_object.typ.is_ptr() {
1035 member_access_type = '->'
1036 }
1037 inserting_object_type = g.orm_inserting_object_type(inserting_object)
1038 } else {
1039 inserting_object_type = node.table_expr.typ
1040 }
1041 }
1042 inserting_object_sym := g.table.sym(inserting_object_type)
1043 data_var_name := g.new_tmp_var()
1044 g.writeln('orm__QueryData ${data_var_name} = (orm__QueryData){')
1045 g.indent++
1046 if fields.len > 0 {
1047 g.writeln('.fields = builtin__new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
1048 g.indent++
1049 g.writeln('_MOV((string[${fields.len}]){')
1050 g.indent++
1051 for field in fields {
1052 g.writeln('_S("${g.get_orm_column_name_from_struct_field(field)}"),')
1053 }
1054 g.indent--
1055 g.writeln('})')
1056 g.indent--
1057 g.writeln('),')
1058 g.writeln('.data = builtin__new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(orm__Primitive),')
1059 g.indent++
1060 g.writeln('_MOV((orm__Primitive[${fields.len}]){')
1061 g.indent++
1062 for field in fields {
1063 final_field_typ := g.table.final_type(field.typ)
1064 sym := g.table.sym(final_field_typ)
1065 mut typ := g.orm_primitive_field_name(field.typ)
1066 mut ctyp := sym.cname
1067 typ = vint2int(typ)
1068 var := '${node.object_var}${member_access_type}${orm_field_access_name(field.name)}'
1069 if final_field_typ.has_flag(.option) {
1070 g.writeln('${var}.state == 2 ? _const_orm__null_primitive : orm__${typ}_to_primitive(*(${ctyp}*)(${var}.data)),')
1071 } else if inserting_object_sym.kind == .sum_type {
1072 table_sym := g.table.sym(node.table_expr.typ)
1073 sum_type_var := '(*${node.object_var}._${table_sym.cname})${member_access_type}${orm_field_access_name(field.name)}'
1074 g.writeln('orm__${typ}_to_primitive(${sum_type_var}),')
1075 } else {
1076 g.writeln('orm__${typ}_to_primitive(${var}),')
1077 }
1078 }
1079 g.indent--
1080 g.writeln('})')
1081 g.indent--
1082 g.writeln('),')
1083 } else {
1084 g.writeln('.fields = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
1085 g.writeln('.data = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0),')
1086 }
1087 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1088 if auto_fields.len > 0 {
1089 g.writeln('.auto_fields = builtin__new_array_from_c_array(${auto_fields.len}, ${auto_fields.len}, sizeof(${ast.int_type_name}),')
1090 g.indent++
1091 g.write('_MOV((${ast.int_type_name}[${auto_fields.len}]){')
1092 for i in auto_fields {
1093 g.write(' ${i},')
1094 }
1095 g.writeln(' })),')
1096 g.indent--
1097 } else {
1098 g.writeln('.auto_fields = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1099 }
1100 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
1101 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
1102 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
1103 g.indent--
1104 g.writeln('};')
1105 conflict_groups := g.get_orm_upsert_conflict_groups(fields, table_attrs)
1106 conflict_groups_var_name := g.new_tmp_var()
1107 g.write('Array_Array_string ${conflict_groups_var_name} = ')
1108 g.write_orm_upsert_conflict_groups(conflict_groups)
1109 g.writeln(';')
1110 prepared_var_name := g.new_tmp_var()
1111 g.writeln('orm__UpsertData ${prepared_var_name} = orm__prepare_upsert(${data_var_name}, ${conflict_groups_var_name});')
1112 g.writeln('${result_name}_void ${result_var_name};')
1113 g.writeln('if (!${prepared_var_name}.valid) {')
1114 g.indent++
1115 g.write('${result_var_name} = orm__upsert_missing_conflict_error(')
1116 g.write_orm_table_struct(node.table_expr.typ)
1117 g.writeln(');')
1118 g.indent--
1119 g.writeln('} else {')
1120 g.indent++
1121 select_result_var_name := g.new_tmp_var()
1122 g.writeln('${result_name}_Array_Array_orm__Primitive ${select_result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_select(')
1123 g.indent++
1124 g.writeln('${connection_var_name}._object, // Connection object')
1125 g.writeln('(orm__SelectConfig){')
1126 g.indent++
1127 g.write('.table = ')
1128 g.write_orm_table_struct(node.table_expr.typ)
1129 g.writeln(',')
1130 g.writeln('.aggregate_kind = orm__AggregateKind__count,')
1131 g.writeln('.aggregate_field = _S(""),')
1132 g.writeln('.has_where = true,')
1133 g.writeln('.has_order = false,')
1134 g.writeln('.order = _S(""),')
1135 g.writeln('.order_type = orm__OrderType__asc,')
1136 g.writeln('.has_limit = false,')
1137 g.writeln('.primary = _S("id"),')
1138 g.writeln('.has_offset = false,')
1139 g.writeln('.has_distinct = false,')
1140 g.writeln('.fields = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
1141 g.writeln('.select_exprs = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
1142 g.writeln('.types = builtin__new_array_from_c_array(1, 1, sizeof(${ast.int_type_name}), _MOV((${ast.int_type_name}[1]){ ${ast.int_type.idx()}, })),')
1143 g.writeln('.joins = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__JoinConfig), 0),')
1144 g.indent--
1145 g.writeln('},')
1146 g.writeln('(orm__QueryData){')
1147 g.indent++
1148 g.writeln('.fields = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
1149 g.writeln('.data = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0),')
1150 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1151 g.writeln('.auto_fields = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1152 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
1153 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
1154 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
1155 g.indent--
1156 g.writeln('},')
1157 g.writeln('${prepared_var_name}.where')
1158 g.indent--
1159 g.writeln(');')
1160 g.writeln('${result_var_name}.is_error = ${select_result_var_name}.is_error;')
1161 g.writeln('${result_var_name}.err = ${select_result_var_name}.err;')
1162 g.writeln('if (!${result_var_name}.is_error) {')
1163 g.indent++
1164 count_rows_var_name := g.new_tmp_var()
1165 count_var_name := g.new_tmp_var()
1166 g.writeln('Array_Array_orm__Primitive ${count_rows_var_name} = (*(Array_Array_orm__Primitive*)${select_result_var_name}.data);')
1167 g.writeln('${ast.int_type_name} ${count_var_name} = orm__upsert_count(${count_rows_var_name});')
1168 g.writeln('if (${count_var_name} == 0) {')
1169 g.indent++
1170 g.writeln('${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_insert(')
1171 g.indent++
1172 g.writeln('${connection_var_name}._object, // Connection object')
1173 g.write_orm_table_struct(node.table_expr.typ)
1174 g.writeln(',')
1175 g.writeln('${data_var_name}')
1176 g.indent--
1177 g.writeln(');')
1178 g.indent--
1179 g.writeln('} else if (${count_var_name} == 1) {')
1180 g.indent++
1181 g.writeln('if (${prepared_var_name}.insert_data.fields.len == 0) {')
1182 g.indent++
1183 g.writeln('${result_var_name} = (${result_name}_void){0};')
1184 g.indent--
1185 g.writeln('} else {')
1186 g.indent++
1187 g.writeln('${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_update(')
1188 g.indent++
1189 g.writeln('${connection_var_name}._object, // Connection object')
1190 g.write_orm_table_struct(node.table_expr.typ)
1191 g.writeln(',')
1192 g.writeln('${prepared_var_name}.insert_data,')
1193 g.writeln('${prepared_var_name}.where')
1194 g.indent--
1195 g.writeln(');')
1196 g.indent--
1197 g.writeln('}')
1198 g.indent--
1199 g.writeln('} else {')
1200 g.indent++
1201 g.write('${result_var_name} = orm__upsert_ambiguous_error(')
1202 g.write_orm_table_struct(node.table_expr.typ)
1203 g.writeln(');')
1204 g.indent--
1205 g.writeln('}')
1206 g.indent--
1207 g.writeln('}')
1208 g.indent--
1209 g.writeln('}')
1210}
1211
1212// write_orm_update writes C code that calls ORM functions for updating rows.
1213fn (mut g Gen) write_orm_update(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, _ []ast.Attr) {
1214 mut dynamic_update_data_var := ''
1215 if node.is_dynamic {
1216 dynamic_update_data_var = g.emit_dynamic_sql_query_data(node.update_data_expr)
1217 }
1218 mut dynamic_where_var := ''
1219 if dynamic_where_expr := g.resolve_sql_query_data_expr(node.where_expr) {
1220 dynamic_where_var = g.emit_sql_query_data(dynamic_where_expr, true)
1221 }
1222 if node.is_array_update {
1223 g.write_orm_bulk_update(node, table_name, connection_var_name, result_var_name)
1224 return
1225 }
1226 g.writeln('// sql { update `${table_name}` }')
1227 g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_update(')
1228 g.indent++
1229 g.writeln('${connection_var_name}._object, // Connection object')
1230 g.write_orm_table_struct(node.table_expr.typ)
1231 g.writeln(',')
1232 if node.is_dynamic {
1233 g.writeln('${dynamic_update_data_var},')
1234 } else {
1235 g.writeln('(orm__QueryData){')
1236 g.indent++
1237 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
1238 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
1239 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1240 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
1241
1242 if node.updated_columns.len > 0 {
1243 g.writeln('.fields = builtin__new_array_from_c_array(${node.updated_columns.len}, ${node.updated_columns.len}, sizeof(string),')
1244 g.indent++
1245 g.writeln('_MOV((string[${node.updated_columns.len}]){')
1246 g.indent++
1247 for field in node.updated_columns {
1248 g.writeln('_S("${field}"),')
1249 }
1250 g.indent--
1251 g.writeln('})')
1252 g.indent--
1253 } else {
1254 g.writeln('.fields = builtin____new_array_with_default_noscan(${node.updated_columns.len}, ${node.updated_columns.len}, sizeof(string), 0')
1255 }
1256
1257 g.writeln2('),',
1258 '.data = builtin__new_array_from_c_array(${node.update_exprs.len}, ${node.update_exprs.len}, sizeof(orm__Primitive),')
1259
1260 if node.update_exprs.len > 0 {
1261 g.indent++
1262 g.writeln('_MOV((orm__Primitive[${node.update_exprs.len}]){')
1263 g.indent++
1264 for i, e in node.update_exprs {
1265 column := node.updated_columns[i]
1266 if field := g.get_orm_field_by_column_name(node.fields, column) {
1267 final_field_typ := g.table.final_type(field.typ.clear_flag(.option))
1268 sym := g.table.sym(final_field_typ)
1269 if sym.kind == .struct && sym.name != 'time.Time' {
1270 g.write_orm_struct_field_expr_to_primitive(field, e)
1271 continue
1272 }
1273 }
1274 g.write_orm_expr_to_primitive(e)
1275 }
1276 g.indent--
1277 g.writeln('})')
1278 g.indent--
1279 }
1280
1281 g.writeln('),')
1282 g.indent--
1283 g.writeln('},')
1284 }
1285 if dynamic_where_var != '' {
1286 g.writeln(dynamic_where_var)
1287 } else {
1288 g.write_orm_where(node.where_expr)
1289 }
1290 g.indent--
1291 g.writeln(');')
1292}
1293
1294fn (mut g Gen) write_orm_bulk_update(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string) {
1295 row_type := g.styp(node.table_expr.typ)
1296 row_var := g.new_tmp_var()
1297 idx_var := g.new_tmp_var()
1298 data_var := g.new_tmp_var()
1299 where_data_var := g.new_tmp_var()
1300 where_fields_var := g.new_tmp_var()
1301 where_kinds_var := g.new_tmp_var()
1302 where_is_and_var := g.new_tmp_var()
1303 key_field := g.get_orm_field_by_column_name(node.fields, node.array_update_key) or {
1304 verror('ORM: field "${node.array_update_key}" does not exist on "${g.sql_table_name}"')
1305 }
1306 g.writeln('${result_name}_void ${result_var_name};')
1307 g.writeln('if (${node.array_update_var}.len == 0) {')
1308 g.indent++
1309 g.writeln('${result_var_name} = (${result_name}_void){0};')
1310 g.indent--
1311 g.writeln('} else {')
1312 g.indent++
1313 g.writeln('Array_orm__Primitive ${data_var} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
1314 g.writeln('Array_orm__Primitive ${where_data_var} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
1315 g.writeln('Array_string ${where_fields_var} = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0);')
1316 g.writeln('Array_orm__OperationKind ${where_kinds_var} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0);')
1317 g.writeln('Array_bool ${where_is_and_var} = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0);')
1318 g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${node.array_update_var}.len; ${idx_var}++) {')
1319 g.indent++
1320 g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${node.array_update_var}, ${idx_var}));')
1321 g.writeln('builtin__array_push(&${where_fields_var}, _MOV((string[1]){ _S("${node.array_update_key}") }));')
1322 g.writeln('builtin__array_push(&${where_kinds_var}, _MOV((orm__OperationKind[1]){ orm__OperationKind__eq }));')
1323 g.writeln('if (${idx_var} > 0) {')
1324 g.indent++
1325 g.writeln('builtin__array_push(&${where_is_and_var}, _MOV((bool[1]){ false }));')
1326 g.indent--
1327 g.writeln('}')
1328 g.write('builtin__array_push(&${where_data_var}, _MOV((orm__Primitive[1]){')
1329 g.write_orm_field_access_to_primitive(key_field, row_var, node.table_expr.typ,
1330 g.table.sym(node.table_expr.typ))
1331 g.writeln('}));')
1332 g.indent--
1333 g.writeln('}')
1334 for expr in node.update_exprs {
1335 selector := expr as ast.SelectorExpr
1336 value_field := get_orm_field_by_struct_field_name(node.fields, selector.field_name) or {
1337 verror('ORM: field "${selector.field_name}" does not exist on "${g.sql_table_name}"')
1338 }
1339 g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${node.array_update_var}.len; ${idx_var}++) {')
1340 g.indent++
1341 g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${node.array_update_var}, ${idx_var}));')
1342 g.write('builtin__array_push(&${data_var}, _MOV((orm__Primitive[1]){')
1343 g.write_orm_field_access_to_primitive(key_field, row_var, node.table_expr.typ,
1344 g.table.sym(node.table_expr.typ))
1345 g.writeln('}));')
1346 g.write('builtin__array_push(&${data_var}, _MOV((orm__Primitive[1]){')
1347 g.write_orm_field_access_to_primitive(value_field, row_var, node.table_expr.typ,
1348 g.table.sym(node.table_expr.typ))
1349 g.writeln('}));')
1350 g.indent--
1351 g.writeln('}')
1352 }
1353 g.writeln('// sql { update `${table_name}` }')
1354 g.writeln('${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_update(')
1355 g.indent++
1356 g.writeln('${connection_var_name}._object, // Connection object')
1357 g.write_orm_table_struct(node.table_expr.typ)
1358 g.writeln(',')
1359 g.writeln('(orm__QueryData){')
1360 g.indent++
1361 g.writeln('.fields = builtin__new_array_from_c_array(${node.updated_columns.len}, ${node.updated_columns.len}, sizeof(string),')
1362 g.indent++
1363 g.writeln('_MOV((string[${node.updated_columns.len}]){')
1364 g.indent++
1365 for field in node.updated_columns {
1366 g.writeln('_S("${field}"),')
1367 }
1368 g.indent--
1369 g.writeln('})')
1370 g.indent--
1371 g.writeln('),')
1372 g.writeln('.data = ${data_var},')
1373 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1374 g.writeln('.auto_fields = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1375 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
1376 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
1377 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
1378 g.writeln('.batch_rows = ${node.array_update_var}.len,')
1379 g.writeln('.batch_key = _S("${node.array_update_key}"),')
1380 g.indent--
1381 g.writeln('},')
1382 g.writeln('(orm__QueryData){')
1383 g.indent++
1384 g.writeln('.fields = ${where_fields_var},')
1385 g.writeln('.data = ${where_data_var},')
1386 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1387 g.writeln('.auto_fields = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1388 g.writeln('.kinds = ${where_kinds_var},')
1389 g.writeln('.is_and = ${where_is_and_var},')
1390 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
1391 g.indent--
1392 g.writeln('}')
1393 g.indent--
1394 g.writeln(');')
1395 g.indent--
1396 g.writeln('}')
1397}
1398
1399fn (mut g Gen) write_orm_field_access_to_primitive(field ast.StructField, object_var string, table_typ ast.Type,
1400 inserting_object_sym &ast.TypeSymbol) {
1401 final_field_typ := g.table.final_type(field.typ)
1402 mut sym := g.table.sym(final_field_typ)
1403 mut typ := g.orm_primitive_field_name(field.typ)
1404 mut ctyp := sym.cname
1405 if sym.kind == .struct && ctyp != 'time__Time' {
1406 foreign_info := sym.info as ast.Struct
1407 primary_field := g.get_orm_struct_primary_field(foreign_info.fields) or {
1408 verror('ORM: struct field `${field.name}` of type `${sym.name}` has no primary field')
1409 }
1410 g.write_orm_field_access_to_primitive(primary_field,
1411 '${object_var}.${orm_field_access_name(field.name)}',
1412 final_field_typ.clear_flag(.option), g.table.sym(final_field_typ.clear_flag(.option)))
1413 return
1414 }
1415 typ = vint2int(typ)
1416 field_access := if inserting_object_sym.kind == .sum_type {
1417 table_sym := g.table.sym(table_typ)
1418 '(*${object_var}._${table_sym.cname}).${orm_field_access_name(field.name)}'
1419 } else {
1420 '${object_var}.${orm_field_access_name(field.name)}'
1421 }
1422 if final_field_typ.has_flag(.option) {
1423 g.writeln('${field_access}.state == 2 ? _const_orm__null_primitive : orm__${typ}_to_primitive(*(${ctyp}*)(${field_access}.data)),')
1424 } else {
1425 g.writeln('orm__${typ}_to_primitive(${field_access}),')
1426 }
1427}
1428
1429fn get_orm_field_by_struct_field_name(fields []ast.StructField, name string) ?ast.StructField {
1430 for field in fields {
1431 if field.name == name {
1432 return field
1433 }
1434 }
1435 return none
1436}
1437
1438// write_orm_delete writes C code that calls ORM functions for deleting rows.
1439fn (mut g Gen) write_orm_delete(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, _ []ast.Attr) {
1440 g.writeln('// sql { delete from `${table_name}` }')
1441 g.writeln('${result_name}_void ${result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method__v_delete(')
1442 g.indent++
1443 g.writeln('${connection_var_name}._object, // Connection object')
1444 g.write_orm_table_struct(node.table_expr.typ)
1445 g.writeln(',')
1446 g.write_orm_where(node.where_expr)
1447 g.indent--
1448 g.writeln(');')
1449}
1450
1451// write_orm_insert_with_last_ids writes C code that calls ORM functions for
1452// inserting a struct into a table, saving inserted `id` into a passed variable.
1453fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_var_name string, table_name string,
1454 last_ids_arr string, res string, pid string, fkey string, or_expr ast.OrExpr) {
1455 mut subs := []ast.SqlStmtLine{}
1456
1457 mut subs_unwrapped_c_typ := []string{}
1458 mut arrs := []ast.SqlStmtLine{}
1459 mut fkeys := []string{}
1460 mut field_names := []string{}
1461 mut opt_fields := []int{}
1462
1463 for field in node.fields {
1464 final_field_typ := g.table.final_type(field.typ)
1465 sym := g.table.sym(final_field_typ)
1466 if sym.kind == .struct && sym.name != 'time.Time' {
1467 if field.name in node.sub_structs {
1468 subs << unsafe { node.sub_structs[field.name] }
1469
1470 unwrapped_c_typ := g.styp(final_field_typ.clear_flag(.option))
1471 subs_unwrapped_c_typ << if final_field_typ.has_flag(.option) {
1472 unwrapped_c_typ
1473 } else {
1474 ''
1475 }
1476 }
1477 } else if sym.kind == .array {
1478 // Handle foreign keys
1479 if attr := field.attrs.find_first('fkey') {
1480 fkeys << attr.arg
1481 } else {
1482 verror('missing fkey attribute')
1483 }
1484 if final_field_typ.has_flag(.option) {
1485 opt_fields << arrs.len
1486 }
1487 if field.name in node.sub_structs {
1488 arrs << unsafe { node.sub_structs[field.name] }
1489 }
1490 field_names << field.name
1491 }
1492 }
1493
1494 fields := g.orm_non_array_fields(node.fields)
1495 auto_fields := get_auto_field_idxs(fields)
1496
1497 primary_field := g.get_orm_struct_primary_field(fields) or { ast.StructField{} }
1498
1499 is_serial := (primary_field.attrs.contains_arg('sql', 'serial')
1500 || primary_field.attrs.contains('serial')) && primary_field.typ == ast.int_type
1501
1502 mut inserting_object_type := ast.void_type
1503 mut member_access_type := '.'
1504 if node.scope != unsafe { nil } {
1505 if inserting_object := node.scope.find(node.object_var) {
1506 if inserting_object.typ.is_ptr() {
1507 member_access_type = '->'
1508 }
1509 inserting_object_type = g.orm_inserting_object_type(inserting_object)
1510 } else {
1511 inserting_object_type = node.table_expr.typ
1512 }
1513 }
1514
1515 inserting_object_sym := g.table.sym(inserting_object_type)
1516 for i, mut sub in subs {
1517 if subs_unwrapped_c_typ[i].len > 0 {
1518 var := '${node.object_var}${member_access_type}${sub.object_var}'
1519 g.writeln('if(${var}.state == 0) {')
1520 g.indent++
1521 sub.object_var = '(*(${subs_unwrapped_c_typ[i]}*)${node.object_var}${member_access_type}${sub.object_var}.data)'
1522 } else {
1523 sub.object_var = '${node.object_var}${member_access_type}${sub.object_var}'
1524 }
1525 g.sql_stmt_line(sub, connection_var_name, or_expr)
1526 g.writeln('builtin__array_push(&${last_ids_arr}, _MOV((orm__Primitive[1]){')
1527 g.writeln('\torm__int_to_primitive(orm__Connection_name_table[${connection_var_name}._typ]._method_last_id(${connection_var_name}._object))}));')
1528 if subs_unwrapped_c_typ[i].len > 0 {
1529 g.indent--
1530 g.writeln('} else {')
1531 g.writeln('\tbuiltin__array_push(&${last_ids_arr}, _MOV((orm__Primitive[1]){ _const_orm__null_primitive }));')
1532 g.writeln('}')
1533 }
1534 }
1535
1536 g.writeln('// sql { insert into `${table_name}` }')
1537 g.writeln('${result_name}_void ${res} = orm__Connection_name_table[${connection_var_name}._typ]._method_insert(')
1538 g.indent++
1539 g.writeln('${connection_var_name}._object, // Connection object')
1540 g.write_orm_table_struct(node.table_expr.typ)
1541 g.writeln(',')
1542 g.writeln('(orm__QueryData){')
1543 g.indent++
1544 g.writeln('.fields = builtin__new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
1545 g.indent++
1546
1547 if fields.len > 0 {
1548 g.writeln('_MOV((string[${fields.len}]){ ')
1549 g.indent++
1550 for f in fields {
1551 g.writeln('_S("${g.get_orm_column_name_from_struct_field(f)}"),')
1552 }
1553 g.indent--
1554 g.writeln('})')
1555 } else {
1556 g.writeln('NULL')
1557 }
1558 g.indent--
1559 g.writeln('),')
1560
1561 g.writeln('.data = builtin__new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(orm__Primitive),')
1562 g.indent++
1563
1564 if fields.len > 0 {
1565 g.writeln('_MOV((orm__Primitive[${fields.len}]){')
1566 g.indent++
1567 mut structs := 0
1568 for field in fields {
1569 if field.name == fkey {
1570 g.writeln('${pid}, ')
1571 continue
1572 }
1573 final_field_typ := g.table.final_type(field.typ)
1574 mut sym := g.table.sym(final_field_typ)
1575 mut typ := g.orm_primitive_field_name(field.typ)
1576 mut ctyp := sym.cname
1577 if sym.kind == .struct && ctyp != 'time__Time' {
1578 g.writeln('(*(orm__Primitive*) builtin__array_get(${last_ids_arr}, ${structs})),')
1579 structs++
1580 continue
1581 }
1582 // fields processed hereafter can be NULL...
1583 typ = vint2int(typ)
1584 var := '${node.object_var}${member_access_type}${orm_field_access_name(field.name)}'
1585 if final_field_typ.has_flag(.option) {
1586 g.writeln('${var}.state == 2? _const_orm__null_primitive : orm__${typ}_to_primitive(*(${ctyp}*)(${var}.data)),')
1587 } else if inserting_object_sym.kind == .sum_type {
1588 table_sym := g.table.sym(node.table_expr.typ)
1589 sum_type_var := '(*${node.object_var}._${table_sym.cname})${member_access_type}${orm_field_access_name(field.name)}'
1590 g.writeln('orm__${typ}_to_primitive(${sum_type_var}),')
1591 } else {
1592 g.writeln('orm__${typ}_to_primitive(${var}),')
1593 }
1594 }
1595 g.indent--
1596 g.writeln('})')
1597 } else {
1598 g.write('NULL')
1599 }
1600 g.indent--
1601 g.writeln('),')
1602 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1603 if auto_fields.len > 0 {
1604 g.writeln('.auto_fields = builtin__new_array_from_c_array(${auto_fields.len}, ${auto_fields.len}, sizeof(${ast.int_type_name}),')
1605 g.indent++
1606 g.write('_MOV((${ast.int_type_name}[${auto_fields.len}]){')
1607 for i in auto_fields {
1608 g.write(' ${i},')
1609 }
1610 g.writeln(' })),')
1611 g.indent--
1612 } else {
1613 g.writeln('.auto_fields = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1614 }
1615 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
1616 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
1617 g.indent--
1618 g.writeln('}')
1619 g.indent--
1620 g.writeln(');')
1621 // Validate main insertion success otherwise, handled and propagated error.
1622 g.or_block(res, or_expr, ast.int_type.set_flag(.result))
1623
1624 if arrs.len > 0 {
1625 mut id_name := g.new_tmp_var()
1626 if is_serial {
1627 // use last_insert_id if current struct has `int [primary; sql: serial]`
1628 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));')
1629 } else {
1630 // else use the primary key value
1631 mut typ := g.orm_primitive_field_name(primary_field.typ)
1632 typ = vint2int(typ)
1633 g.writeln('orm__Primitive ${id_name} = orm__${typ}_to_primitive(${node.object_var}${member_access_type}${orm_field_access_name(primary_field.name)});')
1634 }
1635 for i, mut arr in arrs {
1636 idx := g.new_tmp_var()
1637 ctyp := g.styp(arr.table_expr.typ)
1638 is_option := opt_fields.contains(i)
1639 if is_option {
1640 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}++) {')
1641 } else {
1642 g.writeln('for (${ast.int_type_name} ${idx} = 0; ${idx} < ${node.object_var}${member_access_type}${arr.object_var}.len; ${idx}++) {')
1643 }
1644 g.indent++
1645 last_ids := g.new_tmp_var()
1646 res_ := g.new_tmp_var()
1647 tmp_var := g.new_tmp_var()
1648 g.writeln('Array_orm__Primitive ${last_ids} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
1649 if is_option {
1650 g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)builtin__array_get(*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data, ${idx}));')
1651 } else {
1652 g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)builtin__array_get(${node.object_var}${member_access_type}${arr.object_var}, ${idx}));')
1653 }
1654 arr.object_var = tmp_var
1655 mut fff := []ast.StructField{}
1656 for f in arr.fields {
1657 mut skip := false
1658 for attr in f.attrs {
1659 if attr.name == 'skip' {
1660 skip = true
1661 }
1662 if attr.name == 'sql' && attr.arg == '-' {
1663 skip = true
1664 }
1665 }
1666 if !skip {
1667 fff << f
1668 }
1669 }
1670 arr.fields = fff.clone()
1671 unsafe { fff.free() }
1672 g.write_orm_insert_with_last_ids(arr, connection_var_name,
1673 g.get_table_name_by_struct_type(arr.table_expr.typ), last_ids, res_, id_name,
1674 fkeys[i], or_expr)
1675 g.indent--
1676 g.writeln('}')
1677 }
1678 }
1679}
1680
1681// write_orm_expr_to_primitive writes C code for casting expressions into a primitive type
1682// by checking support expressions and their types.
1683fn (mut g Gen) write_orm_expr_to_primitive(expr ast.Expr) {
1684 match expr {
1685 ast.InfixExpr {
1686 g.write_orm_primitive(g.table.find_type('orm.InfixType'), expr)
1687 }
1688 ast.StringLiteral {
1689 g.write_orm_primitive(ast.string_type, expr)
1690 }
1691 ast.StringInterLiteral {
1692 g.write_orm_primitive(ast.string_type, expr)
1693 }
1694 ast.IntegerLiteral {
1695 g.write_orm_primitive(ast.int_type, expr)
1696 }
1697 ast.BoolLiteral {
1698 g.write_orm_primitive(ast.bool_type, expr)
1699 }
1700 ast.EnumVal {
1701 g.write_orm_primitive(ast.i64_type, expr)
1702 }
1703 ast.Ident {
1704 info := expr.info as ast.IdentVar
1705 typ := g.orm_if_guard_ident_type(expr, info.typ)
1706 g.write_orm_primitive(orm_expr_effective_type(expr, typ), expr)
1707 }
1708 ast.SelectorExpr {
1709 g.write_orm_primitive(orm_expr_effective_type(expr, expr.typ), expr)
1710 }
1711 ast.CallExpr {
1712 g.write_orm_primitive(orm_expr_effective_type(expr, expr.return_type), expr)
1713 }
1714 ast.CastExpr {
1715 g.write_orm_primitive(expr.typ, expr)
1716 }
1717 ast.Nil {
1718 g.write_orm_primitive(ast.none_type, expr)
1719 }
1720 ast.IfExpr {
1721 g.write_orm_primitive(expr.typ, expr)
1722 }
1723 ast.None {
1724 g.write_orm_primitive(ast.none_type, expr)
1725 }
1726 else {
1727 verror('ORM: ${expr.type_name()} is not supported')
1728 }
1729 }
1730}
1731
1732fn (mut g Gen) write_orm_struct_field_expr_to_primitive(field ast.StructField, expr ast.Expr) {
1733 if expr is ast.None || expr is ast.Nil {
1734 g.writeln('_const_orm__null_primitive,')
1735 return
1736 }
1737 final_field_typ := g.table.final_type(field.typ.clear_flag(.option))
1738 foreign_sym := g.table.sym(final_field_typ)
1739 foreign_info := foreign_sym.info as ast.Struct
1740 primary_field := g.get_orm_struct_primary_field(foreign_info.fields) or {
1741 verror('ORM: struct field `${field.name}` of type `${foreign_sym.name}` has no primary field')
1742 }
1743 g.write_orm_primitive(primary_field.typ, ast.SelectorExpr{
1744 pos: expr.pos()
1745 field_name: primary_field.name
1746 expr: expr
1747 expr_type: orm_expr_effective_type(expr, field.typ).clear_flag(.option).clear_flag(.result)
1748 typ: primary_field.typ
1749 scope: unsafe { nil }
1750 })
1751}
1752
1753fn orm_expr_effective_type(expr ast.Expr, typ ast.Type) ast.Type {
1754 return match expr {
1755 ast.CallExpr {
1756 if expr.or_block.kind != .absent {
1757 typ.clear_option_and_result()
1758 } else {
1759 typ
1760 }
1761 }
1762 ast.Ident {
1763 if expr.or_expr.kind != .absent {
1764 typ.clear_option_and_result()
1765 } else {
1766 typ
1767 }
1768 }
1769 ast.SelectorExpr {
1770 if expr.or_block.kind != .absent {
1771 typ.clear_option_and_result()
1772 } else {
1773 typ
1774 }
1775 }
1776 else {
1777 typ
1778 }
1779 }
1780}
1781
1782// write_orm_primitive writes C code for casting expressions into a primitive type,
1783// which will be used in low-level database libs.
1784fn (mut g Gen) write_orm_primitive(t ast.Type, expr ast.Expr) {
1785 if t == 0 {
1786 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)}')
1787 }
1788 final_field_typ := g.table.final_type(t)
1789 mut sym := g.table.sym(final_field_typ)
1790 mut typ := sym.cname
1791 if typ == 'orm__Primitive' {
1792 g.expr(expr)
1793 g.writeln(',')
1794 return
1795 }
1796 if typ == 'none' {
1797 g.writeln('_const_orm__null_primitive,')
1798 return
1799 }
1800
1801 if expr is ast.InfixExpr {
1802 g.writeln('orm__infix_to_primitive((orm__InfixType){')
1803 g.indent++
1804 g.write('.name = _S("${expr.left}"),')
1805 mut kind := match expr.op {
1806 .plus {
1807 'orm__MathOperationKind__add'
1808 }
1809 .minus {
1810 'orm__MathOperationKind__sub'
1811 }
1812 .div {
1813 'orm__MathOperationKind__div'
1814 }
1815 .mul {
1816 'orm__MathOperationKind__mul'
1817 }
1818 else {
1819 ''
1820 }
1821 }
1822
1823 g.writeln(' .operator = ${kind},')
1824 g.write(' .right = ')
1825 g.write_orm_expr_to_primitive(expr.right)
1826 g.indent--
1827 g.writeln(' }),')
1828 } else {
1829 if typ == 'time__Time' {
1830 typ = 'time'
1831 }
1832
1833 if t.has_flag(.option) {
1834 typ = 'option_${typ}'
1835 } else if g.table.final_sym(t.clear_flag(.option)).kind == .enum {
1836 typ = 'i64'
1837 } else if g.table.final_sym(t).kind == .array {
1838 typ = g.table.sym(g.table.final_type(t)).cname.to_lower()
1839 }
1840 typ = vint2int(typ)
1841 g.write('orm__${typ}_to_primitive(')
1842 if expr is ast.CallExpr {
1843 g.call_expr(expr)
1844 } else {
1845 g.left_is_opt = true
1846 g.expr(expr)
1847 }
1848 g.writeln('),')
1849 }
1850}
1851
1852// write_orm_where writes C code that generates
1853// the `QueryData` structure for passing it into ORM methods.
1854fn (mut g Gen) write_orm_where(where_expr ast.Expr) {
1855 mut fields := []string{}
1856 mut kinds := []string{}
1857 mut parentheses := [][]int{}
1858 mut data := []ast.Expr{}
1859 mut is_ands := []bool{}
1860
1861 g.writeln('// ORM where')
1862
1863 g.writeln('(orm__QueryData){')
1864 g.indent++
1865 g.write_orm_where_expr(where_expr, mut fields, mut parentheses, mut kinds, mut data, mut
1866 is_ands)
1867 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1868 if fields.len > 0 {
1869 g.writeln('.fields = builtin__new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
1870 g.indent++
1871 g.writeln('_MOV((string[${fields.len}]){')
1872 g.indent++
1873 for field in fields {
1874 g.writeln('_S("${field}"),')
1875 }
1876 g.indent--
1877 g.writeln('})')
1878 g.indent--
1879 } else {
1880 g.writeln('.fields = builtin____new_array_with_default_noscan(${fields.len}, ${fields.len}, sizeof(string), 0')
1881 }
1882 g.writeln('),')
1883
1884 g.writeln('.data = builtin__new_array_from_c_array(${data.len}, ${data.len}, sizeof(orm__Primitive),')
1885 g.indent++
1886 if data.len > 0 {
1887 g.writeln('_MOV((orm__Primitive[${data.len}]){')
1888 g.indent++
1889 for e in data {
1890 g.write_orm_expr_to_primitive(e)
1891 }
1892 g.indent--
1893 g.writeln('})')
1894 } else {
1895 g.writeln('0')
1896 }
1897 g.indent--
1898 g.writeln('),')
1899
1900 g.write('.parentheses = ')
1901 if parentheses.len > 0 {
1902 g.write('builtin__new_array_from_c_array(${parentheses.len}, ${parentheses.len}, sizeof(Array_${ast.int_type_name}), _MOV((Array_${ast.int_type_name}[${parentheses.len}]){')
1903 for par in parentheses {
1904 if par.len > 0 {
1905 g.write('builtin__new_array_from_c_array(${par.len}, ${par.len}, sizeof(${ast.int_type_name}), _MOV((${ast.int_type_name}[${par.len}]){')
1906 for val in par {
1907 g.write('${val},')
1908 }
1909 g.write('})),')
1910 } else {
1911 g.write('builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
1912 }
1913 }
1914 g.write('}))')
1915 } else {
1916 g.write('builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0)')
1917 }
1918 g.writeln(',')
1919
1920 if kinds.len > 0 {
1921 g.writeln('.kinds = builtin__new_array_from_c_array(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind),')
1922 g.indent++
1923 g.writeln('_MOV((orm__OperationKind[${kinds.len}]){')
1924 g.indent++
1925 for k in kinds {
1926 g.writeln('${k},')
1927 }
1928 g.indent--
1929 g.writeln('})')
1930 g.indent--
1931 } else {
1932 g.write('.kinds = builtin____new_array_with_default_noscan(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind), 0')
1933 }
1934 g.writeln('),')
1935
1936 if is_ands.len > 0 {
1937 g.write('.is_and = builtin__new_array_from_c_array(${is_ands.len}, ${is_ands.len}, sizeof(bool),')
1938 g.indent++
1939 g.writeln('_MOV((bool[${is_ands.len}]){')
1940 g.indent++
1941 for is_and in is_ands {
1942 g.writeln('${is_and},')
1943 }
1944 g.indent--
1945 g.write('})')
1946 g.indent--
1947 } else {
1948 g.write('.is_and = builtin____new_array_with_default_noscan(${is_ands.len}, ${is_ands.len}, sizeof(bool), 0')
1949 }
1950 g.indent--
1951 g.writeln2('),', '}')
1952}
1953
1954// write_orm_where_expr writes C code that generates expression which is used in the `QueryData`.
1955fn (mut g Gen) write_orm_where_expr(expr ast.Expr, mut fields []string, mut parentheses [][]int, mut kinds []string,
1956 mut data []ast.Expr, mut is_and []bool) {
1957 match expr {
1958 ast.InfixExpr {
1959 g.sql_side = .left
1960 g.write_orm_where_expr(expr.left, mut fields, mut parentheses, mut kinds, mut data, mut
1961 is_and)
1962 is_nil_comparison := expr.right is ast.Nil && expr.op in [.eq, .ne]
1963 mut ignore_rhs := expr.op in [.key_is, .not_is] || is_nil_comparison
1964 mut kind := match expr.op {
1965 .ne {
1966 if is_nil_comparison {
1967 'orm__OperationKind__is_not_null'
1968 } else {
1969 'orm__OperationKind__neq'
1970 }
1971 }
1972 .eq {
1973 if is_nil_comparison {
1974 'orm__OperationKind__is_null'
1975 } else {
1976 'orm__OperationKind__eq'
1977 }
1978 }
1979 .lt {
1980 'orm__OperationKind__lt'
1981 }
1982 .gt {
1983 'orm__OperationKind__gt'
1984 }
1985 .ge {
1986 'orm__OperationKind__ge'
1987 }
1988 .le {
1989 'orm__OperationKind__le'
1990 }
1991 .key_like {
1992 'orm__OperationKind__orm_like'
1993 }
1994 .key_ilike {
1995 'orm__OperationKind__orm_ilike'
1996 }
1997 .key_is {
1998 'orm__OperationKind__is_null'
1999 }
2000 .not_is {
2001 'orm__OperationKind__is_not_null'
2002 }
2003 .key_in {
2004 'orm__OperationKind__in'
2005 }
2006 .not_in {
2007 'orm__OperationKind__not_in'
2008 }
2009 else {
2010 ''
2011 }
2012 }
2013
2014 if kind == '' {
2015 if expr.op == .logical_or {
2016 is_and << false
2017 } else if expr.op == .and {
2018 is_and << true
2019 } else {
2020 kind = 'orm__OperationKind__eq'
2021 }
2022 }
2023 if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr && kind != '' {
2024 kinds << kind
2025 }
2026 if !ignore_rhs { // ignore rhs for unary ops and SQL NULL equality
2027 g.sql_side = .right
2028 g.write_orm_where_expr(expr.right, mut fields, mut parentheses, mut kinds, mut
2029 data, mut is_and)
2030 }
2031 }
2032 ast.ParExpr {
2033 mut par := [fields.len]
2034 g.write_orm_where_expr(expr.expr, mut fields, mut parentheses, mut kinds, mut data, mut
2035 is_and)
2036 par << fields.len - 1
2037 parentheses << par
2038 }
2039 ast.Ident {
2040 if g.sql_side == .left {
2041 field := g.get_orm_current_table_field(expr.name) or {
2042 verror('field "${expr.name}" does not exist on "${g.sql_table_name}"')
2043 }
2044 fields << g.get_orm_column_name_from_struct_field(field)
2045 } else {
2046 data << expr
2047 }
2048 }
2049 ast.StringLiteral {
2050 data << expr
2051 }
2052 ast.StringInterLiteral {
2053 data << expr
2054 }
2055 ast.IntegerLiteral {
2056 data << expr
2057 }
2058 ast.SelectorExpr {
2059 if g.comptime.is_comptime_selector_field_name(expr, 'name') {
2060 fields << g.comptime.comptime_for_field_value.name
2061 } else {
2062 data << expr
2063 }
2064 }
2065 ast.BoolLiteral {
2066 data << expr
2067 }
2068 ast.EnumVal {
2069 data << expr
2070 }
2071 ast.CallExpr {
2072 data << expr
2073 }
2074 ast.None {
2075 data << expr
2076 }
2077 else {}
2078 }
2079}
2080
2081// write_orm_select writes C code that calls ORM functions for selecting rows,
2082// storing the result in the provided variable name
2083fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, result_var string) {
2084 mut fields := []ast.StructField{}
2085 mut primary_field := g.get_orm_struct_primary_field(node.fields) or { ast.StructField{} }
2086 mut dynamic_where_var := ''
2087
2088 for field in node.fields {
2089 mut skip := false
2090 for attr in field.attrs {
2091 if attr.name == 'skip' {
2092 skip = true
2093 }
2094 if attr.name == 'sql' && attr.arg == '-' {
2095 skip = true
2096 }
2097 }
2098 if !skip {
2099 fields << field
2100 }
2101 }
2102
2103 select_result_var_name := g.new_tmp_var()
2104 table_name := g.get_table_name_by_struct_type(node.table_expr.typ)
2105 g.sql_table_name = g.table.sym(node.table_expr.typ).name
2106 g.sql_table_typ = node.table_expr.typ
2107 if node.has_where && node.is_dynamic {
2108 dynamic_where_var = g.emit_dynamic_sql_query_data(node.where_expr)
2109 }
2110
2111 g.writeln('// sql { select from `${table_name}` }')
2112 g.writeln('${result_name}_Array_Array_orm__Primitive ${select_result_var_name} = orm__Connection_name_table[${connection_var_name}._typ]._method_select(')
2113 g.indent++
2114 g.writeln('${connection_var_name}._object, // Connection object')
2115 g.writeln('(orm__SelectConfig){')
2116 g.indent++
2117 g.writeln('.table = ')
2118 g.write_orm_table_struct(node.table_expr.typ)
2119 g.writeln(',')
2120 g.writeln('.aggregate_kind = orm__AggregateKind__${node.aggregate_kind},')
2121 g.writeln('.aggregate_field = _S("${node.aggregate_field}"),')
2122 g.writeln('.has_where = ${node.has_where},')
2123 g.writeln('.has_order = ${node.has_order},')
2124
2125 if node.has_order {
2126 g.write('.order = _S("')
2127 if node.order_expr is ast.Ident {
2128 field := g.get_orm_current_table_field(node.order_expr.name) or {
2129 verror('field "${node.order_expr.name}" does not exist on "${g.sql_table_name}"')
2130 }
2131 g.write(g.get_orm_column_name_from_struct_field(field))
2132 } else {
2133 g.expr(node.order_expr)
2134 }
2135 g.writeln('"),')
2136 if node.has_desc {
2137 g.writeln('.order_type = orm__OrderType__desc,')
2138 } else {
2139 g.writeln('.order_type = orm__OrderType__asc,')
2140 }
2141 }
2142
2143 g.writeln('.has_limit = ${node.has_limit},')
2144 g.writeln('.has_offset = ${node.has_offset},')
2145 g.writeln('.has_distinct = ${node.has_distinct},')
2146
2147 if primary_field.name != '' {
2148 g.writeln('.primary = _S("${primary_field.name}"),')
2149 }
2150
2151 mut select_fields := g.orm_non_array_fields(fields)
2152 if node.aggregate_kind != .none {
2153 select_fields = fields.clone()
2154 }
2155 g.writeln('.fields = builtin__new_array_from_c_array(${select_fields.len}, ${select_fields.len}, sizeof(string),')
2156 g.indent++
2157 mut types := []string{}
2158 mut select_exprs := []string{}
2159 if select_fields.len > 0 {
2160 g.writeln('_MOV((string[${select_fields.len}]){')
2161 g.indent++
2162 for field in select_fields {
2163 column_name := g.get_orm_column_name_from_struct_field(field)
2164 g.writeln('_S("${column_name}"),')
2165 select_exprs << g.get_orm_select_expr_from_struct_field(field)
2166 mut final_field_typ := g.table.final_type(field.typ.clear_flag(.option))
2167 if node.aggregate_kind == .avg {
2168 final_field_typ = ast.f64_type
2169 } else if node.aggregate_kind == .count {
2170 final_field_typ = ast.int_type
2171 }
2172 sym := g.table.sym(final_field_typ)
2173 if sym.name == 'time.Time' {
2174 types << '_const_orm__time_'
2175 continue
2176 }
2177 if sym.kind == .struct {
2178 types << int(ast.int_type).str()
2179 continue
2180 } else if sym.kind == .enum {
2181 types << '_const_orm__enum_'
2182 continue
2183 }
2184 types << final_field_typ.idx().str()
2185 }
2186 g.indent--
2187 g.writeln('})')
2188 } else {
2189 g.writeln('NULL')
2190 }
2191 g.indent--
2192 g.writeln2('),',
2193 '.select_exprs = builtin__new_array_from_c_array(${select_exprs.len}, ${select_exprs.len}, sizeof(string),')
2194 g.indent++
2195
2196 if select_exprs.len > 0 {
2197 g.writeln('_MOV((string[${select_exprs.len}]){')
2198 g.indent++
2199 for select_expr in select_exprs {
2200 expr1 := util.smart_quote(select_expr, false)
2201 expr := cescape_nonascii(expr1)
2202 g.writeln('_S("${expr}"),')
2203 }
2204 g.indent--
2205 g.writeln('})')
2206 } else {
2207 g.writeln('NULL')
2208 }
2209 g.indent--
2210 g.writeln2('),',
2211 '.types = builtin__new_array_from_c_array(${types.len}, ${types.len}, sizeof(${ast.int_type_name}),')
2212 g.indent++
2213
2214 if types.len > 0 {
2215 g.write('_MOV((${ast.int_type_name}[${types.len}]){')
2216 for typ in types {
2217 g.write(' ${typ},')
2218 }
2219 g.writeln(' })')
2220 } else {
2221 g.writeln('NULL')
2222 }
2223 g.indent--
2224 g.writeln('),')
2225 // Generate JOIN clauses array
2226 g.write_orm_joins(node.joins)
2227 g.indent--
2228 g.writeln('},')
2229
2230 mut exprs := []ast.Expr{}
2231 if node.has_limit {
2232 exprs << node.limit_expr
2233 }
2234 if node.has_offset {
2235 exprs << node.offset_expr
2236 }
2237
2238 g.writeln('(orm__QueryData) {')
2239 g.indent++
2240 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
2241 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
2242 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
2243 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
2244 g.writeln('.fields = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
2245 if exprs.len > 0 {
2246 g.write('.data = builtin__new_array_from_c_array(${exprs.len}, ${exprs.len}, sizeof(orm__Primitive),')
2247 g.write(' _MOV((orm__Primitive[${exprs.len}]){')
2248 for e in exprs {
2249 g.write_orm_expr_to_primitive(e)
2250 }
2251 g.writeln('})')
2252 } else {
2253 g.writeln('.data = builtin____new_array_with_default_noscan(${exprs.len}, ${exprs.len}, sizeof(orm__Primitive), 0')
2254 }
2255 g.indent--
2256 g.writeln(')},')
2257
2258 if node.has_where {
2259 if node.is_dynamic {
2260 g.writeln('${dynamic_where_var}')
2261 } else {
2262 g.write_orm_where(node.where_expr)
2263 }
2264 } else {
2265 g.writeln('(orm__QueryData) {')
2266 g.indent++
2267 g.writeln('.types = builtin____new_array_with_default_noscan(0, 0, sizeof(${ast.int_type_name}), 0),')
2268 g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
2269 g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
2270 g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
2271 g.writeln('.fields = builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
2272 g.writeln('.data = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0)')
2273 g.indent--
2274 g.writeln('}')
2275 }
2276 g.indent--
2277 g.writeln(');')
2278
2279 g.writeln('${result_var}.is_error = ${select_result_var_name}.is_error;')
2280 g.writeln('${result_var}.err = ${select_result_var_name}.err;')
2281 g.or_block(result_var, node.or_expr, node.typ)
2282
2283 // or_block could have ended in return (longjump) or could have
2284 // yielded another value, so we must test for on non-error result
2285 g.writeln('if (!${result_var}.is_error) {')
2286 g.indent++
2287
2288 unwrapped_c_typ := g.styp(node.typ.clear_flag(.result))
2289 select_unwrapped_result_var_name := g.new_tmp_var()
2290
2291 g.writeln('Array_Array_orm__Primitive ${select_unwrapped_result_var_name} = (*(Array_Array_orm__Primitive*)${select_result_var_name}.data);')
2292
2293 if node.aggregate_kind != .none {
2294 prim_var := g.new_tmp_var()
2295 aggregate_type := if node.aggregate_kind == .avg {
2296 ast.f64_type
2297 } else {
2298 node.aggregate_field_type
2299 }
2300 primitive_field_name := g.orm_primitive_variant_field_name(aggregate_type)
2301 aggregate_value_styp := g.styp(aggregate_type.clear_flag(.option))
2302 if node.aggregate_kind == .count {
2303 g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = 0;')
2304 g.writeln('if (${select_unwrapped_result_var_name}.len > 0 && (*(Array_orm__Primitive*) builtin__array_get(${select_unwrapped_result_var_name}, 0)).len > 0) {')
2305 g.indent++
2306 g.writeln('orm__Primitive *${prim_var} = &(*(orm__Primitive*) builtin__array_get((*(Array_orm__Primitive*) builtin__array_get(${select_unwrapped_result_var_name}, 0)), 0));')
2307 g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = *(${prim_var}->_${primitive_field_name});')
2308 g.indent--
2309 g.writeln('}')
2310 } else {
2311 aggregate_result_var := g.new_tmp_var()
2312 g.writeln('${unwrapped_c_typ} ${aggregate_result_var} = (${unwrapped_c_typ}){ .state = 2, .err = _const_none__, .data = {E_STRUCT} };')
2313 g.writeln('if (${select_unwrapped_result_var_name}.len > 0 && (*(Array_orm__Primitive*) builtin__array_get(${select_unwrapped_result_var_name}, 0)).len > 0) {')
2314 g.indent++
2315 g.writeln('orm__Primitive *${prim_var} = &(*(orm__Primitive*) builtin__array_get((*(Array_orm__Primitive*) builtin__array_get(${select_unwrapped_result_var_name}, 0)), 0));')
2316 g.writeln('if (${prim_var}->_typ != ${g.table.find_type_idx('orm.Null')}) {')
2317 g.indent++
2318 g.writeln('builtin___option_ok(${prim_var}->_${primitive_field_name}, (_option *)&${aggregate_result_var}, sizeof(${aggregate_value_styp}));')
2319 g.indent--
2320 g.writeln('}')
2321 g.indent--
2322 g.writeln('}')
2323 g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = ${aggregate_result_var};')
2324 }
2325 } else {
2326 tmp := g.new_tmp_var()
2327 idx := g.new_tmp_var()
2328 g.writeln('${ast.int_type_name} ${idx} = 0;')
2329 mut typ_str := ''
2330 if node.is_array {
2331 info := g.table.sym(node.typ).array_info()
2332 typ_str = g.styp(info.elem_type)
2333 base_typ := g.base_type(node.typ)
2334 if node.typ.has_flag(.option) {
2335 g.writeln('${unwrapped_c_typ} ${tmp}_array = { .state = 2, .err = _const_none__, .data = {E_STRUCT} };')
2336 g.writeln('builtin___option_ok(&(${base_typ}[]) { builtin____new_array(0, ${select_unwrapped_result_var_name}.len, sizeof(${typ_str})) }, (_option *)&${tmp}_array, sizeof(${base_typ}));')
2337 } else {
2338 g.writeln('${unwrapped_c_typ} ${tmp}_array = builtin____new_array(0, ${select_unwrapped_result_var_name}.len, sizeof(${typ_str}));')
2339 }
2340 g.writeln('for (; ${idx} < ${select_unwrapped_result_var_name}.len; ${idx}++) {')
2341 g.indent++
2342 g.write('${typ_str} ${tmp} = (${typ_str}) {')
2343 inf := g.table.sym(info.elem_type).struct_info()
2344 for i, field in inf.fields {
2345 g.zero_struct_field(field)
2346 if i != inf.fields.len - 1 {
2347 g.write(', ')
2348 }
2349 }
2350 g.writeln('};')
2351 } else {
2352 g.write('${unwrapped_c_typ} ${tmp} = (${unwrapped_c_typ}){')
2353 info := g.table.sym(node.typ).struct_info()
2354 for i, field in info.fields {
2355 g.zero_struct_field(field)
2356 if i != info.fields.len - 1 {
2357 g.write(', ')
2358 }
2359 }
2360 g.writeln('};')
2361 }
2362
2363 g.writeln('if (${select_unwrapped_result_var_name}.len > 0) {')
2364 g.indent++
2365
2366 mut fields_idx := 0
2367 for field in fields {
2368 array_get_call_code := '(*(orm__Primitive*) builtin__array_get((*(Array_orm__Primitive*) builtin__array_get(${select_unwrapped_result_var_name}, ${idx})), ${fields_idx}))'
2369 final_field_typ := g.table.final_type(field.typ)
2370 sym := g.table.sym(final_field_typ)
2371 field_var := '${tmp}.${orm_field_access_name(field.name)}'
2372 field_c_typ := g.styp(final_field_typ)
2373 if sym.kind == .struct && sym.name != 'time.Time' {
2374 mut sub := node.sub_structs[field.name] or { continue }
2375 mut where_expr := sub.where_expr as ast.InfixExpr
2376 mut ident := where_expr.right as ast.Ident
2377 primitive_type_index := g.table.find_type('orm.Primitive')
2378
2379 if primitive_type_index != 0 {
2380 if mut ident.info is ast.IdentVar {
2381 ident.info.typ = primitive_type_index
2382 }
2383 }
2384
2385 ident.name = array_get_call_code
2386 ident.obj = ast.Var{
2387 name: array_get_call_code
2388 }
2389 where_expr.right = ident
2390 sub.where_expr = where_expr
2391
2392 sub_result_var := g.new_tmp_var()
2393 sub_result_c_typ := g.styp(sub.typ)
2394 g.writeln('${sub_result_c_typ} ${sub_result_var};')
2395 g.write_orm_select(sub, connection_var_name, sub_result_var)
2396 if final_field_typ.has_flag(.option) {
2397 unwrapped_field_c_typ := g.styp(final_field_typ.clear_flag(.option))
2398 g.writeln('if (!${sub_result_var}.is_error)')
2399 g.writeln('\tbuiltin___option_ok(${sub_result_var}.data, (_option *)&${field_var}, sizeof(${unwrapped_field_c_typ}));')
2400 g.writeln('else')
2401 g.writeln('\t${field_var} = (${field_c_typ}){ .state = 2, .err = _const_none__, .data = {E_STRUCT} };')
2402 } else {
2403 g.writeln('if (!${sub_result_var}.is_error)')
2404 g.writeln('\t${field_var} = *(${field_c_typ}*)${sub_result_var}.data;')
2405 }
2406 fields_idx++
2407 } else if sym.kind == .array {
2408 mut fkey := ''
2409 if attr := field.attrs.find_first('fkey') {
2410 fkey = attr.arg
2411 } else {
2412 verror('missing fkey attribute')
2413 }
2414 sub := node.sub_structs[field.name] or { continue }
2415 if sub.has_where {
2416 mut where_expr := sub.where_expr as ast.InfixExpr
2417 mut left_where_expr := where_expr.left as ast.Ident
2418 mut right_where_expr := where_expr.right as ast.Ident
2419 left_where_expr.name = fkey
2420 right_where_expr.name = tmp
2421 right_where_expr.obj = ast.Var{
2422 name: tmp
2423 }
2424 where_expr.left = left_where_expr
2425 where_expr.right = ast.SelectorExpr{
2426 pos: right_where_expr.pos
2427 field_name: primary_field.name
2428 is_mut: false
2429 expr: right_where_expr
2430 expr_type: (right_where_expr.info as ast.IdentVar).typ
2431 typ: (right_where_expr.info as ast.IdentVar).typ
2432 scope: unsafe { nil }
2433 }
2434
2435 mut sql_expr_select_array := ast.SqlExpr{
2436 typ: final_field_typ.set_flag(.result)
2437 aggregate_kind: sub.aggregate_kind
2438 aggregate_field: sub.aggregate_field
2439 db_expr: sub.db_expr
2440 has_where: sub.has_where
2441 has_offset: sub.has_offset
2442 offset_expr: sub.offset_expr
2443 has_order: sub.has_order
2444 order_expr: sub.order_expr
2445 has_desc: sub.has_desc
2446 is_array: true
2447 is_generated: true
2448 pos: sub.pos
2449 has_limit: sub.has_limit
2450 limit_expr: sub.limit_expr
2451 table_expr: sub.table_expr
2452 fields: sub.fields
2453 sub_structs: sub.sub_structs
2454 where_expr: where_expr
2455 aggregate_field_type: sub.aggregate_field_type
2456 }
2457
2458 sub_result_var := g.new_tmp_var()
2459 sub_result_c_typ := g.styp(sub.typ)
2460 g.writeln('${sub_result_c_typ} ${sub_result_var};')
2461 g.write_orm_select(sql_expr_select_array, connection_var_name, sub_result_var)
2462 g.writeln('if (!${sub_result_var}.is_error) {')
2463 if final_field_typ.has_flag(.option) {
2464 g.writeln('\t${field_var}.state = 0;')
2465 g.writeln('\t*(${g.base_type(final_field_typ)}*)${field_var}.data = *(${g.base_type(final_field_typ)}*)${sub_result_var}.data;')
2466 } else {
2467 g.writeln('\t${field_var} = *(${g.base_type(field.typ)}*)${sub_result_var}.data;')
2468 }
2469 g.writeln('}')
2470 }
2471 } else if final_field_typ.has_flag(.option) {
2472 prim_var := g.new_tmp_var()
2473 g.writeln('orm__Primitive *${prim_var} = &${array_get_call_code};')
2474 g.writeln('if (${prim_var}->_typ == ${g.table.find_type_idx('orm.Null')})')
2475 g.writeln('\t${field_var} = (${field_c_typ}){ .state = 2, .err = _const_none__, .data = {E_STRUCT} };')
2476
2477 g.writeln('else')
2478 g.writeln('\tbuiltin___option_ok(${prim_var}->_${sym.cname}, (_option *)&${field_var}, sizeof(${sym.cname}));')
2479 fields_idx++
2480 } else if sym.kind == .enum {
2481 mut typ := sym.cname
2482 g.writeln('${tmp}.${orm_field_access_name(field.name)} = (${typ}) (*(${array_get_call_code}._i64));')
2483 fields_idx++
2484 } else {
2485 g.writeln('${field_var} = *(${array_get_call_code}._${sym.cname});')
2486 fields_idx++
2487 }
2488 }
2489
2490 if node.is_array {
2491 if node.typ.has_flag(.option) {
2492 g.writeln('${tmp}_array.state = 0;')
2493 g.writeln('builtin__array_push((${g.base_type(node.typ)}*)&${tmp}_array.data, _MOV((${typ_str}[]){ ${tmp} }));')
2494 } else {
2495 g.writeln('builtin__array_push(&${tmp}_array, _MOV((${typ_str}[]){ ${tmp} }));')
2496 }
2497 g.indent--
2498 g.writeln('}')
2499 }
2500
2501 g.indent--
2502 if !node.is_array {
2503 g.writeln('} else {')
2504 g.writeln('\t${result_var}.is_error = true;')
2505 }
2506 g.writeln('}')
2507
2508 if node.is_array {
2509 if node.typ.has_flag(.option) {
2510 g.writeln('*(${g.base_type(node.typ)}*) ${result_var}.data = *(${g.base_type(node.typ)}*)${tmp}_array.data;')
2511 } else {
2512 g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp}_array;')
2513 }
2514 } else {
2515 g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp};')
2516 }
2517 }
2518
2519 g.indent--
2520 g.writeln('}')
2521
2522 if node.is_generated {
2523 g.writeln(';')
2524 }
2525}
2526
2527// filter_struct_fields_by_orm_attrs filters struct fields taking into its attributes.
2528// Used by non-create queries for skipping fields.
2529fn (_ &Gen) filter_struct_fields_by_orm_attrs(fields []ast.StructField) []ast.StructField {
2530 mut ret := []ast.StructField{}
2531
2532 for field in fields {
2533 if field.attrs.contains('skip') || field.attrs.contains_arg('sql', '-') {
2534 continue
2535 }
2536 ret << field
2537 }
2538
2539 return ret
2540}
2541
2542// get_db_expr_type returns the database type from the database expression.
2543fn (g &Gen) get_db_expr_type(expr ast.Expr) ?ast.Type {
2544 if expr is ast.Ident {
2545 if expr.info is ast.IdentVar {
2546 return g.table.unaliased_type(expr.info.typ)
2547 }
2548 } else if expr is ast.SelectorExpr {
2549 return g.table.unaliased_type(expr.typ)
2550 }
2551
2552 return none
2553}
2554
2555// get_table_attrs_by_struct_type returns the struct attrs.
2556fn (g &Gen) get_table_attrs_by_struct_type(typ ast.Type) []ast.Attr {
2557 sym := g.table.sym(typ)
2558 info := sym.struct_info()
2559 return info.attrs
2560}
2561
2562// orm_table_field_names recursively collects field names from a struct type,
2563// including prefixed fields from embedded structs. Used to populate Table.fields
2564// for scope filter validation in apply_data_scope.
2565fn (g &Gen) orm_table_field_names(typ ast.Type) []string {
2566 return g.orm_table_field_names_with_prefix(typ, '')
2567}
2568
2569fn (g &Gen) orm_table_field_names_with_prefix(typ ast.Type, prefix string) []string {
2570 sym := g.table.sym(typ)
2571 info := sym.struct_info()
2572 mut names := []string{}
2573 for field in info.fields {
2574 if field.is_embed {
2575 embed_sym := g.table.sym(field.typ)
2576 if embed_sym.info is ast.Struct {
2577 embed_prefix := prefixed_orm_field_name(prefix, embed_sym.embed_name())
2578 names << g.orm_table_field_names_with_prefix(field.typ, embed_prefix)
2579 }
2580 } else {
2581 // Skip fields with @[skip] or @[sql:'-'] as they have no database column
2582 if field.attrs.contains('skip') || field.attrs.contains_arg('sql', '-') {
2583 continue
2584 }
2585 names << prefixed_orm_field_name(prefix, field.name)
2586 }
2587 }
2588 return names
2589}
2590
2591// orm_table_column_names recursively collects SQL column names from a struct type,
2592// including columns from embedded structs. Used to populate Table.columns for
2593// SQL column name resolution in apply_data_scope.
2594fn (g &Gen) orm_table_column_names(typ ast.Type) []string {
2595 sym := g.table.sym(typ)
2596 info := sym.struct_info()
2597 mut names := []string{}
2598 for field in info.fields {
2599 if field.is_embed {
2600 embed_sym := g.table.sym(field.typ)
2601 if embed_sym.info is ast.Struct {
2602 names << g.orm_table_column_names(field.typ)
2603 }
2604 } else {
2605 // Skip fields with @[skip] or @[sql:'-'] as they have no database column
2606 if field.attrs.contains('skip') || field.attrs.contains_arg('sql', '-') {
2607 continue
2608 }
2609 names << g.get_orm_column_name_from_struct_field(field)
2610 }
2611 }
2612 return names
2613}
2614
2615fn prefixed_orm_field_name(prefix string, name string) string {
2616 if prefix == '' {
2617 return name
2618 }
2619 return '${prefix}.${name}'
2620}
2621
2622// get_table_name_by_struct_type converts the struct type to a table name.
2623// For generic types, uses ngname (name without generic params) to get the base table name.
2624fn (g &Gen) get_table_name_by_struct_type(typ ast.Type) string {
2625 sym := g.table.sym(typ)
2626 info := sym.struct_info()
2627 // Use ngname for generic types to strip the generic parameters (e.g., Message[Payload] -> Message)
2628 // Fall back to stripping manually from name if ngname is empty
2629 base_name := if sym.ngname.len > 0 {
2630 sym.ngname
2631 } else {
2632 // Strip generic parameters manually (e.g., main.Message[main.Payload] -> main.Message)
2633 sym.name.all_before('[')
2634 }
2635 mut table_name := util.strip_mod_name(base_name)
2636
2637 if attr := info.attrs.find_first('table') {
2638 table_name = attr.arg
2639 } else {
2640 // Keep default ORM table names aligned with unquoted SQL identifiers across DB drivers.
2641 table_name = table_name.to_lower()
2642 }
2643 escaped_table_name := cescape_nonascii(util.smart_quote(table_name, false))
2644 return escaped_table_name
2645}
2646
2647// get_orm_current_table_field returns the current processing table's struct field by name.
2648fn (g &Gen) get_orm_current_table_field(name string) ?ast.StructField {
2649 // Use sql_table_typ directly for proper generic type support
2650 sym := g.table.sym(g.sql_table_typ)
2651 // For GenericInst types, get the struct info from the parent type
2652 info := if sym.info is ast.GenericInst {
2653 g.table.type_symbols[sym.info.parent_idx].struct_info()
2654 } else {
2655 sym.struct_info()
2656 }
2657
2658 for field in info.fields {
2659 if field.name == name {
2660 return field
2661 }
2662 }
2663
2664 return none
2665}
2666
2667// get_orm_column_name_from_struct_field converts the struct field to a table column name.
2668fn (g &Gen) get_orm_column_name_from_struct_field(field ast.StructField) string {
2669 mut name := field.name
2670
2671 if attr := field.attrs.find_first('sql') {
2672 if attr.arg !in ['serial', 'i8', 'i16', 'i32', 'int', 'i64', 'u8', 'u16', 'u32', 'u64',
2673 'f32', 'f64', 'bool', 'string'] {
2674 name = attr.arg
2675 }
2676 }
2677
2678 final_field_typ := g.table.final_type(field.typ)
2679 sym := g.table.sym(final_field_typ)
2680 if sym.kind == .struct && sym.name != 'time.Time' {
2681 name = '${name}_id'
2682 }
2683
2684 return name
2685}
2686
2687fn (g &Gen) get_orm_field_by_column_name(fields []ast.StructField, column string) ?ast.StructField {
2688 for field in fields {
2689 if g.get_orm_column_name_from_struct_field(field) == column {
2690 return field
2691 }
2692 }
2693 return none
2694}
2695
2696// get_orm_select_expr_from_struct_field returns the SQL expression used in a SELECT list.
2697fn (g &Gen) get_orm_select_expr_from_struct_field(field ast.StructField) string {
2698 if attr := field.attrs.find_first('sql_select') {
2699 arg := attr.arg.trim_space()
2700 if arg.len >= 2 && ((arg.starts_with("'") && arg.ends_with("'"))
2701 || (arg.starts_with('"') && arg.ends_with('"'))) {
2702 return arg[1..arg.len - 1].trim_space()
2703 }
2704 return arg
2705 }
2706 return g.get_orm_column_name_from_struct_field(field)
2707}
2708
2709// get_orm_struct_primary_field returns the table's primary column field.
2710fn (_ &Gen) get_orm_struct_primary_field(fields []ast.StructField) ?ast.StructField {
2711 for field in fields {
2712 if _ := field.attrs.find_first('primary') {
2713 return field
2714 }
2715 }
2716 return none
2717}
2718
2719fn (g &Gen) get_orm_upsert_conflict_groups(fields []ast.StructField, table_attrs []ast.Attr) [][]string {
2720 mut groups := [][]string{}
2721 mut named_unique_groups := map[string][]string{}
2722 mut named_unique_group_order := []string{}
2723 mut seen := map[string]bool{}
2724 for field in fields {
2725 column_name := g.get_orm_column_name_from_struct_field(field)
2726 for attr in field.attrs {
2727 match attr.name {
2728 'primary' {
2729 key := column_name
2730 if key !in seen {
2731 groups << [column_name]
2732 seen[key] = true
2733 }
2734 }
2735 'unique' {
2736 if attr.arg != '' && attr.kind == .string {
2737 if attr.arg !in named_unique_groups {
2738 named_unique_groups[attr.arg] = []string{}
2739 named_unique_group_order << attr.arg
2740 }
2741 named_unique_groups[attr.arg] << column_name
2742 } else {
2743 key := column_name
2744 if key !in seen {
2745 groups << [column_name]
2746 seen[key] = true
2747 }
2748 }
2749 }
2750 else {}
2751 }
2752 }
2753 }
2754 for group_name in named_unique_group_order {
2755 group := named_unique_groups[group_name]
2756 key := group.join(',')
2757 if key !in seen {
2758 groups << group
2759 seen[key] = true
2760 }
2761 }
2762 for attr in table_attrs {
2763 if attr.name != 'unique_key' || attr.arg == '' || attr.kind != .string {
2764 continue
2765 }
2766 mut group := []string{}
2767 for raw_field_name in attr.arg.split(',') {
2768 field_name := raw_field_name.trim_space()
2769 if field_name != '' {
2770 group << field_name
2771 }
2772 }
2773 key := group.join(',')
2774 if group.len > 0 && key !in seen {
2775 groups << group
2776 seen[key] = true
2777 }
2778 }
2779 return groups
2780}
2781
2782fn (mut g Gen) write_orm_upsert_conflict_groups(groups [][]string) {
2783 if groups.len == 0 {
2784 g.write('builtin____new_array_with_default_noscan(0, 0, sizeof(Array_string), 0)')
2785 return
2786 }
2787 g.write('builtin__new_array_from_c_array(${groups.len}, ${groups.len}, sizeof(Array_string), _MOV((Array_string[${groups.len}]){')
2788 for group in groups {
2789 if group.len == 0 {
2790 g.write('builtin____new_array_with_default_noscan(0, 0, sizeof(string), 0),')
2791 continue
2792 }
2793 g.write('builtin__new_array_from_c_array(${group.len}, ${group.len}, sizeof(string), _MOV((string[${group.len}]){')
2794 for field_name in group {
2795 g.write('_S("${field_name}"),')
2796 }
2797 g.write('})),')
2798 }
2799 g.write('}))') // closing: `}` compound literal init, `)` _MOV, `)` new_array_from_c_array
2800}
2801
2802// return indexes of any auto-increment fields or fields with default values
2803fn get_auto_field_idxs(fields []ast.StructField) []int {
2804 mut ret := []int{}
2805 for i, field in fields {
2806 for attr in field.attrs {
2807 if attr.name == 'default' {
2808 ret << i
2809 } else if attr.name == 'sql' && attr.arg == 'serial' {
2810 ret << i
2811 } else if attr.name == 'serial' && attr.kind == .plain && !attr.has_arg {
2812 ret << i
2813 }
2814 }
2815 }
2816 return ret
2817}
2818
2819// write_orm_joins writes C code for the joins array in SelectConfig
2820fn (mut g Gen) write_orm_joins(joins []ast.JoinClause) {
2821 if joins.len == 0 {
2822 g.writeln('.joins = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__JoinConfig), 0),')
2823 return
2824 }
2825
2826 g.writeln('.joins = builtin__new_array_from_c_array(${joins.len}, ${joins.len}, sizeof(orm__JoinConfig),')
2827 g.indent++
2828 g.writeln('_MOV((orm__JoinConfig[${joins.len}]){')
2829 g.indent++
2830
2831 for join in joins {
2832 g.writeln('(orm__JoinConfig){')
2833 g.indent++
2834
2835 // Write join kind
2836 kind_str := match join.kind {
2837 .inner { 'orm__JoinType__inner' }
2838 .left { 'orm__JoinType__left' }
2839 .right { 'orm__JoinType__right' }
2840 .full_outer { 'orm__JoinType__full_outer' }
2841 }
2842
2843 g.writeln('.kind = ${kind_str},')
2844
2845 // Write joined table info
2846 g.write('.table = ')
2847 g.write_orm_table_struct(join.table_expr.typ)
2848 g.writeln(',')
2849
2850 // Extract column names from the ON expression (should be an InfixExpr)
2851 left_col, right_col := g.extract_join_columns(join.on_expr)
2852 g.writeln('.on_left_col = _S("${left_col}"),')
2853 g.writeln('.on_right_col = _S("${right_col}"),')
2854
2855 g.indent--
2856 g.writeln('},')
2857 }
2858
2859 g.indent--
2860 g.writeln('})')
2861 g.indent--
2862 g.writeln('),')
2863}
2864
2865// extract_join_columns extracts the left and right column names from a JOIN ON expression.
2866// The ON expression is expected to be an InfixExpr like: User.dept_id == Department.id
2867// Returns (left_col, right_col) where left_col is from the main table, right_col is from the joined table.
2868fn (g &Gen) extract_join_columns(on_expr ast.Expr) (string, string) {
2869 if on_expr is ast.InfixExpr {
2870 left_col := g.extract_join_field_name(on_expr.left)
2871 right_col := g.extract_join_field_name(on_expr.right)
2872 return left_col, right_col
2873 }
2874
2875 // Fallback: return empty strings if the expression is not the expected format
2876 return '', ''
2877}
2878
2879// extract_join_field_name extracts a field name from a JOIN ON expression operand.
2880// Handles both SelectorExpr (Table.field) and EnumVal (when parser interprets Type.field as enum).
2881fn (g &Gen) extract_join_field_name(expr ast.Expr) string {
2882 if expr is ast.SelectorExpr {
2883 return expr.field_name
2884 }
2885 if expr is ast.EnumVal {
2886 // EnumVal.val contains the field name (e.g., "department_id" from "User.department_id")
2887 return expr.val
2888 }
2889 return ''
2890}
2891