v4 / vlib / orm / orm.v
1840 lines · 1690 sloc · 43.01 KB · f581cbb1c964f89133439f4e303136998ddcfcfb
Raw
1@[has_globals]
2module orm
3
4import time
5
6const default_tenant_filter_field_name = 'tenant_id'
7const tenant_filter_attr_name = 'tenant_filter'
8const tenant_field_attr_name = 'tenant_field'
9const ignore_tenant_filter_attr_name = 'ignore_tenant_filter'
10
11pub const num64 = [typeof[i64]().idx, typeof[u64]().idx]
12pub const nums = [
13 typeof[i8]().idx,
14 typeof[i16]().idx,
15 typeof[int]().idx,
16 typeof[u8]().idx,
17 typeof[u16]().idx,
18 typeof[u32]().idx,
19 typeof[bool]().idx,
20]
21pub const float = [
22 typeof[f32]().idx,
23 typeof[f64]().idx,
24]
25pub const type_string = typeof[string]().idx
26pub const serial = -1
27pub const time_ = -2
28pub const enum_ = -3
29pub const type_idx = {
30 'i8': typeof[i8]().idx
31 'i16': typeof[i16]().idx
32 'int': typeof[int]().idx
33 'i64': typeof[i64]().idx
34 'u8': typeof[u8]().idx
35 'u16': typeof[u16]().idx
36 'u32': typeof[u32]().idx
37 'u64': typeof[u64]().idx
38 'f32': typeof[f32]().idx
39 'f64': typeof[f64]().idx
40 'bool': typeof[bool]().idx
41 'string': typeof[string]().idx
42}
43pub const string_max_len = 2048
44pub const null_primitive = Primitive(Null{})
45
46pub type Primitive = Null
47 | bool
48 | f32
49 | f64
50 | i16
51 | i64
52 | i8
53 | int
54 | string
55 | time.Time
56 | u16
57 | u32
58 | u64
59 | u8
60 | InfixType
61 | []bool
62 | []f32
63 | []f64
64 | []i16
65 | []i64
66 | []i8
67 | []int
68 | []string
69 | []time.Time
70 | []u16
71 | []u32
72 | []u64
73 | []u8
74 | []InfixType
75 | []Primitive
76
77pub struct Null {}
78
79// Row represents a single result row from a raw SQL exec query.
80pub struct Row {
81pub mut:
82 vals []string
83 names []string // column names; populated by drivers that provide them
84}
85
86pub enum OperationKind {
87 neq // !=
88 eq // ==
89 gt // >
90 lt // <
91 ge // >=
92 le // <=
93 orm_like // LIKE
94 orm_ilike // ILIKE
95 is_null // IS NULL
96 is_not_null // IS NOT NULL
97 in // IN
98 not_in // NOT IN
99}
100
101pub enum MathOperationKind {
102 add // +
103 sub // -
104 mul // *
105 div // /
106}
107
108pub enum StmtKind {
109 insert
110 update
111 delete
112}
113
114pub enum OrderType {
115 asc
116 desc
117}
118
119pub enum AggregateKind {
120 none
121 count
122 sum
123 avg
124 min
125 max
126}
127
128// JoinType represents the type of SQL JOIN operation
129pub enum JoinType {
130 inner // INNER JOIN - returns only matching rows
131 left // LEFT JOIN - returns all left rows, NULL for non-matching right
132 right // RIGHT JOIN - returns all right rows, NULL for non-matching left
133 full_outer // FULL OUTER JOIN - returns all rows from both tables
134}
135
136fn (jt JoinType) to_str() string {
137 return match jt {
138 .inner { 'INNER JOIN' }
139 .left { 'LEFT JOIN' }
140 .right { 'RIGHT JOIN' }
141 .full_outer { 'FULL OUTER JOIN' }
142 }
143}
144
145// JoinConfig holds configuration for a JOIN clause in a SELECT query
146pub struct JoinConfig {
147pub mut:
148 kind JoinType
149 table Table
150 on_left_col string // Column from main table (e.g., 'user_id')
151 on_right_col string // Column from joined table (e.g., 'id')
152}
153
154pub enum SQLDialect {
155 default
156 h2
157 mysql
158 pg
159 sqlite
160}
161
162fn (kind OperationKind) to_str() string {
163 str := match kind {
164 // While most SQL databases support "!=" for not equal, "<>" is the standard
165 // operator.
166 .neq { '<>' }
167 .eq { '=' }
168 .gt { '>' }
169 .lt { '<' }
170 .ge { '>=' }
171 .le { '<=' }
172 .orm_like { 'LIKE' }
173 .orm_ilike { 'ILIKE' }
174 .is_null { 'IS NULL' }
175 .is_not_null { 'IS NOT NULL' }
176 .in { 'IN' }
177 .not_in { 'NOT IN' }
178 }
179
180 return str
181}
182
183fn (kind OperationKind) is_unary() bool {
184 return kind in [.is_null, .is_not_null]
185}
186
187fn (kind OrderType) to_str() string {
188 return match kind {
189 .desc {
190 'DESC'
191 }
192 .asc {
193 'ASC'
194 }
195 }
196}
197
198fn (kind AggregateKind) to_str() string {
199 return match kind {
200 .none { '' }
201 .count { 'COUNT(*)' }
202 .sum { 'SUM' }
203 .avg { 'AVG' }
204 .min { 'MIN' }
205 .max { 'MAX' }
206 }
207}
208
209// Examples for QueryData in SQL: abc == 3 && b == 'test'
210// => fields[abc, b]; data[3, 'test']; types[index of int, index of string]; kinds[.eq, .eq]; is_and[true];
211// Every field, data, type & kind of operation in the expr share the same index in the arrays
212// is_and defines how they're addicted to each other either and or or
213// parentheses defines which fields will be inside ()
214// auto_fields are indexes of fields where db should generate a value when absent in an insert
215pub struct QueryData {
216pub mut:
217 fields []string
218 data []Primitive
219 types []int
220 parentheses [][]int
221 kinds []OperationKind
222 auto_fields []int
223 is_and []bool
224 batch_rows int
225 batch_key string
226}
227
228pub struct InfixType {
229pub:
230 name string
231 operator MathOperationKind
232 right Primitive
233}
234
235pub struct Table {
236pub mut:
237 name string
238 attrs []VAttribute
239 fields []string // struct field names, used to skip scope filters that don't apply
240 columns []string // SQL column names (parallel to fields), used for SQL generation
241}
242
243// new_table creates a Table with the given name and attributes.
244// Prefer using this constructor over positional initialization,
245// as new fields may be added to Table in future versions.
246pub fn new_table(name string, attrs []VAttribute) Table {
247 return Table{
248 name: name
249 attrs: attrs
250 }
251}
252
253pub struct TableField {
254pub mut:
255 name string
256 typ int
257 nullable bool
258 default_val string
259 attrs []VAttribute
260 is_arr bool
261}
262
263// table - Table struct
264// aggregate_kind - Select rows or return a single aggregate value
265// has_where - Select all or use a where expr
266// has_order - Order the results
267// order - Name of the column which will be ordered
268// order_type - Type of order (asc, desc)
269// has_limit - Limits the output data
270// primary - Name of the primary field
271// has_offset - Add an offset to the result
272// fields - Fields to select
273// types - Types to select
274// joins - JOIN clauses for this query
275pub struct SelectConfig {
276pub mut:
277 table Table
278 aggregate_kind AggregateKind
279 aggregate_field string
280 has_where bool
281 has_order bool
282 order string
283 order_type OrderType
284 has_limit bool
285 primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false
286 has_offset bool
287 has_distinct bool
288 fields []string
289 select_exprs []string
290 types []int
291 joins []JoinConfig // JOIN clauses for this query
292}
293
294struct TenantFilterState {
295mut:
296 enabled bool
297 field_name string
298 has_current_tenant bool
299 current_tenant Primitive
300}
301
302struct TenantFilterScopeState {
303 enabled bool
304 has_current_tenant bool
305 current_tenant Primitive
306}
307
308@[deprecated: 'use `orm.DataScope` and `orm.new_db()` for per-instance request-level filtering']
309@[deprecated_after: '2027-06-08']
310pub struct TenantFilterConfig {
311pub:
312 enabled bool = true
313 field_name string = default_tenant_filter_field_name
314}
315
316__global tenant_filter_state = TenantFilterState{
317 enabled: false
318 field_name: default_tenant_filter_field_name
319 has_current_tenant: false
320 current_tenant: null_primitive
321}
322
323// Interfaces gets called from the backend and can be implemented
324// Since the orm supports arrays aswell, they have to be returned too.
325// A row is represented as []Primitive, where the data is connected to the fields of the struct by their
326// index. The indices are mapped with the SelectConfig.field array. This is the mapping for a struct.
327// To have an array, there has to be an array of structs, basically [][]Primitive
328//
329// Every function without last_id() returns an optional, which returns an error if present
330// last_id returns the last inserted id of the db
331pub interface Connection {
332mut:
333 select(config SelectConfig, data QueryData, where QueryData) ![][]Primitive
334 insert(table Table, data QueryData) !
335 update(table Table, data QueryData, where QueryData) !
336 delete(table Table, where QueryData) !
337 create(table Table, fields []TableField) !
338 drop(table Table) !
339 last_id() int
340 execute(query string) ![]Row
341}
342
343// TransactionalConnection extends Connection with transaction primitives.
344pub interface TransactionalConnection {
345 Connection
346mut:
347 orm_begin() !
348 orm_commit() !
349 orm_rollback() !
350 orm_savepoint(name string) !
351 orm_rollback_to(name string) !
352 orm_release_savepoint(name string) !
353}
354
355// configure_tenant_filter configures the global ORM tenant filter behavior.
356@[deprecated: 'use `orm.DataScope` and `orm.new_db()` for per-instance request-level filtering']
357@[deprecated_after: '2027-06-08']
358pub fn configure_tenant_filter(config TenantFilterConfig) {
359 tenant_filter_state.enabled = config.enabled
360 tenant_filter_state.field_name = normalize_tenant_filter_field_name(config.field_name)
361}
362
363// set_tenant_filter_enabled enables or disables global tenant filtering.
364@[deprecated: 'use `orm.DataScope` and `orm.new_db()` for per-instance request-level filtering']
365@[deprecated_after: '2027-06-08']
366pub fn set_tenant_filter_enabled(enabled bool) {
367 tenant_filter_state.enabled = enabled
368}
369
370// set_current_tenant_id sets the current tenant id used by global tenant filtering.
371@[deprecated: 'use `orm.DataScope` and `orm.new_db()` for per-instance request-level filtering']
372@[deprecated_after: '2027-06-08']
373pub fn set_current_tenant_id(tenant_id Primitive) {
374 if tenant_id is Null {
375 clear_current_tenant_id()
376 return
377 }
378 tenant_filter_state.has_current_tenant = true
379 tenant_filter_state.current_tenant = tenant_id
380}
381
382// clear_current_tenant_id clears the current tenant id used by global tenant filtering.
383@[deprecated: 'use `orm.DataScope` and `orm.new_db()` for per-instance request-level filtering']
384@[deprecated_after: '2027-06-08']
385pub fn clear_current_tenant_id() {
386 tenant_filter_state.has_current_tenant = false
387 tenant_filter_state.current_tenant = null_primitive
388}
389
390// with_tenant executes `callback` with a temporary tenant id and enabled tenant filtering.
391@[deprecated: 'use `orm.DataScope` and `orm.new_db()` for per-instance request-level filtering']
392@[deprecated_after: '2027-06-08']
393pub fn with_tenant[T](tenant_id Primitive, callback fn () !T) !T {
394 saved := tenant_filter_scope_snapshot()
395 tenant_filter_state.enabled = true
396 tenant_filter_state.has_current_tenant = true
397 tenant_filter_state.current_tenant = tenant_id
398 defer {
399 tenant_filter_scope_restore(saved)
400 }
401 return callback()
402}
403
404// with_tenant_value executes `callback` with a temporary tenant id and enabled tenant filtering.
405@[deprecated: 'use `orm.DataScope` and `orm.new_db()` for per-instance request-level filtering']
406@[deprecated_after: '2027-06-08']
407pub fn with_tenant_value[T](tenant_id Primitive, callback fn () T) T {
408 saved := tenant_filter_scope_snapshot()
409 tenant_filter_state.enabled = true
410 tenant_filter_state.has_current_tenant = true
411 tenant_filter_state.current_tenant = tenant_id
412 defer {
413 tenant_filter_scope_restore(saved)
414 }
415 return callback()
416}
417
418// without_tenant_filter executes `callback` with tenant filtering temporarily disabled.
419@[deprecated: 'use `orm.DB.unscoped()` for per-instance scope bypass']
420@[deprecated_after: '2027-06-08']
421pub fn without_tenant_filter[T](callback fn () !T) !T {
422 saved := tenant_filter_scope_snapshot()
423 tenant_filter_state.enabled = false
424 defer {
425 tenant_filter_scope_restore(saved)
426 }
427 return callback()
428}
429
430// without_tenant_filter_value executes `callback` with tenant filtering temporarily disabled.
431@[deprecated: 'use `orm.DB.unscoped()` for per-instance scope bypass']
432@[deprecated_after: '2027-06-08']
433pub fn without_tenant_filter_value[T](callback fn () T) T {
434 saved := tenant_filter_scope_snapshot()
435 tenant_filter_state.enabled = false
436 defer {
437 tenant_filter_scope_restore(saved)
438 }
439 return callback()
440}
441
442// apply_tenant_filter appends the configured tenant filter condition to `where`.
443pub fn apply_tenant_filter(table Table, where QueryData) QueryData {
444 if !tenant_filter_state.enabled || !tenant_filter_state.has_current_tenant {
445 return where
446 }
447 if table_ignores_tenant_filter(table) {
448 return where
449 }
450 tenant_field_name := table_tenant_filter_field_name(table)
451 if tenant_field_name == '' || tenant_field_name in where.fields {
452 return where
453 }
454 mut where_with_tenant := clone_query_data(where)
455 original_fields_len := where_with_tenant.fields.len
456 if original_fields_len > 1 {
457 // Preserve original WHERE precedence before appending `AND tenant = ...`.
458 where_with_tenant.parentheses << [0, original_fields_len - 1]
459 }
460 if original_fields_len > 0 {
461 where_with_tenant.is_and << true
462 }
463 where_with_tenant.fields << tenant_field_name
464 where_with_tenant.data << tenant_filter_state.current_tenant
465 where_with_tenant.types << tenant_filter_primitive_type(tenant_filter_state.current_tenant)
466 where_with_tenant.kinds << .eq
467 return where_with_tenant
468}
469
470fn tenant_filter_scope_snapshot() TenantFilterScopeState {
471 return TenantFilterScopeState{
472 enabled: tenant_filter_state.enabled
473 has_current_tenant: tenant_filter_state.has_current_tenant
474 current_tenant: tenant_filter_state.current_tenant
475 }
476}
477
478fn tenant_filter_scope_restore(saved TenantFilterScopeState) {
479 tenant_filter_state.enabled = saved.enabled
480 tenant_filter_state.has_current_tenant = saved.has_current_tenant
481 tenant_filter_state.current_tenant = saved.current_tenant
482}
483
484fn normalize_tenant_filter_field_name(field_name string) string {
485 name := trim_attr_arg(field_name)
486 if name == '' {
487 return default_tenant_filter_field_name
488 }
489 return name
490}
491
492fn trim_attr_arg(arg string) string {
493 mut out := arg.trim_space()
494 if out.len >= 2 && ((out.starts_with("'") && out.ends_with("'"))
495 || (out.starts_with('"') && out.ends_with('"'))) {
496 out = out[1..out.len - 1].trim_space()
497 }
498 return out
499}
500
501fn tenant_filter_array_primitive_type[T](value []T) int {
502 if value.len > 0 {
503 first := value[0]
504 return tenant_filter_primitive_type(Primitive(first))
505 }
506 return type_idx['int']
507}
508
509fn tenant_filter_primitive_type(value Primitive) int {
510 return match value {
511 bool {
512 type_idx['bool']
513 }
514 i8 {
515 type_idx['i8']
516 }
517 i16 {
518 type_idx['i16']
519 }
520 int {
521 type_idx['int']
522 }
523 i64 {
524 type_idx['i64']
525 }
526 u8 {
527 type_idx['u8']
528 }
529 u16 {
530 type_idx['u16']
531 }
532 u32 {
533 type_idx['u32']
534 }
535 u64 {
536 type_idx['u64']
537 }
538 f32 {
539 type_idx['f32']
540 }
541 f64 {
542 type_idx['f64']
543 }
544 string {
545 type_string
546 }
547 time.Time {
548 time_
549 }
550 Null {
551 type_idx['int']
552 }
553 InfixType {
554 tenant_filter_primitive_type(value.right)
555 }
556 []Primitive {
557 if value.len > 0 {
558 tenant_filter_primitive_type(value[0])
559 } else {
560 type_idx['int']
561 }
562 }
563 []bool {
564 tenant_filter_array_primitive_type(value)
565 }
566 []f32 {
567 tenant_filter_array_primitive_type(value)
568 }
569 []f64 {
570 tenant_filter_array_primitive_type(value)
571 }
572 []i16 {
573 tenant_filter_array_primitive_type(value)
574 }
575 []i64 {
576 tenant_filter_array_primitive_type(value)
577 }
578 []i8 {
579 tenant_filter_array_primitive_type(value)
580 }
581 []int {
582 tenant_filter_array_primitive_type(value)
583 }
584 []string {
585 tenant_filter_array_primitive_type(value)
586 }
587 []time.Time {
588 tenant_filter_array_primitive_type(value)
589 }
590 []u16 {
591 tenant_filter_array_primitive_type(value)
592 }
593 []u32 {
594 tenant_filter_array_primitive_type(value)
595 }
596 []u64 {
597 tenant_filter_array_primitive_type(value)
598 }
599 []u8 {
600 tenant_filter_array_primitive_type(value)
601 }
602 []InfixType {
603 tenant_filter_array_primitive_type(value)
604 }
605 }
606}
607
608fn table_tenant_filter_field_name(table Table) string {
609 mut field_name := tenant_filter_state.field_name
610 for attr in table.attrs {
611 if attr_name_matches(attr.name, tenant_field_attr_name) && attr.has_arg {
612 override_field_name := trim_attr_arg(attr.arg)
613 if override_field_name != '' {
614 field_name = override_field_name
615 }
616 }
617 }
618 return normalize_tenant_filter_field_name(field_name)
619}
620
621fn table_ignores_tenant_filter(table Table) bool {
622 for attr in table.attrs {
623 if attr_name_matches(attr.name, ignore_tenant_filter_attr_name) {
624 if !attr.has_arg {
625 return true
626 }
627 if is_enabled := parse_bool_attr(attr.arg) {
628 return is_enabled
629 }
630 return true
631 }
632 if attr_name_matches(attr.name, tenant_filter_attr_name) && attr.has_arg {
633 if is_enabled := parse_bool_attr(attr.arg) {
634 return !is_enabled
635 }
636 }
637 }
638 return false
639}
640
641fn attr_name_matches(name string, expected string) bool {
642 return name == expected || name.ends_with('.${expected}')
643}
644
645fn parse_bool_attr(raw string) ?bool {
646 value := trim_attr_arg(raw).to_lower()
647 return match value {
648 '1', 'true', 'yes', 'on' {
649 true
650 }
651 '0', 'false', 'no', 'off' {
652 false
653 }
654 else {
655 none
656 }
657 }
658}
659
660// primitive_type returns the type index for a Primitive value.
661fn primitive_type(value Primitive) int {
662 return match value {
663 bool {
664 type_idx['bool']
665 }
666 i8 {
667 type_idx['i8']
668 }
669 i16 {
670 type_idx['i16']
671 }
672 int {
673 type_idx['int']
674 }
675 i64 {
676 type_idx['i64']
677 }
678 u8 {
679 type_idx['u8']
680 }
681 u16 {
682 type_idx['u16']
683 }
684 u32 {
685 type_idx['u32']
686 }
687 u64 {
688 type_idx['u64']
689 }
690 f32 {
691 type_idx['f32']
692 }
693 f64 {
694 type_idx['f64']
695 }
696 string {
697 type_string
698 }
699 time.Time {
700 time_
701 }
702 Null {
703 type_idx['int']
704 }
705 InfixType {
706 primitive_type(value.right)
707 }
708 []Primitive {
709 if value.len > 0 {
710 primitive_type(value[0])
711 } else {
712 type_idx['int']
713 }
714 }
715 []bool {
716 if value.len > 0 {
717 type_idx['bool']
718 } else {
719 type_idx['int']
720 }
721 }
722 []f32 {
723 if value.len > 0 {
724 type_idx['f32']
725 } else {
726 type_idx['int']
727 }
728 }
729 []f64 {
730 if value.len > 0 {
731 type_idx['f64']
732 } else {
733 type_idx['int']
734 }
735 }
736 []i16 {
737 if value.len > 0 {
738 type_idx['i16']
739 } else {
740 type_idx['int']
741 }
742 }
743 []i64 {
744 if value.len > 0 {
745 type_idx['i64']
746 } else {
747 type_idx['int']
748 }
749 }
750 []i8 {
751 if value.len > 0 {
752 type_idx['i8']
753 } else {
754 type_idx['int']
755 }
756 }
757 []int {
758 if value.len > 0 {
759 type_idx['int']
760 } else {
761 type_idx['int']
762 }
763 }
764 []string {
765 if value.len > 0 {
766 type_string
767 } else {
768 type_idx['int']
769 }
770 }
771 []time.Time {
772 if value.len > 0 {
773 time_
774 } else {
775 type_idx['int']
776 }
777 }
778 []u16 {
779 if value.len > 0 {
780 type_idx['u16']
781 } else {
782 type_idx['int']
783 }
784 }
785 []u32 {
786 if value.len > 0 {
787 type_idx['u32']
788 } else {
789 type_idx['int']
790 }
791 }
792 []u64 {
793 if value.len > 0 {
794 type_idx['u32']
795 } else {
796 type_idx['int']
797 }
798 }
799 []u8 {
800 if value.len > 0 {
801 type_idx['u8']
802 } else {
803 type_idx['int']
804 }
805 }
806 []InfixType {
807 if value.len > 0 {
808 primitive_type(value[0].right)
809 } else {
810 type_idx['int']
811 }
812 }
813 }
814}
815
816fn clone_query_data(data QueryData) QueryData {
817 return QueryData{
818 fields: data.fields.clone()
819 data: data.data.clone()
820 types: data.types.clone()
821 parentheses: data.parentheses.map(it.clone())
822 kinds: data.kinds.clone()
823 auto_fields: data.auto_fields.clone()
824 is_and: data.is_and.clone()
825 batch_rows: data.batch_rows
826 batch_key: data.batch_key
827 }
828}
829
830// Generates an sql stmt, from universal parameter
831// q - The quotes character, which can be different in every type, so it's variable
832// num - Stmt uses nums at prepared statements (? or ?1)
833// qm - Character for prepared statement (qm for question mark, as in sqlite)
834// start_pos - When num is true, it's the start position of the counter
835pub fn orm_stmt_gen(sql_dialect SQLDialect, table Table, q string, kind StmtKind, num bool, qm string,
836 start_pos int, data QueryData, where QueryData) (string, QueryData) {
837 mut str := ''
838 mut c := start_pos
839 insert_data := prepare_insert_query_data(data)
840
841 match kind {
842 .insert {
843 row_count := if insert_data.batch_rows > 0 { insert_data.batch_rows } else { 1 }
844 mut values := []string{}
845 mut select_fields := []string{}
846 are_values_empty := insert_data.fields.len == 0
847
848 for column_name in insert_data.fields {
849 select_fields << '${q}${column_name}${q}'
850 }
851 if !are_values_empty {
852 for _ in 0 .. row_count {
853 mut row_values := []string{}
854 for _ in insert_data.fields {
855 row_values << factory_insert_qm_value(num, qm, c)
856 c++
857 }
858 values << '(${row_values.join(', ')})'
859 }
860 }
861
862 str += 'INSERT INTO ${q}${table.name}${q} '
863
864 if are_values_empty {
865 if row_count == 1 && sql_dialect in [.sqlite, .pg, .h2] {
866 str += 'DEFAULT VALUES'
867 } else {
868 str += '() VALUES '
869 str += []string{len: row_count, init: '()'}.join(', ')
870 }
871 } else {
872 str += '('
873 str += select_fields.join(', ')
874 str += ') VALUES '
875 str += values.join(', ')
876 }
877 }
878 .update {
879 str += 'UPDATE ${q}${table.name}${q} SET '
880 if data.batch_rows > 0 {
881 for i, field in data.fields {
882 str += '${q}${field}${q} = CASE ${q}${data.batch_key}${q} '
883 for _ in 0 .. data.batch_rows {
884 str += 'WHEN ${qm}'
885 if num {
886 str += '${c}'
887 c++
888 }
889 str += ' THEN ${qm}'
890 if num {
891 str += '${c}'
892 c++
893 }
894 str += ' '
895 }
896 str += 'ELSE ${q}${field}${q} END'
897 if i < data.fields.len - 1 {
898 str += ', '
899 }
900 }
901 } else {
902 for i, field in data.fields {
903 str += '${q}${field}${q} = '
904 if data.data.len > i {
905 d := data.data[i]
906 if d is InfixType {
907 op := match d.operator {
908 .add {
909 '+'
910 }
911 .sub {
912 '-'
913 }
914 .mul {
915 '*'
916 }
917 .div {
918 '/'
919 }
920 }
921
922 str += '${d.name} ${op} ${qm}'
923 } else {
924 str += '${qm}'
925 }
926 } else {
927 str += '${qm}'
928 }
929 if num {
930 str += '${c}'
931 c++
932 }
933 if i < data.fields.len - 1 {
934 str += ', '
935 }
936 }
937 }
938 str += ' WHERE '
939 }
940 .delete {
941 str += 'DELETE FROM ${q}${table.name}${q} WHERE '
942 }
943 }
944
945 // where
946 if kind == .update || kind == .delete {
947 str += gen_where_clause(where, q, qm, num, mut &c)
948 }
949 str += ';'
950 $if trace_orm_stmt ? {
951 eprintln('> orm_stmt sql_dialect: ${sql_dialect} | table: ${table.name} | kind: ${kind} | query: ${str}')
952 }
953 $if trace_orm ? {
954 eprintln('> orm: ${str}')
955 }
956 returned_data := if kind == .insert { insert_data } else { data }
957
958 return str, returned_data
959}
960
961fn prepare_insert_query_data(data QueryData) QueryData {
962 mut prepared := QueryData{
963 batch_rows: data.batch_rows
964 batch_key: data.batch_key
965 parentheses: data.parentheses.clone()
966 is_and: data.is_and.clone()
967 }
968 mut included_indexes := []int{}
969 if data.batch_rows > 0 && data.fields.len > 0 {
970 for i, column_name in data.fields {
971 mut skip_auto_field := i in data.auto_fields
972 if skip_auto_field {
973 for row in 0 .. data.batch_rows {
974 data_idx := row * data.fields.len + i
975 if data_idx >= data.data.len
976 || !should_skip_insert_auto_field(data.data[data_idx]) {
977 skip_auto_field = false
978 break
979 }
980 }
981 }
982 if skip_auto_field {
983 continue
984 }
985 prepared.fields << column_name
986 if i < data.types.len {
987 prepared.types << data.types[i]
988 }
989 if i < data.kinds.len {
990 prepared.kinds << data.kinds[i]
991 }
992 if i in data.auto_fields {
993 prepared.auto_fields << prepared.fields.len - 1
994 }
995 included_indexes << i
996 }
997 for row in 0 .. data.batch_rows {
998 for i in included_indexes {
999 data_idx := row * data.fields.len + i
1000 if data_idx < data.data.len {
1001 prepared.data << data.data[data_idx]
1002 }
1003 }
1004 }
1005 return prepared
1006 }
1007 for i, column_name in data.fields {
1008 if i >= data.data.len {
1009 prepared.fields << column_name
1010 if i < data.types.len {
1011 prepared.types << data.types[i]
1012 }
1013 if i < data.kinds.len {
1014 prepared.kinds << data.kinds[i]
1015 }
1016 if i in data.auto_fields {
1017 prepared.auto_fields << prepared.fields.len - 1
1018 }
1019 continue
1020 }
1021 if i in data.auto_fields && should_skip_insert_auto_field(data.data[i]) {
1022 continue
1023 }
1024 prepared.fields << column_name
1025 prepared.data << data.data[i]
1026 if i < data.types.len {
1027 prepared.types << data.types[i]
1028 }
1029 if i < data.kinds.len {
1030 prepared.kinds << data.kinds[i]
1031 }
1032 if i in data.auto_fields {
1033 prepared.auto_fields << prepared.fields.len - 1
1034 }
1035 }
1036 return prepared
1037}
1038
1039fn should_skip_insert_auto_field(value Primitive) bool {
1040 mut x := value
1041 return match mut x {
1042 Null { true }
1043 string { x == '' }
1044 i8, i16, int, i64, u8, u16, u32, u64 { u64(x) == 0 }
1045 f32, f64 { f64(x) == 0 }
1046 time.Time { x == time.Time{} }
1047 bool { !x }
1048 else { false }
1049 }
1050}
1051
1052fn build_upsert_where(data QueryData, conflict_groups [][]string) !QueryData {
1053 mut field_indexes := map[string]int{}
1054 for i, field in data.fields {
1055 field_indexes[field] = i
1056 }
1057 mut where := QueryData{}
1058 for group in conflict_groups {
1059 if group.len == 0 {
1060 continue
1061 }
1062 start := where.fields.len
1063 if start > 0 {
1064 where.is_and << false
1065 }
1066 for i, field_name in group {
1067 idx := field_indexes[field_name] or {
1068 return error('${@FN}(): missing conflict field `${field_name}` in upsert data')
1069 }
1070 if idx >= data.data.len {
1071 return error('${@FN}(): missing conflict value for `${field_name}` in upsert data')
1072 }
1073 where.fields << field_name
1074 where.data << data.data[idx]
1075 where.kinds << .eq
1076 if i > 0 {
1077 where.is_and << true
1078 }
1079 }
1080 if group.len > 1 {
1081 where.parentheses << [start, where.fields.len - 1]
1082 }
1083 }
1084 return where
1085}
1086
1087fn upsert_conflict_groups(data QueryData, conflict_groups [][]string) [][]string {
1088 mut present_fields := map[string]bool{}
1089 for field in data.fields {
1090 present_fields[field] = true
1091 }
1092 mut usable := [][]string{}
1093 for group in conflict_groups {
1094 if group.len == 0 {
1095 continue
1096 }
1097 mut ok := true
1098 for field_name in group {
1099 if field_name !in present_fields {
1100 ok = false
1101 break
1102 }
1103 }
1104 if ok {
1105 usable << group
1106 }
1107 }
1108 return usable
1109}
1110
1111pub struct UpsertData {
1112pub:
1113 valid bool
1114pub mut:
1115 insert_data QueryData
1116 where QueryData
1117}
1118
1119// prepare_upsert resolves the filtered insert data and the conflict `WHERE` clause for an upsert.
1120pub fn prepare_upsert(data QueryData, conflict_groups [][]string) UpsertData {
1121 insert_data := prepare_insert_query_data(data)
1122 usable_groups := upsert_conflict_groups(insert_data, conflict_groups)
1123 if usable_groups.len == 0 {
1124 return UpsertData{
1125 insert_data: insert_data
1126 }
1127 }
1128 where := build_upsert_where(insert_data, usable_groups) or {
1129 return UpsertData{
1130 insert_data: insert_data
1131 }
1132 }
1133 return UpsertData{
1134 valid: true
1135 insert_data: insert_data
1136 where: where
1137 }
1138}
1139
1140// upsert_count converts a `select count(*)` ORM result into an integer count.
1141pub fn upsert_count(result [][]Primitive) int {
1142 if result.len == 0 || result[0].len == 0 {
1143 return 0
1144 }
1145 count_val := result[0][0]
1146 return match count_val {
1147 int { count_val }
1148 i64 { int(count_val) }
1149 u64 { int(count_val) }
1150 else { 0 }
1151 }
1152}
1153
1154// upsert_missing_conflict_error returns the standard missing-conflict error for SQL upserts.
1155pub fn upsert_missing_conflict_error(table Table) ! {
1156 return error('upsert(): table `${table.name}` needs at least one primary or unique field with a concrete value')
1157}
1158
1159// upsert_ambiguous_error returns the standard ambiguous-match error for SQL upserts.
1160pub fn upsert_ambiguous_error(table Table) ! {
1161 return error('upsert(): upsert on table `${table.name}` matched multiple rows')
1162}
1163
1164// Generates an sql select stmt, from universal parameter
1165// orm - See SelectConfig
1166// q, num, qm, start_pos - see orm_stmt_gen
1167// where - See QueryData
1168pub fn orm_select_gen(cfg SelectConfig, q string, num bool, qm string, start_pos int, where QueryData) string {
1169 mut str := 'SELECT '
1170
1171 if cfg.has_distinct {
1172 str += 'DISTINCT '
1173 }
1174
1175 if cfg.aggregate_kind != .none {
1176 if cfg.aggregate_kind == .count {
1177 str += cfg.aggregate_kind.to_str()
1178 } else {
1179 str += '${cfg.aggregate_kind.to_str()}(${q}${cfg.aggregate_field}${q})'
1180 }
1181 } else {
1182 for i, field in cfg.fields {
1183 select_expr := if cfg.select_exprs.len > i && cfg.select_exprs[i] != '' {
1184 cfg.select_exprs[i]
1185 } else {
1186 field
1187 }
1188 if select_expr == field {
1189 str += '${q}${field}${q}'
1190 } else {
1191 str += select_expr
1192 }
1193 if i < cfg.fields.len - 1 {
1194 str += ', '
1195 }
1196 }
1197 }
1198
1199 str += ' FROM ${q}${cfg.table.name}${q}'
1200
1201 // Generate JOIN clauses
1202 for join in cfg.joins {
1203 str += ' ${join.kind.to_str()} ${q}${join.table.name}${q}'
1204 str += ' ON ${q}${cfg.table.name}${q}.${q}${join.on_left_col}${q}'
1205 str += ' = ${q}${join.table.name}${q}.${q}${join.on_right_col}${q}'
1206 }
1207
1208 mut c := start_pos
1209
1210 if cfg.has_where {
1211 str += ' WHERE '
1212 $if trace_orm_where ? {
1213 eprintln('> orm_select_gen: where.fields.len = ${where.fields.len}')
1214 eprintln('> orm_select_gen: where.kinds.len = ${where.kinds.len}')
1215 for i, field in where.fields {
1216 eprintln('> orm_select_gen: field[${i}] = ${field}')
1217 }
1218 }
1219 str += gen_where_clause(where, q, qm, num, mut &c)
1220 }
1221
1222 // Note: do not order, if the user did not want it explicitly,
1223 // ordering is *slow*, especially if there are no indexes!
1224 if cfg.has_order {
1225 str += ' ORDER BY '
1226 str += '${q}${cfg.order}${q} '
1227 str += cfg.order_type.to_str()
1228 }
1229
1230 if cfg.has_limit {
1231 str += ' LIMIT ${qm}'
1232 if num {
1233 str += '${c}'
1234 c++
1235 }
1236 }
1237
1238 if cfg.has_offset {
1239 str += ' OFFSET ${qm}'
1240 if num {
1241 str += '${c}'
1242 c++
1243 }
1244 }
1245
1246 str += ';'
1247 $if trace_orm_query ? {
1248 eprintln('> orm_query: ${str}')
1249 }
1250 $if trace_orm ? {
1251 eprintln('> orm: ${str}')
1252 }
1253 return str
1254}
1255
1256const table_qualified_field_separator = '::v_orm_table::'
1257
1258fn table_qualified_field(table_name string, column_name string) string {
1259 return '${table_name}${table_qualified_field_separator}${column_name}'
1260}
1261
1262fn gen_where_clause(where QueryData, q string, qm string, num bool, mut c &int) string {
1263 mut str := ''
1264 mut data_idx := 0
1265 for i, field in where.fields {
1266 current_pre_par := where.parentheses.count(it[0] == i)
1267 current_post_par := where.parentheses.count(it[1] == i)
1268
1269 if current_pre_par > 0 {
1270 str += ' ( '.repeat(current_pre_par)
1271 }
1272 str += gen_qualified_field(field, q) + ' ${where.kinds[i].to_str()}'
1273 if !where.kinds[i].is_unary() {
1274 array_len := if where.data.len > data_idx {
1275 primitive_array_len(where.data[data_idx])
1276 } else {
1277 -1
1278 }
1279 if array_len >= 0 {
1280 mut tmp := []string{len: array_len}
1281 for j in 0 .. array_len {
1282 tmp[j] = '${qm}'
1283 if num {
1284 tmp[j] += '${c}'
1285 c++
1286 }
1287 }
1288 str += ' (${tmp.join(', ')})'
1289 } else {
1290 str += ' ${qm}'
1291 if num {
1292 str += '${c}'
1293 c++
1294 }
1295 }
1296 data_idx++
1297 }
1298 if current_post_par > 0 {
1299 str += ' ) '.repeat(current_post_par)
1300 }
1301 if i < where.fields.len - 1 {
1302 if where.is_and[i] {
1303 str += ' AND '
1304 } else {
1305 str += ' OR '
1306 }
1307 }
1308 }
1309 return str
1310}
1311
1312// gen_qualified_field renders a field name with the given quote character q.
1313// Table-qualified fields use an internal marker so embedded ORM column names
1314// containing dots (e.g. `Coordinates.latitude`) stay single quoted identifiers.
1315fn gen_qualified_field(field string, q string) string {
1316 if idx := field.index(table_qualified_field_separator) {
1317 column_start := idx + table_qualified_field_separator.len
1318 return '${q}${field[..idx]}${q}.${q}${field[column_start..]}${q}'
1319 }
1320 return '${q}${field}${q}'
1321}
1322
1323// Generates an sql table stmt, from universal parameter
1324// table - Table struct
1325// q - see orm_stmt_gen
1326// defaults - enables default values in stmt
1327// def_unique_len - sets default unique length for texts
1328// fields - See TableField
1329// sql_from_v - Function which maps type indices to sql type names
1330// alternative - Needed for msdb
1331fn parse_table_attr_fields(table Table, attr VAttribute, valid_sql_field_names []string) ![]string {
1332 if attr.arg == '' || attr.kind != .string {
1333 return error("${attr.name} attribute needs to be in the format [${attr.name}: 'f1, f2, f3']")
1334 }
1335 mut attr_fields := []string{}
1336 for raw_field_name in attr.arg.split(',') {
1337 field_name := raw_field_name.trim_space()
1338 if field_name == '' {
1339 return error("${attr.name} attribute needs to be in the format [${attr.name}: 'f1, f2, f3']")
1340 }
1341 if field_name !in valid_sql_field_names {
1342 return error("table `${table.name}` has no field's name: `${field_name}`")
1343 }
1344 if field_name !in attr_fields {
1345 attr_fields << field_name
1346 }
1347 }
1348 return attr_fields
1349}
1350
1351pub fn orm_table_gen(sql_dialect SQLDialect, table Table, q string, defaults bool, def_unique_len int, fields []TableField, sql_from_v fn (int) !string,
1352 alternative bool) !string {
1353 mut str := 'CREATE TABLE IF NOT EXISTS ${q}${table.name}${q} ('
1354
1355 if alternative {
1356 str = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=${q}${table.name}${q} and xtype=${q}U${q}) CREATE TABLE ${q}${table.name}${q} ('
1357 }
1358
1359 mut fs := []string{}
1360 mut unique_fields := []string{}
1361 mut unique := map[string][]string{}
1362 mut primary := ''
1363 mut primary_typ := 0
1364 mut table_comment := ''
1365 mut field_comments := map[string]string{}
1366 mut index_fields := []string{}
1367 mut unique_key_fields := [][]string{}
1368
1369 valid_sql_field_names := fields.map(sql_field_name(it))
1370
1371 for attr in table.attrs {
1372 match attr.name {
1373 'comment' {
1374 if attr.arg != '' && attr.kind == .string {
1375 table_comment = attr.arg.replace('"', '\\"')
1376 }
1377 }
1378 'index' {
1379 attr_fields := parse_table_attr_fields(table, attr, valid_sql_field_names) or {
1380 return err
1381 }
1382 for field_name in attr_fields {
1383 if field_name !in index_fields {
1384 index_fields << field_name
1385 }
1386 }
1387 }
1388 'unique_key' {
1389 attr_fields := parse_table_attr_fields(table, attr, valid_sql_field_names) or {
1390 return err
1391 }
1392 if attr_fields.len > 0 {
1393 unique_key_fields << attr_fields
1394 }
1395 }
1396 else {}
1397 }
1398 }
1399
1400 for field in fields {
1401 if field.is_arr {
1402 continue
1403 }
1404 mut default_val := field.default_val
1405 mut has_default := default_val != ''
1406 mut nullable := field.nullable
1407 mut is_unique := false
1408 mut is_skip := false
1409 mut unique_len := 0
1410 mut references_table := ''
1411 mut references_field := ''
1412 mut field_comment := ''
1413 mut field_name := sql_field_name(field)
1414 mut col_typ := sql_from_v(sql_field_type(field)) or {
1415 // Struct fields are treated as foreign key references, which requires a primary key
1416 if primary_typ == 0 {
1417 return error('struct field `${field_name}` in table `${table.name}` requires a primary key field for foreign key reference - add a field with [primary] attribute or use [sql: \'-\'] to skip this field')
1418 }
1419 field_name = '${field_name}_id'
1420 sql_from_v(primary_typ)!
1421 }
1422 for attr in field.attrs {
1423 match attr.name {
1424 'sql' {
1425 // [sql:'-']
1426 if attr.arg == '-' {
1427 is_skip = true
1428 }
1429 }
1430 'primary' {
1431 primary = field_name
1432 primary_typ = field.typ
1433 }
1434 'unique' {
1435 if attr.arg != '' {
1436 if attr.kind == .string {
1437 if attr.arg !in unique {
1438 unique[attr.arg] = []string{}
1439 }
1440 unique[attr.arg] << field_name
1441 continue
1442 } else if attr.kind == .number {
1443 unique_len = attr.arg.int()
1444 is_unique = true
1445 continue
1446 }
1447 }
1448 is_unique = true
1449 }
1450 'skip' {
1451 is_skip = true
1452 }
1453 'sql_type' {
1454 col_typ = attr.arg.str()
1455 }
1456 'default' {
1457 has_default = true
1458 if default_val == '' {
1459 default_val = attr.arg.str()
1460 }
1461 }
1462 'references' {
1463 nullable = true
1464 if attr.arg == '' {
1465 if field.name.ends_with('_id') {
1466 references_table = field.name.trim_right('_id')
1467 references_field = 'id'
1468 } else {
1469 return error("references attribute can only be implicit if the field name ends with '_id'")
1470 }
1471 } else {
1472 if attr.arg.trim(' ') == '' {
1473 return error("references attribute needs to be in the format [references], [references: 'tablename'], or [references: 'tablename(field_id)']")
1474 }
1475 if attr.arg.contains('(') {
1476 if ref_table, ref_field := attr.arg.split_once('(') {
1477 if !ref_field.ends_with(')') {
1478 return error("explicit references attribute should be written as [references: 'tablename(field_id)']")
1479 }
1480 references_table = ref_table
1481 references_field = ref_field[..ref_field.len - 1]
1482 }
1483 } else {
1484 references_table = attr.arg
1485 references_field = 'id'
1486 }
1487 }
1488 }
1489 'comment' {
1490 if attr.arg != '' && attr.kind == .string {
1491 field_comment = attr.arg.replace("'", "\\'")
1492 field_comments[field_name] = field_comment
1493 }
1494 }
1495 'index' {
1496 if field_name !in index_fields {
1497 index_fields << field_name
1498 }
1499 }
1500 else {}
1501 }
1502 }
1503 if is_skip {
1504 continue
1505 }
1506 mut stmt := ''
1507 if col_typ == '' {
1508 return error('Unknown type (${field.typ}) for field ${field.name} in struct ${table.name}')
1509 }
1510 stmt = '${q}${field_name}${q} ${col_typ}'
1511 if defaults && has_default {
1512 if default_val != '' {
1513 stmt += ' DEFAULT ${default_val}'
1514 } else {
1515 // Handle @[default: ''] - explicitly set DEFAULT '' for the column
1516 stmt += " DEFAULT ''"
1517 }
1518 }
1519 if sql_dialect == .mysql && field_comment != '' {
1520 stmt += " COMMENT '${field_comment}'"
1521 }
1522 if !nullable {
1523 stmt += ' NOT NULL'
1524 }
1525 if is_unique {
1526 mut f := 'UNIQUE(${q}${field_name}${q}'
1527 if col_typ == 'TEXT' && def_unique_len > 0 {
1528 if unique_len > 0 {
1529 f += '(${unique_len})'
1530 } else {
1531 f += '(${def_unique_len})'
1532 }
1533 }
1534 f += ')'
1535 unique_fields << f
1536 }
1537 if references_table != '' {
1538 stmt += ' REFERENCES ${q}${references_table}${q}(${q}${references_field}${q})'
1539 }
1540 fs << stmt
1541 }
1542
1543 if unique.len > 0 {
1544 for k, v in unique {
1545 mut tmp := []string{}
1546 for f in v {
1547 tmp << '${q}${f}${q}'
1548 }
1549 fs << '/* ${k} */UNIQUE(${tmp.join(', ')})'
1550 }
1551 }
1552 for key_fields in unique_key_fields {
1553 mut tmp := []string{}
1554 for field_name in key_fields {
1555 tmp << '${q}${field_name}${q}'
1556 }
1557 fs << 'UNIQUE(${tmp.join(', ')})'
1558 }
1559
1560 if primary != '' {
1561 fs << 'PRIMARY KEY(${q}${primary}${q})'
1562 }
1563
1564 fs << unique_fields
1565 unique_fields.clear() // ownership transferred to fs to avoid double-free under -autofree
1566 str += fs.join(', ')
1567 if index_fields.len > 0 && sql_dialect == .mysql {
1568 str += ', INDEX `idx_${table.name}` (`'
1569 str += index_fields.join('`,`')
1570 str += '`)'
1571 }
1572 str += ')'
1573 if sql_dialect == .mysql && table_comment != '' {
1574 str += " COMMENT = '${table_comment}'"
1575 }
1576 str += ';'
1577
1578 if sql_dialect in [.pg, .h2] {
1579 if table_comment != '' {
1580 str += "\nCOMMENT ON TABLE \"${table.name}\" IS '${table_comment}';"
1581 }
1582 for f, c in field_comments {
1583 str += "\nCOMMENT ON COLUMN \"${table.name}\".\"${f}\" IS '${c}';"
1584 }
1585 }
1586 if sql_dialect in [.pg, .sqlite, .h2] && index_fields.len > 0 {
1587 str += '\nCREATE INDEX "idx_${table.name}" ON "${table.name}" ("'
1588 str += index_fields.join('","')
1589 str += '");'
1590 }
1591 $if trace_orm_create ? {
1592 eprintln('> orm_create table: ${table.name} | query: ${str}')
1593 }
1594 $if trace_orm ? {
1595 eprintln('> orm: ${str}')
1596 }
1597
1598 return str
1599}
1600
1601// Get's the sql field type
1602fn sql_field_type(field TableField) int {
1603 mut typ := field.typ
1604 for attr in field.attrs {
1605 // @[serial]
1606 if attr.name == 'serial' && attr.kind == .plain && !attr.has_arg {
1607 typ = serial
1608 break
1609 }
1610
1611 if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' {
1612 // @[sql: serial]
1613 if attr.arg.to_lower() == 'serial' {
1614 typ = serial
1615 break
1616 }
1617 typ = type_idx[attr.arg]
1618 break
1619 }
1620 }
1621 return typ
1622}
1623
1624// Get's the sql field name
1625fn sql_field_name(field TableField) string {
1626 mut name := field.name
1627 for attr in field.attrs {
1628 if attr.name == 'sql' && attr.has_arg && attr.kind == .string {
1629 name = attr.arg
1630 break
1631 }
1632 }
1633 return name
1634}
1635
1636// Get's the SQL select expression for a field.
1637fn sql_field_select_expr(field TableField) string {
1638 for attr in field.attrs {
1639 if attr.name == 'sql_select' && attr.has_arg {
1640 return trim_attr_arg(attr.arg)
1641 }
1642 }
1643 return sql_field_name(field)
1644}
1645
1646// needed for backend functions
1647
1648fn bool_to_primitive(b bool) Primitive {
1649 return Primitive(b)
1650}
1651
1652fn option_bool_to_primitive(b ?bool) Primitive {
1653 return if b_ := b { Primitive(b_) } else { null_primitive }
1654}
1655
1656fn array_bool_to_primitive(b []bool) Primitive {
1657 return Primitive(b.map(bool_to_primitive(it)))
1658}
1659
1660fn f32_to_primitive(b f32) Primitive {
1661 return Primitive(b)
1662}
1663
1664fn option_f32_to_primitive(b ?f32) Primitive {
1665 return if b_ := b { Primitive(b_) } else { null_primitive }
1666}
1667
1668fn array_f32_to_primitive(b []f32) Primitive {
1669 return Primitive(b.map(f32_to_primitive(it)))
1670}
1671
1672fn f64_to_primitive(b f64) Primitive {
1673 return Primitive(b)
1674}
1675
1676fn option_f64_to_primitive(b ?f64) Primitive {
1677 return if b_ := b { Primitive(b_) } else { null_primitive }
1678}
1679
1680fn array_f64_to_primitive(b []f64) Primitive {
1681 return Primitive(b.map(f64_to_primitive(it)))
1682}
1683
1684fn i8_to_primitive(b i8) Primitive {
1685 return Primitive(b)
1686}
1687
1688fn option_i8_to_primitive(b ?i8) Primitive {
1689 return if b_ := b { Primitive(b_) } else { null_primitive }
1690}
1691
1692fn array_i8_to_primitive(b []i8) Primitive {
1693 return Primitive(b.map(i8_to_primitive(it)))
1694}
1695
1696fn i16_to_primitive(b i16) Primitive {
1697 return Primitive(b)
1698}
1699
1700fn option_i16_to_primitive(b ?i16) Primitive {
1701 return if b_ := b { Primitive(b_) } else { null_primitive }
1702}
1703
1704fn array_i16_to_primitive(b []i16) Primitive {
1705 return Primitive(b.map(i16_to_primitive(it)))
1706}
1707
1708fn int_to_primitive(b int) Primitive {
1709 return Primitive(b)
1710}
1711
1712fn option_int_to_primitive(b ?int) Primitive {
1713 return if b_ := b { Primitive(b_) } else { null_primitive }
1714}
1715
1716fn array_int_to_primitive(b []int) Primitive {
1717 return Primitive(b.map(int_to_primitive(it)))
1718}
1719
1720// int_literal_to_primitive handles int literal value
1721fn int_literal_to_primitive(b int) Primitive {
1722 return Primitive(b)
1723}
1724
1725fn option_int_literal_to_primitive(b ?int) Primitive {
1726 return if b_ := b { Primitive(b_) } else { null_primitive }
1727}
1728
1729fn array_int_literal_to_primitive(b []int) Primitive {
1730 return Primitive(b.map(int_literal_to_primitive(it)))
1731}
1732
1733// float_literal_to_primitive handles float literal value
1734fn float_literal_to_primitive(b f64) Primitive {
1735 return Primitive(b)
1736}
1737
1738fn option_float_literal_to_primitive(b ?f64) Primitive {
1739 return if b_ := b { Primitive(b_) } else { null_primitive }
1740}
1741
1742fn array_float_literal_to_primitive(b []f64) Primitive {
1743 return Primitive(b.map(float_literal_to_primitive(it)))
1744}
1745
1746fn i64_to_primitive(b i64) Primitive {
1747 return Primitive(b)
1748}
1749
1750fn option_i64_to_primitive(b ?i64) Primitive {
1751 return if b_ := b { Primitive(b_) } else { null_primitive }
1752}
1753
1754fn array_i64_to_primitive(b []i64) Primitive {
1755 return Primitive(b.map(i64_to_primitive(it)))
1756}
1757
1758fn u8_to_primitive(b u8) Primitive {
1759 return Primitive(b)
1760}
1761
1762fn option_u8_to_primitive(b ?u8) Primitive {
1763 return if b_ := b { Primitive(b_) } else { null_primitive }
1764}
1765
1766fn array_u8_to_primitive(b []u8) Primitive {
1767 return Primitive(b.map(u8_to_primitive(it)))
1768}
1769
1770fn u16_to_primitive(b u16) Primitive {
1771 return Primitive(b)
1772}
1773
1774fn option_u16_to_primitive(b ?u16) Primitive {
1775 return if b_ := b { Primitive(b_) } else { null_primitive }
1776}
1777
1778fn array_u16_to_primitive(b []u16) Primitive {
1779 return Primitive(b.map(u16_to_primitive(it)))
1780}
1781
1782fn u32_to_primitive(b u32) Primitive {
1783 return Primitive(b)
1784}
1785
1786fn option_u32_to_primitive(b ?u32) Primitive {
1787 return if b_ := b { Primitive(b_) } else { null_primitive }
1788}
1789
1790fn array_u32_to_primitive(b []u32) Primitive {
1791 return Primitive(b.map(u32_to_primitive(it)))
1792}
1793
1794fn u64_to_primitive(b u64) Primitive {
1795 return Primitive(b)
1796}
1797
1798fn option_u64_to_primitive(b ?u64) Primitive {
1799 return if b_ := b { Primitive(b_) } else { null_primitive }
1800}
1801
1802fn array_u64_to_primitive(b []u64) Primitive {
1803 return Primitive(b.map(u64_to_primitive(it)))
1804}
1805
1806fn string_to_primitive(b string) Primitive {
1807 return Primitive(b)
1808}
1809
1810fn option_string_to_primitive(b ?string) Primitive {
1811 return if b_ := b { Primitive(b_) } else { null_primitive }
1812}
1813
1814fn array_string_to_primitive(b []string) Primitive {
1815 return Primitive(b.map(string_to_primitive(it)))
1816}
1817
1818fn time_to_primitive(b time.Time) Primitive {
1819 return Primitive(b)
1820}
1821
1822fn option_time_to_primitive(b ?time.Time) Primitive {
1823 return if b_ := b { Primitive(b_) } else { null_primitive }
1824}
1825
1826fn array_time_to_primitive(b []time.Time) Primitive {
1827 return Primitive(b.map(time_to_primitive(it)))
1828}
1829
1830fn infix_to_primitive(b InfixType) Primitive {
1831 return Primitive(b)
1832}
1833
1834fn factory_insert_qm_value(num bool, qm string, c int) string {
1835 if num {
1836 return '${qm}${c}'
1837 } else {
1838 return '${qm}'
1839 }
1840}
1841