v / vlib / orm / orm_func.v
1315 lines · 1232 sloc · 34.48 KB · 7b724e98ac187e233bd03716901c29f5e00f815f
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](meta)
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](meta []TableField) 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 fields: meta.map(it.name)
446 columns: meta.map(sql_field_name(it))
447 }
448}
449
450// struct_meta return a struct's fields info
451fn struct_meta[T]() []TableField {
452 mut meta := []TableField{}
453 $for field in T.fields {
454 if !field.is_embed {
455 mut attrs := []VAttribute{}
456 mut is_skip := false
457 for attr in field.attrs {
458 f := attr.split_any(':')
459 if f.len == 1 {
460 ff := f[0].trim_space()
461 if ff == 'skip' {
462 is_skip = true
463 }
464 attrs << VAttribute{
465 name: ff
466 }
467 continue
468 }
469 if f.len == 2 {
470 ff := f[1].trim_space()
471 if f[0].trim_space() == 'sql' && ff == '-' {
472 is_skip = true
473 }
474 mut kind := AttributeKind.plain
475 if ff == 'true' || ff == 'false' {
476 kind = .bool
477 } else if ff.starts_with('if ') {
478 kind = .comptime_define
479 } else if (ff.starts_with("'") && ff.ends_with("'"))
480 || (ff.starts_with('"') && ff.ends_with('"')) {
481 kind = .string
482 } else if ff.contains_only('0123456789') {
483 kind = .number
484 } else if ff !in ['serial', 'i8', 'i16', 'int', 'i64', 'u8', 'u16', 'u32',
485 'u64', 'f32', 'f64', 'bool', 'string'] {
486 // @[sql: data_type] need kind = .plain
487 // @[sql: column_name] need kind = .string
488 kind = .string
489 }
490 attrs << VAttribute{
491 name: f[0].trim_space()
492 has_arg: true
493 arg: ff
494 kind: kind
495 }
496 }
497 }
498
499 mut field_type := field.typ
500 if typeof(field).name.contains('time.Time') {
501 field_type = time_
502 } else if field.is_struct {
503 field_type = type_idx['int']
504 } else if field.is_enum {
505 field_type = enum_
506 }
507
508 if !is_skip {
509 meta << TableField{
510 name: field.name
511 typ: field_type
512 nullable: field.is_option
513 attrs: attrs
514 }
515 }
516 }
517 }
518 return meta
519}
520
521// map_row map a row result into a struct
522fn (qb &QueryBuilder[T]) map_row(row []Primitive) !T {
523 mut instance := T{}
524
525 $for field in T.fields {
526 mut m := TableField{}
527 mm := qb.meta.filter(it.name == field.name)
528 if mm.len != 0 {
529 m = mm[0]
530 index := qb.config.fields.index(sql_field_name(m))
531 if index >= 0 {
532 value := row[index]
533
534 $if field.typ is $option {
535 if value == Primitive(Null{}) {
536 instance.$(field.name) = none
537 }
538 }
539 if value != Primitive(Null{}) {
540 $if field.unaliased_typ is i8 || field.unaliased_typ is ?i8 {
541 instance.$(field.name) = match value {
542 i8 { i8(value) }
543 i16 { i8(value) }
544 int { i8(value) }
545 i64 { i8(value) }
546 u8 { i8(value) }
547 u16 { i8(value) }
548 u32 { i8(value) }
549 u64 { i8(value) }
550 bool { i8(value) }
551 f32 { i8(value) }
552 f64 { i8(value) }
553 else { 0 }
554 }
555 } $else $if field.unaliased_typ is i16 || field.unaliased_typ is ?i16 {
556 instance.$(field.name) = match value {
557 i8 { i16(value) }
558 i16 { i16(value) }
559 int { i16(value) }
560 i64 { i16(value) }
561 u8 { i16(value) }
562 u16 { i16(value) }
563 u32 { i16(value) }
564 u64 { i16(value) }
565 bool { i16(value) }
566 f32 { i16(value) }
567 f64 { i16(value) }
568 else { 0 }
569 }
570 } $else $if field.unaliased_typ is int || field.unaliased_typ is ?int {
571 instance.$(field.name) = match value {
572 i8 { int(value) }
573 i16 { int(value) }
574 int { int(value) }
575 i64 { int(value) }
576 u8 { int(value) }
577 u16 { int(value) }
578 u32 { int(value) }
579 u64 { int(value) }
580 bool { int(value) }
581 f32 { int(value) }
582 f64 { int(value) }
583 else { 0 }
584 }
585 } $else $if field.unaliased_typ is i64 || field.unaliased_typ is ?i64
586 || field.unaliased_typ is $enum {
587 instance.$(field.name) = match value {
588 i8 { i64(value) }
589 i16 { i64(value) }
590 int { i64(value) }
591 i64 { i64(value) }
592 u8 { i64(value) }
593 u16 { i64(value) }
594 u32 { i64(value) }
595 u64 { i64(value) }
596 bool { i64(value) }
597 f32 { i64(value) }
598 f64 { i64(value) }
599 else { 0 }
600 }
601 } $else $if field.unaliased_typ is u8 || field.unaliased_typ is ?u8 {
602 instance.$(field.name) = match value {
603 i8 { u8(value) }
604 i16 { u8(value) }
605 int { u8(value) }
606 i64 { u8(value) }
607 u8 { u8(value) }
608 u16 { u8(value) }
609 u32 { u8(value) }
610 u64 { u8(value) }
611 bool { u8(value) }
612 f32 { u8(value) }
613 f64 { u8(value) }
614 else { 0 }
615 }
616 } $else $if field.unaliased_typ is u16 || field.unaliased_typ is ?u16 {
617 instance.$(field.name) = match value {
618 i8 { u16(value) }
619 i16 { u16(value) }
620 int { u16(value) }
621 i64 { u16(value) }
622 u8 { u16(value) }
623 u16 { u16(value) }
624 u32 { u16(value) }
625 u64 { u16(value) }
626 bool { u16(value) }
627 f32 { u16(value) }
628 f64 { u16(value) }
629 else { 0 }
630 }
631 } $else $if field.unaliased_typ is u32 || field.unaliased_typ is ?u32 {
632 instance.$(field.name) = match value {
633 i8 { u32(value) }
634 i16 { u32(value) }
635 int { u32(value) }
636 i64 { u32(value) }
637 u8 { u32(value) }
638 u16 { u32(value) }
639 u32 { u32(value) }
640 u64 { u32(value) }
641 bool { u32(value) }
642 f32 { u32(value) }
643 f64 { u32(value) }
644 else { 0 }
645 }
646 } $else $if field.unaliased_typ is u64 || field.unaliased_typ is ?u64 {
647 instance.$(field.name) = match value {
648 i8 { u64(value) }
649 i16 { u64(value) }
650 int { u64(value) }
651 i64 { u64(value) }
652 u8 { u64(value) }
653 u16 { u64(value) }
654 u32 { u64(value) }
655 u64 { u64(value) }
656 bool { u64(value) }
657 f32 { u64(value) }
658 f64 { u64(value) }
659 else { 0 }
660 }
661 } $else $if field.unaliased_typ is f32 || field.unaliased_typ is ?f32 {
662 instance.$(field.name) = match value {
663 i8 { f32(value) }
664 i16 { f32(value) }
665 int { f32(value) }
666 i64 { f32(value) }
667 u8 { f32(value) }
668 u16 { f32(value) }
669 u32 { f32(value) }
670 u64 { f32(value) }
671 bool { f32(value) }
672 f32 { value }
673 f64 { f32(value) }
674 else { 0 }
675 }
676 } $else $if field.unaliased_typ is f64 || field.unaliased_typ is ?f64 {
677 instance.$(field.name) = match value {
678 i8 { f64(value) }
679 i16 { f64(value) }
680 int { f64(value) }
681 i64 { f64(value) }
682 u8 { f64(value) }
683 u16 { f64(value) }
684 u32 { f64(value) }
685 u64 { f64(value) }
686 bool { f64(value) }
687 f32 { f64(value) }
688 f64 { value }
689 else { 0 }
690 }
691 } $else $if field.unaliased_typ is bool || field.unaliased_typ is ?bool {
692 instance.$(field.name) = match value {
693 i8 { value != 0 }
694 i16 { value != 0 }
695 int { value != 0 }
696 i64 { value != 0 }
697 u8 { value != 0 }
698 u16 { value != 0 }
699 u32 { value != 0 }
700 u64 { value != 0 }
701 bool { value }
702 f32 { value != 0 }
703 f64 { value != 0 }
704 else { false }
705 }
706 } $else $if field.unaliased_typ is string || field.unaliased_typ is ?string {
707 instance.$(field.name) = value as string
708 } $else $if field.unaliased_typ is time.Time
709 || field.unaliased_typ is ?time.Time {
710 if m.typ == time_ {
711 instance.$(field.name) = value as time.Time
712 } else if m.typ == type_string {
713 instance.$(field.name) = time.parse(value as string)!
714 }
715 }
716 }
717 }
718 }
719 }
720 return instance
721}
722
723// prepare QueryBuilder, ready for gen SQL
724fn (qb_ &QueryBuilder[T]) prepare() ! {
725 mut qb := unsafe { qb_ }
726
727 // check for mismatch `(` and `)`
728 for p in qb.where.parentheses {
729 if p[1] == -1 {
730 return error('${@FN}(): missing `)`')
731 }
732 }
733
734 // auto fill field's names if not set by `select`
735 if qb.config.fields.len == 0 {
736 qb.config.fields = qb.meta.map(sql_field_name(it))
737 }
738
739 if qb.config.select_exprs.len != qb.config.fields.len {
740 mut select_exprs := []string{cap: qb.config.fields.len}
741 for f in qb.config.fields {
742 mut select_expr := f
743 for ff in qb.meta {
744 if sql_field_name(ff) == f {
745 select_expr = sql_field_select_expr(ff)
746 break
747 }
748 }
749 select_exprs << select_expr
750 }
751 qb.config.select_exprs = select_exprs
752 }
753
754 if qb.config.types.len == 0 {
755 // set field's types
756 mut types := []int{cap: qb.config.fields.len}
757 for f in qb.config.fields {
758 for ff in qb.meta {
759 if sql_field_name(ff) == f {
760 types << ff.typ
761 }
762 }
763 }
764 qb.config.types = types
765 }
766}
767
768fn (qb &QueryBuilder[T]) get_meta_field_by_sql_name(field string) ?TableField {
769 for meta_field in qb.meta {
770 if sql_field_name(meta_field) == field {
771 return meta_field
772 }
773 }
774 return none
775}
776
777fn is_numeric_type_idx(typ int) bool {
778 return typ in nums || typ in num64 || typ in float
779}
780
781fn is_min_max_supported_type_idx(typ int) bool {
782 return is_numeric_type_idx(typ) || typ == type_string || typ == time_
783}
784
785fn (qb &QueryBuilder[T]) validate_aggregate_field(kind AggregateKind, field string) !TableField {
786 meta_field := qb.get_meta_field_by_sql_name(field) or {
787 return error("${@FN}(): table `${qb.config.table}` has no field's name: `${field}`")
788 }
789 match kind {
790 .sum, .avg {
791 if !is_numeric_type_idx(meta_field.typ) {
792 msg := match kind {
793 .sum { '${@FN}(): `sum` requires a numeric field' }
794 .avg { '${@FN}(): `avg` requires a numeric field' }
795 else { '${@FN}(): aggregate requires a numeric field' }
796 }
797
798 return error(msg)
799 }
800 }
801 .min, .max {
802 if !is_min_max_supported_type_idx(meta_field.typ) {
803 msg := match kind {
804 .min { '${@FN}(): `min` requires a numeric, string, or time.Time field' }
805 .max { '${@FN}(): `max` requires a numeric, string, or time.Time field' }
806 else { '${@FN}(): aggregate requires a numeric, string, or time.Time field' }
807 }
808
809 return error(msg)
810 }
811 }
812 else {}
813 }
814
815 return meta_field
816}
817
818fn (qb &QueryBuilder[T]) build_aggregate_config(kind AggregateKind, field string) !SelectConfig {
819 mut cfg := qb.config
820 cfg.aggregate_kind = kind
821 cfg.aggregate_field = ''
822 cfg.fields = []
823 cfg.types = []
824 if kind == .count {
825 cfg.types = [type_idx['int']]
826 return cfg
827 }
828
829 meta_field := qb.validate_aggregate_field(kind, field)!
830 cfg.aggregate_field = field
831 cfg.fields = [field]
832 cfg.types = [if kind == .avg { type_idx['f64'] } else { meta_field.typ }]
833 return cfg
834}
835
836fn primitive_to_aggregate_value(value Primitive) AggregateValue {
837 return if value == Primitive(Null{}) {
838 AggregateValue{}
839 } else {
840 AggregateValue{
841 has_value: true
842 value: value
843 }
844 }
845}
846
847// as_int returns the aggregate value as `int`, or `none` when it is null or not numeric.
848pub fn (value AggregateValue) as_int() ?int {
849 if !value.has_value {
850 return none
851 }
852 return match value.value {
853 i8 { int(value.value) }
854 i16 { int(value.value) }
855 int { value.value }
856 i64 { int(value.value) }
857 u8 { int(value.value) }
858 u16 { int(value.value) }
859 u32 { int(value.value) }
860 u64 { int(value.value) }
861 f32 { int(value.value) }
862 f64 { int(value.value) }
863 else { return none }
864 }
865}
866
867// as_f64 returns the aggregate value as `f64`, or `none` when it is null or not numeric.
868pub fn (value AggregateValue) as_f64() ?f64 {
869 if !value.has_value {
870 return none
871 }
872 return match value.value {
873 i8 { f64(value.value) }
874 i16 { f64(value.value) }
875 int { f64(value.value) }
876 i64 { f64(value.value) }
877 u8 { f64(value.value) }
878 u16 { f64(value.value) }
879 u32 { f64(value.value) }
880 u64 { f64(value.value) }
881 f32 { f64(value.value) }
882 f64 { value.value }
883 else { return none }
884 }
885}
886
887// as_string returns the aggregate value as `string`, or `none` when it is null or not a string.
888pub fn (value AggregateValue) as_string() ?string {
889 if !value.has_value {
890 return none
891 }
892 return match value.value {
893 string { value.value }
894 else { return none }
895 }
896}
897
898// as_time returns the aggregate value as `time.Time`, or `none` when it is null or not a time.
899pub fn (value AggregateValue) as_time() ?time.Time {
900 if !value.has_value {
901 return none
902 }
903 return match value.value {
904 time.Time { value.value }
905 else { return none }
906 }
907}
908
909// query start a query and return result in struct `T`
910pub fn (qb_ &QueryBuilder[T]) query() ![]T {
911 mut qb := unsafe { qb_ }
912 defer {
913 qb.reset()
914 }
915 qb.prepare()!
916 rows := qb.conn.select(qb.config, qb.data, qb.where)!
917 mut result := []T{cap: rows.len}
918 for row in rows {
919 result << qb.map_row[T](row)!
920 }
921 return result
922}
923
924// count start a count query and return result
925pub fn (qb_ &QueryBuilder[T]) count() !int {
926 mut qb := unsafe { qb_ }
927 defer {
928 qb.reset()
929 }
930 qb.prepare()!
931 count_config := qb.build_aggregate_config(.count, '')!
932 result := qb.conn.select(count_config, qb.data, qb.where)!
933
934 if result.len == 0 || result[0].len == 0 {
935 return 0
936 }
937 count_val := result[0][0]
938 return match count_val {
939 int { count_val }
940 i64 { int(count_val) }
941 u64 { int(count_val) }
942 else { return error('${@FN}(): invalid count result type') }
943 }
944}
945
946// sum returns the sum of the field values as an `AggregateValue`.
947pub fn (qb_ &QueryBuilder[T]) sum(field string) !AggregateValue {
948 mut qb := unsafe { qb_ }
949 defer {
950 qb.reset()
951 }
952 qb.prepare()!
953 qb.validate_aggregate_field(.sum, field)!
954 cfg := qb.build_aggregate_config(.sum, field)!
955 result := qb.conn.select(cfg, qb.data, qb.where)!
956 if result.len == 0 || result[0].len == 0 {
957 return AggregateValue{}
958 }
959 return primitive_to_aggregate_value(result[0][0])
960}
961
962// min returns the smallest field value as an `AggregateValue`.
963pub fn (qb_ &QueryBuilder[T]) min(field string) !AggregateValue {
964 mut qb := unsafe { qb_ }
965 defer {
966 qb.reset()
967 }
968 qb.prepare()!
969 qb.validate_aggregate_field(.min, field)!
970 cfg := qb.build_aggregate_config(.min, field)!
971 result := qb.conn.select(cfg, qb.data, qb.where)!
972 if result.len == 0 || result[0].len == 0 {
973 return AggregateValue{}
974 }
975 return primitive_to_aggregate_value(result[0][0])
976}
977
978// max returns the largest field value as an `AggregateValue`.
979pub fn (qb_ &QueryBuilder[T]) max(field string) !AggregateValue {
980 mut qb := unsafe { qb_ }
981 defer {
982 qb.reset()
983 }
984 qb.prepare()!
985 qb.validate_aggregate_field(.max, field)!
986 cfg := qb.build_aggregate_config(.max, field)!
987 result := qb.conn.select(cfg, qb.data, qb.where)!
988 if result.len == 0 || result[0].len == 0 {
989 return AggregateValue{}
990 }
991 return primitive_to_aggregate_value(result[0][0])
992}
993
994// avg returns the average field value as an `AggregateValue`.
995pub fn (qb_ &QueryBuilder[T]) avg(field string) !AggregateValue {
996 mut qb := unsafe { qb_ }
997 defer {
998 qb.reset()
999 }
1000 qb.prepare()!
1001 qb.validate_aggregate_field(.avg, field)!
1002 cfg := qb.build_aggregate_config(.avg, field)!
1003 result := qb.conn.select(cfg, qb.data, qb.where)!
1004 if result.len == 0 || result[0].len == 0 {
1005 return AggregateValue{}
1006 }
1007 return primitive_to_aggregate_value(result[0][0])
1008}
1009
1010// insert insert a record into the database
1011pub fn (qb_ &QueryBuilder[T]) insert[T](value T) !&QueryBuilder[T] {
1012 mut qb := unsafe { qb_ }
1013 defer {
1014 qb.reset()
1015 }
1016 qb.insert_many([value])!
1017 return qb
1018}
1019
1020// insert_many insert records into the database
1021// Uses batch INSERT for efficiency when inserting multiple records.
1022pub fn (qb_ &QueryBuilder[T]) insert_many[T](values []T) !&QueryBuilder[T] {
1023 mut qb := unsafe { qb_ }
1024 defer {
1025 qb.reset()
1026 }
1027 qb.prepare()!
1028 if values.len == 0 {
1029 return error('${@FN}(): `insert` need at least one record')
1030 }
1031 mut batch := fill_data_with_struct[T](values[0], qb.meta)
1032 batch.batch_rows = values.len
1033 for i in 1 .. values.len {
1034 next := fill_data_with_struct[T](values[i], qb.meta)
1035 for d in next.data {
1036 batch.data << d
1037 }
1038 }
1039 qb.conn.insert(qb.config.table, batch)!
1040 return qb
1041}
1042
1043// save updates all mapped fields in `value` using the struct primary key or `id` field.
1044pub fn save[T](conn Connection, value T) ! {
1045 mut qb := new_query[T](conn)
1046 data, where := build_save_query_data[T](qb.meta, qb.config.table.name, value)!
1047 qb.conn.update(qb.config.table, data, where)!
1048}
1049
1050fn build_save_query_data[T](meta []TableField, table_name string, value T) !(QueryData, QueryData) {
1051 data := fill_data_with_struct[T](value, meta)
1052 if data.fields.len != data.data.len {
1053 return error('${@FN}(): table `${table_name}` contains fields that `save` cannot map automatically')
1054 }
1055 primary_field_name := find_save_primary_field_name(meta) or {
1056 return error('${@FN}(): table `${table_name}` needs a primary key or `id` field to use `save`')
1057 }
1058 mut update_data := QueryData{}
1059 mut where_data := QueryData{
1060 kinds: [.eq]
1061 }
1062 for i, field_name in data.fields {
1063 if field_name == primary_field_name {
1064 where_data.fields << field_name
1065 where_data.data << data.data[i]
1066 continue
1067 }
1068 update_data.fields << field_name
1069 update_data.data << data.data[i]
1070 }
1071 if where_data.fields.len == 0 {
1072 return error('${@FN}(): struct value is missing the primary key field `${primary_field_name}`')
1073 }
1074 if update_data.fields.len == 0 {
1075 return error('${@FN}(): no updatable fields were found for table `${table_name}`')
1076 }
1077 return update_data, where_data
1078}
1079
1080fn find_save_primary_field_name(meta []TableField) ?string {
1081 for field in meta {
1082 for attr in field.attrs {
1083 if attr_name_matches(attr.name, 'primary') {
1084 return sql_field_name(field)
1085 }
1086 }
1087 }
1088 for field in meta {
1089 field_name := sql_field_name(field)
1090 if field.name == 'id' || field_name == 'id' {
1091 return field_name
1092 }
1093 }
1094 return none
1095}
1096
1097fn fill_data_with_struct[T](value T, meta []TableField) QueryData {
1098 mut qb := QueryData{}
1099 $for field in T.fields {
1100 sql_fields := meta.filter(it.name == field.name)
1101 if sql_fields.len == 1 {
1102 sql_f := sql_fields[0]
1103 sql_f_name := sql_field_name(sql_f)
1104 sql_f_type := sql_field_type(sql_f)
1105
1106 if sql_f_type == serial {
1107 // `serial` should be auto field
1108 qb.auto_fields << qb.fields.len
1109 }
1110 qb.fields << sql_f_name
1111
1112 $if field.unaliased_typ is bool {
1113 qb.data << bool_to_primitive(bool(value.$(field.name)))
1114 } $else $if field.unaliased_typ is ?bool {
1115 qb.data << option_bool_to_primitive(value.$(field.name))
1116 }
1117 $if field.unaliased_typ is f32 {
1118 qb.data << f32_to_primitive(f32(value.$(field.name)))
1119 } $else $if field.unaliased_typ is ?f32 {
1120 qb.data << option_f32_to_primitive(value.$(field.name))
1121 }
1122 $if field.unaliased_typ is f64 {
1123 qb.data << f64_to_primitive(f64(value.$(field.name)))
1124 } $else $if field.unaliased_typ is ?f64 {
1125 qb.data << option_f64_to_primitive(value.$(field.name))
1126 }
1127 $if field.unaliased_typ is i8 {
1128 qb.data << i8_to_primitive(i8(value.$(field.name)))
1129 } $else $if field.unaliased_typ is ?i8 {
1130 qb.data << option_i8_to_primitive(value.$(field.name))
1131 }
1132 $if field.unaliased_typ is i16 {
1133 qb.data << i16_to_primitive(i16(value.$(field.name)))
1134 } $else $if field.unaliased_typ is ?i16 {
1135 qb.data << option_i16_to_primitive(value.$(field.name))
1136 }
1137 $if field.unaliased_typ is int {
1138 qb.data << int_to_primitive(int(value.$(field.name)))
1139 } $else $if field.unaliased_typ is ?int {
1140 qb.data << option_int_to_primitive(value.$(field.name))
1141 }
1142 $if field.unaliased_typ is i64 {
1143 qb.data << i64_to_primitive(i64(value.$(field.name)))
1144 } $else $if field.unaliased_typ is ?i64 {
1145 qb.data << option_i64_to_primitive(value.$(field.name))
1146 }
1147 $if field.unaliased_typ is u8 {
1148 qb.data << u8_to_primitive(u8(value.$(field.name)))
1149 } $else $if field.unaliased_typ is ?u8 {
1150 qb.data << option_u8_to_primitive(value.$(field.name))
1151 }
1152 $if field.unaliased_typ is u16 {
1153 qb.data << u16_to_primitive(u16(value.$(field.name)))
1154 } $else $if field.unaliased_typ is ?u16 {
1155 qb.data << option_u16_to_primitive(value.$(field.name))
1156 }
1157 $if field.unaliased_typ is u32 {
1158 qb.data << u32_to_primitive(u32(value.$(field.name)))
1159 } $else $if field.unaliased_typ is ?u32 {
1160 qb.data << option_u32_to_primitive(value.$(field.name))
1161 }
1162 $if field.unaliased_typ is u64 {
1163 qb.data << u64_to_primitive(u64(value.$(field.name)))
1164 } $else $if field.unaliased_typ is ?u64 {
1165 qb.data << option_u64_to_primitive(value.$(field.name))
1166 }
1167 $if field.unaliased_typ is string {
1168 qb.data << string_to_primitive(string(value.$(field.name)))
1169 } $else $if field.unaliased_typ is ?string {
1170 qb.data << option_string_to_primitive(value.$(field.name))
1171 } $else $if field.unaliased_typ is time.Time {
1172 if sql_f_type == type_string {
1173 qb.data << string_to_primitive(value.$(field.name).format_ss())
1174 } else {
1175 qb.data << time_to_primitive(value.$(field.name))
1176 }
1177 } $else $if field.unaliased_typ is ?time.Time {
1178 if sql_f_type == type_string {
1179 b := value.$(field.name)
1180 if b_ := b {
1181 qb.data << Primitive(b_.format_ss())
1182 } else {
1183 qb.data << null_primitive
1184 }
1185 } else {
1186 qb.data << option_time_to_primitive(value.$(field.name))
1187 }
1188 } $else $if field.unaliased_typ is $enum {
1189 qb.data << i64_to_primitive(i64(value.$(field.name)))
1190 }
1191 }
1192 }
1193 return qb
1194}
1195
1196// update update record(s) in the database
1197pub fn (qb_ &QueryBuilder[T]) update() !&QueryBuilder[T] {
1198 mut qb := unsafe { qb_ }
1199 defer {
1200 qb.reset()
1201 }
1202 qb.prepare()!
1203 if qb.data.fields.len == 0 {
1204 return error('${@FN}(): `update` need at least one `set` clause')
1205 }
1206 qb.conn.update(qb.config.table, qb.data, qb.where)!
1207 return qb
1208}
1209
1210// update_many updates multiple records by a key field, using batch CASE WHEN for efficiency.
1211// key_field is the column used to match rows (e.g. 'id').
1212// field_names selects which columns to update; if empty, all struct fields except key_field are updated.
1213pub fn update_many[T](mut conn Connection, values []T, key_field string, field_names ...string) ! {
1214 if values.len == 0 {
1215 return error('${@FN}(): need at least one record')
1216 }
1217 mut qb := new_query[T](conn)
1218
1219 // Build the field list from the first value
1220 first := fill_data_with_struct[T](values[0], qb.meta)
1221 mut key_index := -1
1222 mut value_fields := []string{}
1223 mut value_indexes := []int{}
1224
1225 for i, field in first.fields {
1226 if field == key_field {
1227 key_index = i
1228 } else if field_names.len == 0 || field in field_names {
1229 value_fields << field
1230 value_indexes << i
1231 }
1232 }
1233
1234 if key_index < 0 {
1235 return error('${@FN}(): key field `${key_field}` not found in table `${qb.config.table.name}`')
1236 }
1237
1238 if value_fields.len == 0 {
1239 return error('${@FN}(): no updatable fields found for table `${qb.config.table.name}`')
1240 }
1241
1242 mut update_data := QueryData{
1243 fields: value_fields
1244 batch_rows: values.len
1245 batch_key: key_field
1246 }
1247
1248 // Build data: per value_field, per row: [key_value, value_field_value]
1249 rows := values.map(fill_data_with_struct[T](it, qb.meta))
1250
1251 for fj in value_indexes {
1252 for row in rows {
1253 if key_index < row.data.len {
1254 update_data.data << row.data[key_index]
1255 }
1256 if fj < row.data.len {
1257 update_data.data << row.data[fj]
1258 }
1259 }
1260 }
1261
1262 // Build WHERE clause using IN
1263 mut key_values := []Primitive{}
1264 for row in rows {
1265 if key_index < row.data.len {
1266 key_values << row.data[key_index]
1267 }
1268 }
1269
1270 mut where_data := QueryData{
1271 fields: [key_field]
1272 data: [Primitive(key_values)]
1273 kinds: [.in]
1274 }
1275
1276 conn.update(qb.config.table, update_data, where_data)!
1277}
1278
1279// delete delete record(s) in the database
1280pub fn (qb_ &QueryBuilder[T]) delete() !&QueryBuilder[T] {
1281 mut qb := unsafe { qb_ }
1282 defer {
1283 qb.reset()
1284 }
1285 qb.prepare()!
1286 qb.conn.delete(qb.config.table, qb.where)!
1287 return qb
1288}
1289
1290// create create a table
1291pub fn (qb_ &QueryBuilder[T]) create() !&QueryBuilder[T] {
1292 mut qb := unsafe { qb_ }
1293 defer {
1294 qb.reset()
1295 }
1296 qb.conn.create(qb.config.table, qb.meta)!
1297 return qb
1298}
1299
1300// drop drop a table
1301pub fn (qb_ &QueryBuilder[T]) drop() !&QueryBuilder[T] {
1302 mut qb := unsafe { qb_ }
1303 defer {
1304 qb.reset()
1305 }
1306 qb.conn.drop(qb.config.table)!
1307 return qb
1308}
1309
1310// last_id returns the last inserted id of the db
1311pub fn (qb_ &QueryBuilder[T]) last_id() int {
1312 mut qb := unsafe { qb_ }
1313 qb.reset()
1314 return qb.conn.last_id()
1315}
1316