v2 / vlib / orm / orm_func.v
1313 lines · 1230 sloc · 34.39 KB · d5578efae67ef01ab3a63866d429b7dacf6c237d
Raw
1module orm
2
3import time
4import strings.textscanner
5
6const operators = ['=', '!=', '<>', '>=', '<=', '>', '<', 'LIKE', 'ILIKE', 'IS NULL', 'IS NOT NULL',
7 'IN', 'NOT IN']!
8
9pub struct AggregateValue {
10pub:
11 has_value bool
12 value Primitive = Null{}
13}
14
15@[heap]
16pub struct QueryBuilder[T] {
17pub mut:
18 meta []TableField
19 valid_sql_field_names []string
20 conn Connection
21 config SelectConfig
22 data QueryData
23 where QueryData
24}
25
26// new_query create a new query object for struct `T`
27pub fn new_query[T](conn Connection) &QueryBuilder[T] {
28 meta := struct_meta[T]()
29 return &QueryBuilder[T]{
30 meta: meta
31 valid_sql_field_names: meta.map(sql_field_name(it))
32 conn: conn
33 config: SelectConfig{
34 table: table_from_struct[T]()
35 }
36 data: QueryData{}
37 where: QueryData{}
38 }
39}
40
41// reset reset a query object, but keep the connection and table name
42pub fn (qb_ &QueryBuilder[T]) reset() &QueryBuilder[T] {
43 mut qb := unsafe { qb_ }
44 old_table := qb.config.table
45 qb.config = SelectConfig{
46 table: old_table
47 }
48 qb.data = QueryData{}
49 qb.where = QueryData{}
50 return qb
51}
52
53// where create a `where` clause, it will `AND` with previous `where` clause.
54// valid token in the `condition` include: `field's names`, `operator`, `(`, `)`, `?`, `AND`, `OR`, `||`, `&&`,
55// valid `operator` incldue: `=`, `!=`, `<>`, `>=`, `<=`, `>`, `<`, `LIKE`, `ILIKE`, `IS NULL`, `IS NOT NULL`, `IN`, `NOT IN`
56// example: `where('(a > ? AND b <= ?) OR (c <> ? AND (x = ? OR y = ?))', a, b, c, x, y)`
57pub fn (qb_ &QueryBuilder[T]) where(condition string, params ...Primitive) !&QueryBuilder[T] {
58 mut qb := unsafe { qb_ }
59 if qb.where.fields.len > 0 {
60 // skip first field
61 qb.where.is_and << true // and
62 }
63 qb.parse_conditions(condition, normalize_primitive_arguments(params))!
64 qb.config.has_where = true
65 return qb
66}
67
68// or_where create a `where` clause, it will `OR` with previous `where` clause.
69pub fn (qb_ &QueryBuilder[T]) or_where(condition string, params ...Primitive) !&QueryBuilder[T] {
70 mut qb := unsafe { qb_ }
71 if qb.where.fields.len > 0 {
72 // skip first field
73 qb.where.is_and << false // or
74 }
75 qb.parse_conditions(condition, normalize_primitive_arguments(params))!
76 qb.config.has_where = true
77 return qb
78}
79
80fn normalize_primitive_arguments(params []Primitive) []Primitive {
81 mut normalized := []Primitive{cap: params.len}
82 for param in params {
83 normalized << primitive_value(param)
84 }
85 return normalized
86}
87
88fn primitive_value(value Primitive) Primitive {
89 return match value {
90 []bool { primitive_array(value) }
91 []f32 { primitive_array(value) }
92 []f64 { primitive_array(value) }
93 []i16 { primitive_array(value) }
94 []i64 { primitive_array(value) }
95 []i8 { primitive_array(value) }
96 []int { primitive_array(value) }
97 []string { primitive_array(value) }
98 []time.Time { primitive_array(value) }
99 []u16 { primitive_array(value) }
100 []u32 { primitive_array(value) }
101 []u64 { primitive_array(value) }
102 []u8 { primitive_array(value) }
103 []InfixType { primitive_array(value) }
104 else { value }
105 }
106}
107
108fn primitive_array[T](values []T) []Primitive {
109 mut out := []Primitive{cap: values.len}
110 for value in values {
111 out << Primitive(value)
112 }
113 return out
114}
115
116fn parse_error(msg string, pos int, conds string) ! {
117 mut m := msg + '\n' + '\t' + conds + '\n\t' + ' '.repeat(pos) + '^\n'
118 return error(m)
119}
120
121enum ParserState {
122 field
123 op
124 qm
125}
126
127struct MyTextScanner {
128 textscanner.TextScanner
129mut:
130 last_tok_start int
131}
132
133// next_tok get next token from scanner, skip whitespace
134fn (mut ss MyTextScanner) next_tok() string {
135 mut ret := ''
136 ss.skip_whitespace()
137 ss.last_tok_start = ss.pos
138 ss_upper := ss.input.to_upper()
139
140 // check for longest token first
141 if ss_upper[ss.pos..].starts_with('IS NOT NULL') {
142 ss.pos += 11
143 return 'IS NOT NULL'
144 }
145 if ss_upper[ss.pos..].starts_with('IS NULL') {
146 ss.pos += 7
147 return 'IS NULL'
148 }
149 if ss_upper[ss.pos..].starts_with('NOT IN') {
150 ss.pos += 6
151 return 'NOT IN'
152 }
153 if ss.remaining() >= 2 {
154 two_chars := ss.input[ss.pos..ss.pos + 2]
155 if two_chars in ['>=', '<=', '<>', '!=', '||', '&&', 'IN'] {
156 ss.pos += 2
157 return two_chars
158 }
159 }
160 if ss.remaining() > 0 {
161 c := ss.input[ss.pos]
162 if c in [`>`, `<`, `=`] {
163 ss.pos++
164 return c.ascii_str()
165 }
166 }
167 for ss.remaining() > 0 {
168 c := u8(ss.next()) // only support ascii now
169 if c.is_alnum() || c == `_` || c == `$` {
170 ret += c.ascii_str()
171 } else {
172 if ret.len == 0 {
173 ret = c.ascii_str()
174 } else {
175 // already contain a tok
176 ss.back()
177 }
178 break
179 }
180 }
181 return ret
182}
183
184// parse_conditions update `qb` by parsing the `conds` string
185fn (qb_ &QueryBuilder[T]) parse_conditions(conds string, params []Primitive) ! {
186 // conditions: '(a > ? AND b <= ?) OR (c <> ? AND (x = ? OR y = ?))'
187 mut qb := unsafe { qb_ }
188 if conds.len == 0 {
189 return error('${@FN}(): empty condition')
190 }
191 required_params := conds.count('?')
192 if required_params != params.len {
193 parse_error('${@FN}(): condition requires `${required_params}` params but got `${params.len}`',
194 0, conds)!
195 }
196
197 mut s := MyTextScanner{
198 input: conds
199 ilen: conds.len
200 }
201
202 mut state := ParserState.field
203 mut tok := ''
204 mut current_field := ''
205 mut current_op := OperationKind.eq
206 mut current_is_and := true
207 mut i := 0
208 mut paren_stack := []int{}
209 mut is_first_field := true
210 for s.remaining() > 0 {
211 tok = s.next_tok()
212 match state {
213 .field {
214 // only support valid field names
215 if tok in qb.valid_sql_field_names {
216 current_field = tok
217 state = .op
218 } else if tok == '(' {
219 paren_stack << qb.where.fields.len
220 } else if tok == ')' {
221 if paren_stack.len == 0 {
222 parse_error('${@FN}: unexpected `)`', s.last_tok_start, conds)!
223 }
224 start_pos := paren_stack.pop()
225 qb.where.parentheses << [start_pos, qb.where.fields.len - 1]
226 } else {
227 parse_error("${@FN}: table `${qb.config.table}` has no field's name: `${tok}`",
228 s.last_tok_start, conds)!
229 }
230 }
231 .op {
232 current_op = match tok.to_upper() {
233 '=' {
234 OperationKind.eq
235 }
236 '<>' {
237 OperationKind.neq
238 }
239 '!=' {
240 OperationKind.neq
241 }
242 '>' {
243 OperationKind.gt
244 }
245 '<' {
246 OperationKind.lt
247 }
248 '>=' {
249 OperationKind.ge
250 }
251 '<=' {
252 OperationKind.le
253 }
254 'LIKE' {
255 OperationKind.orm_like
256 }
257 'ILIKE' {
258 OperationKind.orm_ilike
259 }
260 'IS NULL' {
261 OperationKind.is_null
262 }
263 'IS NOT NULL' {
264 OperationKind.is_not_null
265 }
266 'IN' {
267 OperationKind.in
268 }
269 'NOT IN' {
270 OperationKind.not_in
271 }
272 else {
273 parse_error('${@FN}(): unsupported operator: `${tok}`', s.last_tok_start,
274 conds)!
275 OperationKind.eq
276 }
277 }
278
279 if current_op in [.is_null, .is_not_null]! {
280 qb.where.fields << current_field
281 qb.where.kinds << current_op
282 if is_first_field {
283 is_first_field = false
284 } else {
285 // skip first field
286 qb.where.is_and << current_is_and
287 }
288 }
289 state = .qm
290 }
291 .qm {
292 if tok == '?' {
293 // finish an expr, update `qb`
294 qb.where.fields << current_field
295 qb.where.data << params[i]
296 qb.where.kinds << current_op
297 if is_first_field {
298 is_first_field = false
299 } else {
300 // skip first field
301 qb.where.is_and << current_is_and
302 }
303 i++
304 } else if tok == ')' {
305 if paren_stack.len == 0 {
306 parse_error('${@FN}: unexpected `)`', s.last_tok_start, conds)!
307 }
308 start_pos := paren_stack.pop()
309 qb.where.parentheses << [start_pos, qb.where.fields.len - 1]
310 } else if tok == 'AND' {
311 current_is_and = true
312 state = .field
313 } else if tok == 'OR' {
314 current_is_and = false
315 state = .field
316 } else if tok == '&&' {
317 current_is_and = true
318 state = .field
319 } else if tok == '||' {
320 current_is_and = false
321 state = .field
322 } else {
323 parse_error('${@FN}(): unexpected `${tok}`, maybe `AND`,`OR`',
324 s.last_tok_start, conds)!
325 }
326 }
327 }
328 }
329}
330
331// order create a `order` clause
332pub fn (qb_ &QueryBuilder[T]) order(order_type OrderType, field string) !&QueryBuilder[T] {
333 mut qb := unsafe { qb_ }
334 if field in qb.valid_sql_field_names {
335 qb.config.has_order = true
336 qb.config.order = field
337 qb.config.order_type = order_type
338 } else {
339 return error("${@FN}(): table `${qb.config.table}` has no field's name: `${field}`")
340 }
341 return qb
342}
343
344// limit create a `limit` clause
345pub fn (qb_ &QueryBuilder[T]) limit(limit int) !&QueryBuilder[T] {
346 mut qb := unsafe { qb_ }
347 if limit > 0 {
348 qb.config.has_limit = true
349 qb.data.data << Primitive(limit)
350 qb.data.types << type_idx['int']
351 } else {
352 return error('${@FN}(): `limit` should be a positive integer')
353 }
354 return qb
355}
356
357// offset create a `offset` clause
358pub fn (qb_ &QueryBuilder[T]) offset(offset int) !&QueryBuilder[T] {
359 mut qb := unsafe { qb_ }
360 if offset >= 0 {
361 qb.config.has_offset = true
362 qb.data.data << Primitive(offset)
363 qb.data.types << type_idx['int']
364 } else {
365 return error('${@FN}(): `offset` should be a integer > 0')
366 }
367 return qb
368}
369
370// select create a `select` clause
371pub fn (qb_ &QueryBuilder[T]) select(fields ...string) !&QueryBuilder[T] {
372 mut qb := unsafe { qb_ }
373 for f in fields {
374 if f !in qb.valid_sql_field_names {
375 return error("${@FN}(): table `${qb.config.table}` has no field's name: `${f}`")
376 }
377 }
378 qb.config.fields = fields
379 return qb
380}
381
382// distinct marks the query as `SELECT DISTINCT`.
383pub fn (qb_ &QueryBuilder[T]) distinct() !&QueryBuilder[T] {
384 mut qb := unsafe { qb_ }
385 qb.config.has_distinct = true
386 return qb
387}
388
389// set create a `set` clause for `update`
390pub fn (qb_ &QueryBuilder[T]) set(assign string, values ...Primitive) !&QueryBuilder[T] {
391 mut qb := unsafe { qb_ }
392 if assign.len == 0 {
393 return error('${@FN}(): empty `set`')
394 }
395 required_params := assign.count('?')
396 if required_params != values.len {
397 return error('${@FN}(): `set` requires `${required_params}` params but got `${values.len}`')
398 }
399 mut fields := []string{}
400 assign_splits := assign.split_any(',')
401 for assign_split in assign_splits {
402 f := assign_split.split_any('=')
403 if f.len != 2 {
404 return error('${@FN}(): `set` syntax error, it should look like : `a=?,b=?`')
405 }
406 if f[1].trim_space() != '?' {
407 return error('${@FN}(): `set` syntax error, it should look like : `a=?,b=?`')
408 }
409 field := f[0].trim_space()
410 if field !in qb.valid_sql_field_names {
411 return error("${@FN}(): table `${qb.config.table}` has no field's name: `${field}`")
412 }
413 fields << field
414 }
415 qb.data.fields << fields
416 for v in values {
417 qb.data.data << v
418 }
419 return qb
420}
421
422// table_from_struct get table from struct
423fn table_from_struct[T]() Table {
424 mut table_name := T.name
425 // Strip generic parameters from type name (e.g., Message[Payload] -> Message)
426 if bracket_pos := table_name.index('[') {
427 table_name = table_name[..bracket_pos]
428 }
429 mut has_custom_table_name := false
430 mut attrs := []VAttribute{}
431 $for a in T.attributes {
432 $if a.name == 'table' && a.has_arg {
433 table_name = a.arg
434 has_custom_table_name = true
435 }
436 attrs << a
437 }
438 if !has_custom_table_name {
439 // Keep default ORM table names aligned with unquoted SQL identifiers across DB drivers.
440 table_name = table_name.to_lower()
441 }
442 return Table{
443 name: table_name
444 attrs: attrs
445 }
446}
447
448// struct_meta return a struct's fields info
449fn struct_meta[T]() []TableField {
450 mut meta := []TableField{}
451 $for field in T.fields {
452 if !field.is_embed {
453 mut attrs := []VAttribute{}
454 mut is_skip := false
455 for attr in field.attrs {
456 f := attr.split_any(':')
457 if f.len == 1 {
458 ff := f[0].trim_space()
459 if ff == 'skip' {
460 is_skip = true
461 }
462 attrs << VAttribute{
463 name: ff
464 }
465 continue
466 }
467 if f.len == 2 {
468 ff := f[1].trim_space()
469 if f[0].trim_space() == 'sql' && ff == '-' {
470 is_skip = true
471 }
472 mut kind := AttributeKind.plain
473 if ff == 'true' || ff == 'false' {
474 kind = .bool
475 } else if ff.starts_with('if ') {
476 kind = .comptime_define
477 } else if (ff.starts_with("'") && ff.ends_with("'"))
478 || (ff.starts_with('"') && ff.ends_with('"')) {
479 kind = .string
480 } else if ff.contains_only('0123456789') {
481 kind = .number
482 } else if ff !in ['serial', 'i8', 'i16', 'int', 'i64', 'u8', 'u16', 'u32',
483 'u64', 'f32', 'f64', 'bool', 'string'] {
484 // @[sql: data_type] need kind = .plain
485 // @[sql: column_name] need kind = .string
486 kind = .string
487 }
488 attrs << VAttribute{
489 name: f[0].trim_space()
490 has_arg: true
491 arg: ff
492 kind: kind
493 }
494 }
495 }
496
497 mut field_type := field.typ
498 if typeof(field).name.contains('time.Time') {
499 field_type = time_
500 } else if field.is_struct {
501 field_type = type_idx['int']
502 } else if field.is_enum {
503 field_type = enum_
504 }
505
506 if !is_skip {
507 meta << TableField{
508 name: field.name
509 typ: field_type
510 nullable: field.is_option
511 attrs: attrs
512 }
513 }
514 }
515 }
516 return meta
517}
518
519// map_row map a row result into a struct
520fn (qb &QueryBuilder[T]) map_row(row []Primitive) !T {
521 mut instance := T{}
522
523 $for field in T.fields {
524 mut m := TableField{}
525 mm := qb.meta.filter(it.name == field.name)
526 if mm.len != 0 {
527 m = mm[0]
528 index := qb.config.fields.index(sql_field_name(m))
529 if index >= 0 {
530 value := row[index]
531
532 $if field.typ is $option {
533 if value == Primitive(Null{}) {
534 instance.$(field.name) = none
535 }
536 }
537 if value != Primitive(Null{}) {
538 $if field.unaliased_typ is i8 || field.unaliased_typ is ?i8 {
539 instance.$(field.name) = match value {
540 i8 { i8(value) }
541 i16 { i8(value) }
542 int { i8(value) }
543 i64 { i8(value) }
544 u8 { i8(value) }
545 u16 { i8(value) }
546 u32 { i8(value) }
547 u64 { i8(value) }
548 bool { i8(value) }
549 f32 { i8(value) }
550 f64 { i8(value) }
551 else { 0 }
552 }
553 } $else $if field.unaliased_typ is i16 || field.unaliased_typ is ?i16 {
554 instance.$(field.name) = match value {
555 i8 { i16(value) }
556 i16 { i16(value) }
557 int { i16(value) }
558 i64 { i16(value) }
559 u8 { i16(value) }
560 u16 { i16(value) }
561 u32 { i16(value) }
562 u64 { i16(value) }
563 bool { i16(value) }
564 f32 { i16(value) }
565 f64 { i16(value) }
566 else { 0 }
567 }
568 } $else $if field.unaliased_typ is int || field.unaliased_typ is ?int {
569 instance.$(field.name) = match value {
570 i8 { int(value) }
571 i16 { int(value) }
572 int { int(value) }
573 i64 { int(value) }
574 u8 { int(value) }
575 u16 { int(value) }
576 u32 { int(value) }
577 u64 { int(value) }
578 bool { int(value) }
579 f32 { int(value) }
580 f64 { int(value) }
581 else { 0 }
582 }
583 } $else $if field.unaliased_typ is i64 || field.unaliased_typ is ?i64
584 || field.unaliased_typ is $enum {
585 instance.$(field.name) = match value {
586 i8 { i64(value) }
587 i16 { i64(value) }
588 int { i64(value) }
589 i64 { i64(value) }
590 u8 { i64(value) }
591 u16 { i64(value) }
592 u32 { i64(value) }
593 u64 { i64(value) }
594 bool { i64(value) }
595 f32 { i64(value) }
596 f64 { i64(value) }
597 else { 0 }
598 }
599 } $else $if field.unaliased_typ is u8 || field.unaliased_typ is ?u8 {
600 instance.$(field.name) = match value {
601 i8 { u8(value) }
602 i16 { u8(value) }
603 int { u8(value) }
604 i64 { u8(value) }
605 u8 { u8(value) }
606 u16 { u8(value) }
607 u32 { u8(value) }
608 u64 { u8(value) }
609 bool { u8(value) }
610 f32 { u8(value) }
611 f64 { u8(value) }
612 else { 0 }
613 }
614 } $else $if field.unaliased_typ is u16 || field.unaliased_typ is ?u16 {
615 instance.$(field.name) = match value {
616 i8 { u16(value) }
617 i16 { u16(value) }
618 int { u16(value) }
619 i64 { u16(value) }
620 u8 { u16(value) }
621 u16 { u16(value) }
622 u32 { u16(value) }
623 u64 { u16(value) }
624 bool { u16(value) }
625 f32 { u16(value) }
626 f64 { u16(value) }
627 else { 0 }
628 }
629 } $else $if field.unaliased_typ is u32 || field.unaliased_typ is ?u32 {
630 instance.$(field.name) = match value {
631 i8 { u32(value) }
632 i16 { u32(value) }
633 int { u32(value) }
634 i64 { u32(value) }
635 u8 { u32(value) }
636 u16 { u32(value) }
637 u32 { u32(value) }
638 u64 { u32(value) }
639 bool { u32(value) }
640 f32 { u32(value) }
641 f64 { u32(value) }
642 else { 0 }
643 }
644 } $else $if field.unaliased_typ is u64 || field.unaliased_typ is ?u64 {
645 instance.$(field.name) = match value {
646 i8 { u64(value) }
647 i16 { u64(value) }
648 int { u64(value) }
649 i64 { u64(value) }
650 u8 { u64(value) }
651 u16 { u64(value) }
652 u32 { u64(value) }
653 u64 { u64(value) }
654 bool { u64(value) }
655 f32 { u64(value) }
656 f64 { u64(value) }
657 else { 0 }
658 }
659 } $else $if field.unaliased_typ is f32 || field.unaliased_typ is ?f32 {
660 instance.$(field.name) = match value {
661 i8 { f32(value) }
662 i16 { f32(value) }
663 int { f32(value) }
664 i64 { f32(value) }
665 u8 { f32(value) }
666 u16 { f32(value) }
667 u32 { f32(value) }
668 u64 { f32(value) }
669 bool { f32(value) }
670 f32 { value }
671 f64 { f32(value) }
672 else { 0 }
673 }
674 } $else $if field.unaliased_typ is f64 || field.unaliased_typ is ?f64 {
675 instance.$(field.name) = match value {
676 i8 { f64(value) }
677 i16 { f64(value) }
678 int { f64(value) }
679 i64 { f64(value) }
680 u8 { f64(value) }
681 u16 { f64(value) }
682 u32 { f64(value) }
683 u64 { f64(value) }
684 bool { f64(value) }
685 f32 { f64(value) }
686 f64 { value }
687 else { 0 }
688 }
689 } $else $if field.unaliased_typ is bool || field.unaliased_typ is ?bool {
690 instance.$(field.name) = match value {
691 i8 { value != 0 }
692 i16 { value != 0 }
693 int { value != 0 }
694 i64 { value != 0 }
695 u8 { value != 0 }
696 u16 { value != 0 }
697 u32 { value != 0 }
698 u64 { value != 0 }
699 bool { value }
700 f32 { value != 0 }
701 f64 { value != 0 }
702 else { false }
703 }
704 } $else $if field.unaliased_typ is string || field.unaliased_typ is ?string {
705 instance.$(field.name) = value as string
706 } $else $if field.unaliased_typ is time.Time
707 || field.unaliased_typ is ?time.Time {
708 if m.typ == time_ {
709 instance.$(field.name) = value as time.Time
710 } else if m.typ == type_string {
711 instance.$(field.name) = time.parse(value as string)!
712 }
713 }
714 }
715 }
716 }
717 }
718 return instance
719}
720
721// prepare QueryBuilder, ready for gen SQL
722fn (qb_ &QueryBuilder[T]) prepare() ! {
723 mut qb := unsafe { qb_ }
724
725 // check for mismatch `(` and `)`
726 for p in qb.where.parentheses {
727 if p[1] == -1 {
728 return error('${@FN}(): missing `)`')
729 }
730 }
731
732 // auto fill field's names if not set by `select`
733 if qb.config.fields.len == 0 {
734 qb.config.fields = qb.meta.map(sql_field_name(it))
735 }
736
737 if qb.config.select_exprs.len != qb.config.fields.len {
738 mut select_exprs := []string{cap: qb.config.fields.len}
739 for f in qb.config.fields {
740 mut select_expr := f
741 for ff in qb.meta {
742 if sql_field_name(ff) == f {
743 select_expr = sql_field_select_expr(ff)
744 break
745 }
746 }
747 select_exprs << select_expr
748 }
749 qb.config.select_exprs = select_exprs
750 }
751
752 if qb.config.types.len == 0 {
753 // set field's types
754 mut types := []int{cap: qb.config.fields.len}
755 for f in qb.config.fields {
756 for ff in qb.meta {
757 if sql_field_name(ff) == f {
758 types << ff.typ
759 }
760 }
761 }
762 qb.config.types = types
763 }
764}
765
766fn (qb &QueryBuilder[T]) get_meta_field_by_sql_name(field string) ?TableField {
767 for meta_field in qb.meta {
768 if sql_field_name(meta_field) == field {
769 return meta_field
770 }
771 }
772 return none
773}
774
775fn is_numeric_type_idx(typ int) bool {
776 return typ in nums || typ in num64 || typ in float
777}
778
779fn is_min_max_supported_type_idx(typ int) bool {
780 return is_numeric_type_idx(typ) || typ == type_string || typ == time_
781}
782
783fn (qb &QueryBuilder[T]) validate_aggregate_field(kind AggregateKind, field string) !TableField {
784 meta_field := qb.get_meta_field_by_sql_name(field) or {
785 return error("${@FN}(): table `${qb.config.table}` has no field's name: `${field}`")
786 }
787 match kind {
788 .sum, .avg {
789 if !is_numeric_type_idx(meta_field.typ) {
790 msg := match kind {
791 .sum { '${@FN}(): `sum` requires a numeric field' }
792 .avg { '${@FN}(): `avg` requires a numeric field' }
793 else { '${@FN}(): aggregate requires a numeric field' }
794 }
795
796 return error(msg)
797 }
798 }
799 .min, .max {
800 if !is_min_max_supported_type_idx(meta_field.typ) {
801 msg := match kind {
802 .min { '${@FN}(): `min` requires a numeric, string, or time.Time field' }
803 .max { '${@FN}(): `max` requires a numeric, string, or time.Time field' }
804 else { '${@FN}(): aggregate requires a numeric, string, or time.Time field' }
805 }
806
807 return error(msg)
808 }
809 }
810 else {}
811 }
812
813 return meta_field
814}
815
816fn (qb &QueryBuilder[T]) build_aggregate_config(kind AggregateKind, field string) !SelectConfig {
817 mut cfg := qb.config
818 cfg.aggregate_kind = kind
819 cfg.aggregate_field = ''
820 cfg.fields = []
821 cfg.types = []
822 if kind == .count {
823 cfg.types = [type_idx['int']]
824 return cfg
825 }
826
827 meta_field := qb.validate_aggregate_field(kind, field)!
828 cfg.aggregate_field = field
829 cfg.fields = [field]
830 cfg.types = [if kind == .avg { type_idx['f64'] } else { meta_field.typ }]
831 return cfg
832}
833
834fn primitive_to_aggregate_value(value Primitive) AggregateValue {
835 return if value == Primitive(Null{}) {
836 AggregateValue{}
837 } else {
838 AggregateValue{
839 has_value: true
840 value: value
841 }
842 }
843}
844
845// as_int returns the aggregate value as `int`, or `none` when it is null or not numeric.
846pub fn (value AggregateValue) as_int() ?int {
847 if !value.has_value {
848 return none
849 }
850 return match value.value {
851 i8 { int(value.value) }
852 i16 { int(value.value) }
853 int { value.value }
854 i64 { int(value.value) }
855 u8 { int(value.value) }
856 u16 { int(value.value) }
857 u32 { int(value.value) }
858 u64 { int(value.value) }
859 f32 { int(value.value) }
860 f64 { int(value.value) }
861 else { return none }
862 }
863}
864
865// as_f64 returns the aggregate value as `f64`, or `none` when it is null or not numeric.
866pub fn (value AggregateValue) as_f64() ?f64 {
867 if !value.has_value {
868 return none
869 }
870 return match value.value {
871 i8 { f64(value.value) }
872 i16 { f64(value.value) }
873 int { f64(value.value) }
874 i64 { f64(value.value) }
875 u8 { f64(value.value) }
876 u16 { f64(value.value) }
877 u32 { f64(value.value) }
878 u64 { f64(value.value) }
879 f32 { f64(value.value) }
880 f64 { value.value }
881 else { return none }
882 }
883}
884
885// as_string returns the aggregate value as `string`, or `none` when it is null or not a string.
886pub fn (value AggregateValue) as_string() ?string {
887 if !value.has_value {
888 return none
889 }
890 return match value.value {
891 string { value.value }
892 else { return none }
893 }
894}
895
896// as_time returns the aggregate value as `time.Time`, or `none` when it is null or not a time.
897pub fn (value AggregateValue) as_time() ?time.Time {
898 if !value.has_value {
899 return none
900 }
901 return match value.value {
902 time.Time { value.value }
903 else { return none }
904 }
905}
906
907// query start a query and return result in struct `T`
908pub fn (qb_ &QueryBuilder[T]) query() ![]T {
909 mut qb := unsafe { qb_ }
910 defer {
911 qb.reset()
912 }
913 qb.prepare()!
914 rows := qb.conn.select(qb.config, qb.data, qb.where)!
915 mut result := []T{cap: rows.len}
916 for row in rows {
917 result << qb.map_row[T](row)!
918 }
919 return result
920}
921
922// count start a count query and return result
923pub fn (qb_ &QueryBuilder[T]) count() !int {
924 mut qb := unsafe { qb_ }
925 defer {
926 qb.reset()
927 }
928 qb.prepare()!
929 count_config := qb.build_aggregate_config(.count, '')!
930 result := qb.conn.select(count_config, qb.data, qb.where)!
931
932 if result.len == 0 || result[0].len == 0 {
933 return 0
934 }
935 count_val := result[0][0]
936 return match count_val {
937 int { count_val }
938 i64 { int(count_val) }
939 u64 { int(count_val) }
940 else { return error('${@FN}(): invalid count result type') }
941 }
942}
943
944// sum returns the sum of the field values as an `AggregateValue`.
945pub fn (qb_ &QueryBuilder[T]) sum(field string) !AggregateValue {
946 mut qb := unsafe { qb_ }
947 defer {
948 qb.reset()
949 }
950 qb.prepare()!
951 qb.validate_aggregate_field(.sum, field)!
952 cfg := qb.build_aggregate_config(.sum, field)!
953 result := qb.conn.select(cfg, qb.data, qb.where)!
954 if result.len == 0 || result[0].len == 0 {
955 return AggregateValue{}
956 }
957 return primitive_to_aggregate_value(result[0][0])
958}
959
960// min returns the smallest field value as an `AggregateValue`.
961pub fn (qb_ &QueryBuilder[T]) min(field string) !AggregateValue {
962 mut qb := unsafe { qb_ }
963 defer {
964 qb.reset()
965 }
966 qb.prepare()!
967 qb.validate_aggregate_field(.min, field)!
968 cfg := qb.build_aggregate_config(.min, field)!
969 result := qb.conn.select(cfg, qb.data, qb.where)!
970 if result.len == 0 || result[0].len == 0 {
971 return AggregateValue{}
972 }
973 return primitive_to_aggregate_value(result[0][0])
974}
975
976// max returns the largest field value as an `AggregateValue`.
977pub fn (qb_ &QueryBuilder[T]) max(field string) !AggregateValue {
978 mut qb := unsafe { qb_ }
979 defer {
980 qb.reset()
981 }
982 qb.prepare()!
983 qb.validate_aggregate_field(.max, field)!
984 cfg := qb.build_aggregate_config(.max, field)!
985 result := qb.conn.select(cfg, qb.data, qb.where)!
986 if result.len == 0 || result[0].len == 0 {
987 return AggregateValue{}
988 }
989 return primitive_to_aggregate_value(result[0][0])
990}
991
992// avg returns the average field value as an `AggregateValue`.
993pub fn (qb_ &QueryBuilder[T]) avg(field string) !AggregateValue {
994 mut qb := unsafe { qb_ }
995 defer {
996 qb.reset()
997 }
998 qb.prepare()!
999 qb.validate_aggregate_field(.avg, field)!
1000 cfg := qb.build_aggregate_config(.avg, field)!
1001 result := qb.conn.select(cfg, qb.data, qb.where)!
1002 if result.len == 0 || result[0].len == 0 {
1003 return AggregateValue{}
1004 }
1005 return primitive_to_aggregate_value(result[0][0])
1006}
1007
1008// insert insert a record into the database
1009pub fn (qb_ &QueryBuilder[T]) insert[T](value T) !&QueryBuilder[T] {
1010 mut qb := unsafe { qb_ }
1011 defer {
1012 qb.reset()
1013 }
1014 qb.insert_many([value])!
1015 return qb
1016}
1017
1018// insert_many insert records into the database
1019// Uses batch INSERT for efficiency when inserting multiple records.
1020pub fn (qb_ &QueryBuilder[T]) insert_many[T](values []T) !&QueryBuilder[T] {
1021 mut qb := unsafe { qb_ }
1022 defer {
1023 qb.reset()
1024 }
1025 qb.prepare()!
1026 if values.len == 0 {
1027 return error('${@FN}(): `insert` need at least one record')
1028 }
1029 mut batch := fill_data_with_struct[T](values[0], qb.meta)
1030 batch.batch_rows = values.len
1031 for i in 1 .. values.len {
1032 next := fill_data_with_struct[T](values[i], qb.meta)
1033 for d in next.data {
1034 batch.data << d
1035 }
1036 }
1037 qb.conn.insert(qb.config.table, batch)!
1038 return qb
1039}
1040
1041// save updates all mapped fields in `value` using the struct primary key or `id` field.
1042pub fn save[T](conn Connection, value T) ! {
1043 mut qb := new_query[T](conn)
1044 data, where := build_save_query_data[T](qb.meta, qb.config.table.name, value)!
1045 qb.conn.update(qb.config.table, data, where)!
1046}
1047
1048fn build_save_query_data[T](meta []TableField, table_name string, value T) !(QueryData, QueryData) {
1049 data := fill_data_with_struct[T](value, meta)
1050 if data.fields.len != data.data.len {
1051 return error('${@FN}(): table `${table_name}` contains fields that `save` cannot map automatically')
1052 }
1053 primary_field_name := find_save_primary_field_name(meta) or {
1054 return error('${@FN}(): table `${table_name}` needs a primary key or `id` field to use `save`')
1055 }
1056 mut update_data := QueryData{}
1057 mut where_data := QueryData{
1058 kinds: [.eq]
1059 }
1060 for i, field_name in data.fields {
1061 if field_name == primary_field_name {
1062 where_data.fields << field_name
1063 where_data.data << data.data[i]
1064 continue
1065 }
1066 update_data.fields << field_name
1067 update_data.data << data.data[i]
1068 }
1069 if where_data.fields.len == 0 {
1070 return error('${@FN}(): struct value is missing the primary key field `${primary_field_name}`')
1071 }
1072 if update_data.fields.len == 0 {
1073 return error('${@FN}(): no updatable fields were found for table `${table_name}`')
1074 }
1075 return update_data, where_data
1076}
1077
1078fn find_save_primary_field_name(meta []TableField) ?string {
1079 for field in meta {
1080 for attr in field.attrs {
1081 if attr_name_matches(attr.name, 'primary') {
1082 return sql_field_name(field)
1083 }
1084 }
1085 }
1086 for field in meta {
1087 field_name := sql_field_name(field)
1088 if field.name == 'id' || field_name == 'id' {
1089 return field_name
1090 }
1091 }
1092 return none
1093}
1094
1095fn fill_data_with_struct[T](value T, meta []TableField) QueryData {
1096 mut qb := QueryData{}
1097 $for field in T.fields {
1098 sql_fields := meta.filter(it.name == field.name)
1099 if sql_fields.len == 1 {
1100 sql_f := sql_fields[0]
1101 sql_f_name := sql_field_name(sql_f)
1102 sql_f_type := sql_field_type(sql_f)
1103
1104 if sql_f_type == serial {
1105 // `serial` should be auto field
1106 qb.auto_fields << qb.fields.len
1107 }
1108 qb.fields << sql_f_name
1109
1110 $if field.unaliased_typ is bool {
1111 qb.data << bool_to_primitive(bool(value.$(field.name)))
1112 } $else $if field.unaliased_typ is ?bool {
1113 qb.data << option_bool_to_primitive(value.$(field.name))
1114 }
1115 $if field.unaliased_typ is f32 {
1116 qb.data << f32_to_primitive(f32(value.$(field.name)))
1117 } $else $if field.unaliased_typ is ?f32 {
1118 qb.data << option_f32_to_primitive(value.$(field.name))
1119 }
1120 $if field.unaliased_typ is f64 {
1121 qb.data << f64_to_primitive(f64(value.$(field.name)))
1122 } $else $if field.unaliased_typ is ?f64 {
1123 qb.data << option_f64_to_primitive(value.$(field.name))
1124 }
1125 $if field.unaliased_typ is i8 {
1126 qb.data << i8_to_primitive(i8(value.$(field.name)))
1127 } $else $if field.unaliased_typ is ?i8 {
1128 qb.data << option_i8_to_primitive(value.$(field.name))
1129 }
1130 $if field.unaliased_typ is i16 {
1131 qb.data << i16_to_primitive(i16(value.$(field.name)))
1132 } $else $if field.unaliased_typ is ?i16 {
1133 qb.data << option_i16_to_primitive(value.$(field.name))
1134 }
1135 $if field.unaliased_typ is int {
1136 qb.data << int_to_primitive(int(value.$(field.name)))
1137 } $else $if field.unaliased_typ is ?int {
1138 qb.data << option_int_to_primitive(value.$(field.name))
1139 }
1140 $if field.unaliased_typ is i64 {
1141 qb.data << i64_to_primitive(i64(value.$(field.name)))
1142 } $else $if field.unaliased_typ is ?i64 {
1143 qb.data << option_i64_to_primitive(value.$(field.name))
1144 }
1145 $if field.unaliased_typ is u8 {
1146 qb.data << u8_to_primitive(u8(value.$(field.name)))
1147 } $else $if field.unaliased_typ is ?u8 {
1148 qb.data << option_u8_to_primitive(value.$(field.name))
1149 }
1150 $if field.unaliased_typ is u16 {
1151 qb.data << u16_to_primitive(u16(value.$(field.name)))
1152 } $else $if field.unaliased_typ is ?u16 {
1153 qb.data << option_u16_to_primitive(value.$(field.name))
1154 }
1155 $if field.unaliased_typ is u32 {
1156 qb.data << u32_to_primitive(u32(value.$(field.name)))
1157 } $else $if field.unaliased_typ is ?u32 {
1158 qb.data << option_u32_to_primitive(value.$(field.name))
1159 }
1160 $if field.unaliased_typ is u64 {
1161 qb.data << u64_to_primitive(u64(value.$(field.name)))
1162 } $else $if field.unaliased_typ is ?u64 {
1163 qb.data << option_u64_to_primitive(value.$(field.name))
1164 }
1165 $if field.unaliased_typ is string {
1166 qb.data << string_to_primitive(string(value.$(field.name)))
1167 } $else $if field.unaliased_typ is ?string {
1168 qb.data << option_string_to_primitive(value.$(field.name))
1169 } $else $if field.unaliased_typ is time.Time {
1170 if sql_f_type == type_string {
1171 qb.data << string_to_primitive(value.$(field.name).format_ss())
1172 } else {
1173 qb.data << time_to_primitive(value.$(field.name))
1174 }
1175 } $else $if field.unaliased_typ is ?time.Time {
1176 if sql_f_type == type_string {
1177 b := value.$(field.name)
1178 if b_ := b {
1179 qb.data << Primitive(b_.format_ss())
1180 } else {
1181 qb.data << null_primitive
1182 }
1183 } else {
1184 qb.data << option_time_to_primitive(value.$(field.name))
1185 }
1186 } $else $if field.unaliased_typ is $enum {
1187 qb.data << i64_to_primitive(i64(value.$(field.name)))
1188 }
1189 }
1190 }
1191 return qb
1192}
1193
1194// update update record(s) in the database
1195pub fn (qb_ &QueryBuilder[T]) update() !&QueryBuilder[T] {
1196 mut qb := unsafe { qb_ }
1197 defer {
1198 qb.reset()
1199 }
1200 qb.prepare()!
1201 if qb.data.fields.len == 0 {
1202 return error('${@FN}(): `update` need at least one `set` clause')
1203 }
1204 qb.conn.update(qb.config.table, qb.data, qb.where)!
1205 return qb
1206}
1207
1208// update_many updates multiple records by a key field, using batch CASE WHEN for efficiency.
1209// key_field is the column used to match rows (e.g. 'id').
1210// field_names selects which columns to update; if empty, all struct fields except key_field are updated.
1211pub fn update_many[T](mut conn Connection, values []T, key_field string, field_names ...string) ! {
1212 if values.len == 0 {
1213 return error('${@FN}(): need at least one record')
1214 }
1215 mut qb := new_query[T](conn)
1216
1217 // Build the field list from the first value
1218 first := fill_data_with_struct[T](values[0], qb.meta)
1219 mut key_index := -1
1220 mut value_fields := []string{}
1221 mut value_indexes := []int{}
1222
1223 for i, field in first.fields {
1224 if field == key_field {
1225 key_index = i
1226 } else if field_names.len == 0 || field in field_names {
1227 value_fields << field
1228 value_indexes << i
1229 }
1230 }
1231
1232 if key_index < 0 {
1233 return error('${@FN}(): key field `${key_field}` not found in table `${qb.config.table.name}`')
1234 }
1235
1236 if value_fields.len == 0 {
1237 return error('${@FN}(): no updatable fields found for table `${qb.config.table.name}`')
1238 }
1239
1240 mut update_data := QueryData{
1241 fields: value_fields
1242 batch_rows: values.len
1243 batch_key: key_field
1244 }
1245
1246 // Build data: per value_field, per row: [key_value, value_field_value]
1247 rows := values.map(fill_data_with_struct[T](it, qb.meta))
1248
1249 for fj in value_indexes {
1250 for row in rows {
1251 if key_index < row.data.len {
1252 update_data.data << row.data[key_index]
1253 }
1254 if fj < row.data.len {
1255 update_data.data << row.data[fj]
1256 }
1257 }
1258 }
1259
1260 // Build WHERE clause using IN
1261 mut key_values := []Primitive{}
1262 for row in rows {
1263 if key_index < row.data.len {
1264 key_values << row.data[key_index]
1265 }
1266 }
1267
1268 mut where_data := QueryData{
1269 fields: [key_field]
1270 data: [Primitive(key_values)]
1271 kinds: [.in]
1272 }
1273
1274 conn.update(qb.config.table, update_data, where_data)!
1275}
1276
1277// delete delete record(s) in the database
1278pub fn (qb_ &QueryBuilder[T]) delete() !&QueryBuilder[T] {
1279 mut qb := unsafe { qb_ }
1280 defer {
1281 qb.reset()
1282 }
1283 qb.prepare()!
1284 qb.conn.delete(qb.config.table, qb.where)!
1285 return qb
1286}
1287
1288// create create a table
1289pub fn (qb_ &QueryBuilder[T]) create() !&QueryBuilder[T] {
1290 mut qb := unsafe { qb_ }
1291 defer {
1292 qb.reset()
1293 }
1294 qb.conn.create(qb.config.table, qb.meta)!
1295 return qb
1296}
1297
1298// drop drop a table
1299pub fn (qb_ &QueryBuilder[T]) drop() !&QueryBuilder[T] {
1300 mut qb := unsafe { qb_ }
1301 defer {
1302 qb.reset()
1303 }
1304 qb.conn.drop(qb.config.table)!
1305 return qb
1306}
1307
1308// last_id returns the last inserted id of the db
1309pub fn (qb_ &QueryBuilder[T]) last_id() int {
1310 mut qb := unsafe { qb_ }
1311 qb.reset()
1312 return qb.conn.last_id()
1313}
1314