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