v / vlib / orm / orm_func_test.v
759 lines · 703 sloc · 16.99 KB · d5578efae67ef01ab3a63866d429b7dacf6c237d
Raw
1// vtest retry: 2
2import orm
3import db.sqlite
4import time
5
6@[table: 'sys_users']
7@[index: 'name, age']
8struct User {
9 id int @[primary; serial]
10 name string @[index]
11 age int
12 role string @[index]
13 status int @[index]
14 level UserLevel
15 salary int
16 title string
17 score int
18 created_at ?time.Time @[sql_type: 'TIMESTAMP']
19 updated_at time.Time @[sql_type: 'TIMESTAMP']
20 type_i8 i8
21 type_i16 i16
22 type_int int
23 type_i64 i64
24 type_u8 u8
25 type_u16 u16
26 type_u32 u32
27 type_u64 u64
28 type_f32 f32
29 type_f64 f64
30 type_bool bool
31 type_string string
32 option_i8 ?i8
33 option_i16 ?i16
34 option_int ?int
35 option_i64 ?i64
36 option_u8 ?u8
37 option_u16 ?u16
38 option_u32 ?u32
39 option_u64 ?u64
40 option_f32 ?f32
41 option_f64 ?f64
42 option_bool ?bool
43 option_string ?string
44}
45
46enum UserLevel {
47 beginner
48 intermediate
49 advanced
50}
51
52// UserPart is part of User, so we can access only part of the `sys_users` table
53// note: for test, we modify `created_at` field from option to require
54// a `null` value in database, will map to default value of the require field in struct
55@[table: 'sys_users']
56struct UserPart {
57 id int @[primary; serial]
58 name string
59 created_at time.Time @[sql_type: 'TIMESTAMP']
60 updated_at time.Time @[sql_type: 'TIMESTAMP']
61 option_i8 ?i8 = 13 // option with default test
62 option_string ?string = 'this is not none'
63}
64
65fn test_orm_func_where() {
66 mut db := sqlite.connect(':memory:')!
67 defer { db.close() or {} }
68 mut qb := orm.new_query[User](db)
69
70 // single_condition
71 qb.reset()
72 qb.where('age > ?', 25)!
73 assert qb.where.fields == ['age']
74 assert qb.where.kinds == [.gt]
75 assert qb.where.data == [25]
76
77 // chain_condition
78 qb.reset()
79 qb.where('age > ?', 25)!.where('salary < ?', 1000)!
80 assert qb.where.fields == ['age', 'salary']
81 assert qb.where.kinds == [.gt, .lt]
82 assert qb.where.data == [25, 1000]
83
84 // and_or_combination
85 qb.reset()
86 qb.where('name = ? AND status = ? OR role = ? || id = ? && title = ?', 'Alice', 1, 'admin', 1,
87 'st')!
88 assert qb.where.fields == ['name', 'status', 'role', 'id', 'title']
89 assert qb.where.kinds == [.eq, .eq, .eq, .eq, .eq]
90 assert qb.where.is_and == [true, false, false, true]
91
92 // nested_parentheses
93 qb.reset()
94 qb.where('(salary >= ? AND (age <= ? OR title LIKE ?))', 50000, 35, '%Manager%')!
95 assert qb.where.parentheses == [[1, 2], [0, 2]]
96
97 // test lowercase `like`
98 qb.reset()
99 qb.where('(salary >= ? AND (age <= ? OR title like ?))', 50000, 35, '%Manager%')!
100 assert qb.where.parentheses == [[1, 2], [0, 2]]
101 assert qb.where.kinds == [.ge, .le, .orm_like]
102
103 // complex_nesting
104 qb.reset()
105 qb.where('((age = ? OR (salary > ? AND id < ?)) AND (name LIKE ?))', 1, 2, 3, '%test%')!
106 assert qb.where.parentheses == [[1, 2], [0, 2], [3, 3], [0, 3]]
107
108 // in and not in
109 qb.reset()
110 qb.where('name IN ? AND age NOT IN ?', ['Tom'], [2])!
111 assert qb.where.fields == ['name', 'age']
112 assert qb.where.kinds == [.in, .not_in]
113
114 // lowercase in and not in
115 qb.reset()
116 qb.where('name in ? AND age not in ?', ['Tom'], [2])!
117 assert qb.where.fields == ['name', 'age']
118 assert qb.where.kinds == [.in, .not_in]
119
120 // variable arrays for in and not in
121 names := ['Tom']
122 ages := [2]
123 qb.reset()
124 qb.where('name IN ? AND age NOT IN ?', names, ages)!
125 assert qb.where.fields == ['name', 'age']
126 assert qb.where.kinds == [.in, .not_in]
127 assert qb.where.data[0] is []orm.Primitive
128 assert qb.where.data[1] is []orm.Primitive
129 name_params := qb.where.data[0] as []orm.Primitive
130 age_params := qb.where.data[1] as []orm.Primitive
131 assert name_params == [orm.Primitive('Tom')]
132 assert age_params == [orm.Primitive(2)]
133}
134
135fn test_orm_func_stmts() {
136 users := [
137 User{
138 name: 'Tom'
139 age: 30
140 role: 'admin'
141 status: 1
142 salary: 5000
143 title: 'manager'
144 score: 90
145 created_at: time.now()
146 updated_at: time.now()
147 type_i8: 1
148 type_i16: 2
149 type_int: 3
150 type_i64: 4
151 type_u8: 5
152 type_u16: 6
153 type_u32: 7
154 type_u64: 8
155 type_f32: 1.1
156 type_f64: 2.2
157 type_bool: true
158 type_string: 'hello'
159 option_i8: 1
160 option_i16: 2
161 option_int: 3
162 option_i64: 4
163 option_u8: 5
164 option_u16: 6
165 option_u32: 7
166 option_u64: 8
167 option_f32: 1.1
168 option_f64: 2.2
169 option_bool: true
170 option_string: 'hello'
171 },
172 User{
173 name: 'Alice'
174 age: 20
175 role: 'employee'
176 status: 2
177 salary: 2000
178 title: 'doctor'
179 score: 95
180 created_at: time.now()
181 updated_at: time.now()
182 type_i8: 1
183 type_i16: 2
184 type_int: 3
185 type_i64: 4
186 type_u8: 5
187 type_u16: 6
188 type_u32: 7
189 type_u64: 8
190 type_f32: 1.1
191 type_f64: 2.2
192 type_bool: true
193 type_string: 'hello'
194 option_i8: 1
195 option_i16: 2
196 option_int: 3
197 option_i64: 4
198 option_u8: 5
199 option_u16: 6
200 option_u32: 7
201 option_u64: 8
202 option_f32: 1.1
203 option_f64: 2.2
204 option_bool: true
205 option_string: 'hello'
206 },
207 User{
208 name: 'Mars'
209 age: 40
210 role: 'employer'
211 status: 3
212 salary: 1000
213 title: 'doctor'
214 score: 85
215 created_at: time.now()
216 updated_at: time.now()
217 type_i8: 1
218 type_i16: 2
219 type_int: 3
220 type_i64: 4
221 type_u8: 5
222 type_u16: 6
223 type_u32: 7
224 type_u64: 8
225 type_f32: 1.1
226 type_f64: 2.2
227 type_bool: true
228 type_string: 'hello'
229 option_i8: 1
230 option_i16: 2
231 option_int: 3
232 option_i64: 4
233 option_u8: 5
234 option_u16: 6
235 option_u32: 7
236 option_u64: 8
237 option_f32: 1.1
238 option_f64: 2.2
239 option_bool: true
240 option_string: 'hello'
241 },
242 User{
243 name: 'Kitty'
244 age: 18
245 role: 'employer'
246 status: 1
247 salary: 1500
248 title: 'doctor'
249 score: 87
250 created_at: time.now()
251 updated_at: time.now()
252 type_i8: 1
253 type_i16: 2
254 type_int: 3
255 type_i64: 4
256 type_u8: 5
257 type_u16: 6
258 type_u32: 7
259 type_u64: 8
260 type_f32: 1.1
261 type_f64: 2.2
262 type_bool: true
263 type_string: 'hello'
264 option_i8: 1
265 option_i16: 2
266 option_int: 3
267 option_i64: 4
268 option_u8: 5
269 option_u16: 6
270 option_u32: 7
271 option_u64: 8
272 option_f32: 1.1
273 option_f64: 2.2
274 option_bool: true
275 option_string: 'hello'
276 },
277 User{
278 name: 'Silly'
279 age: 27
280 role: 'employer'
281 status: 5
282 level: .intermediate
283 salary: 2500
284 title: 'doctor'
285 score: 81
286 updated_at: time.now()
287 type_i8: 1
288 type_i16: 2
289 type_int: 3
290 type_i64: 4
291 type_u8: 5
292 type_u16: 6
293 type_u32: 7
294 type_u64: 8
295 type_f32: 1.1
296 type_f64: 2.2
297 type_bool: true
298 type_string: 'hello'
299 option_i8: 1
300 option_i16: 2
301 option_int: 3
302 option_i64: 4
303 option_u8: 5
304 option_u16: 6
305 option_u32: 7
306 option_u64: 8
307 option_f32: 1.1
308 option_f64: 2.2
309 option_bool: true
310 // option_string: 'hello' // option with default test
311 },
312 User{
313 name: 'Smith'
314 age: 37
315 role: 'employer'
316 status: 1
317 salary: 4500
318 title: 'doctor'
319 score: 89
320 created_at: time.now()
321 updated_at: time.now()
322 type_i8: 1
323 type_i16: 2
324 type_int: 3
325 type_i64: 4
326 type_u8: 5
327 type_u16: 6
328 type_u32: 7
329 type_u64: 8
330 type_f32: 1.1
331 type_f64: 2.2
332 type_bool: true
333 type_string: 'hello'
334 option_i8: 1
335 option_i16: 2
336 option_int: 3
337 option_i64: 4
338 option_u8: 5
339 option_u16: 6
340 option_u32: 7
341 option_u64: 8
342 option_f32: 1.1
343 option_f64: 2.2
344 option_bool: true
345 option_string: 'hello'
346 },
347 User{
348 name: 'Bob'
349 age: 26
350 role: 'employer'
351 status: 2
352 salary: 6500
353 title: 'doctor'
354 score: 81
355 created_at: time.now()
356 updated_at: time.now()
357 type_i8: 1
358 type_i16: 2
359 type_int: 3
360 type_i64: 4
361 type_u8: 5
362 type_u16: 6
363 type_u32: 7
364 type_u64: 8
365 type_f32: 1.1
366 type_f64: 2.2
367 type_bool: true
368 type_string: 'hello'
369 option_i8: 1
370 option_i16: 2
371 option_int: 3
372 option_i64: 4
373 option_u8: 5
374 option_u16: 6
375 option_u32: 7
376 option_u64: 8
377 option_f32: 1.1
378 option_f64: 2.2
379 option_bool: true
380 option_string: 'hello'
381 },
382 User{
383 name: 'Peter'
384 age: 29
385 role: 'employer'
386 status: 1
387 salary: 3500
388 title: 'doctor'
389 score: 80
390 created_at: time.now()
391 updated_at: time.now()
392 type_i8: 1
393 type_i16: 2
394 type_int: 3
395 type_i64: 4
396 type_u8: 5
397 type_u16: 6
398 type_u32: 7
399 type_u64: 8
400 type_f32: 1.1
401 type_f64: 2.2
402 type_bool: true
403 type_string: 'hello'
404 // option_i8: 1 // option with default test
405 option_i16: 2
406 option_int: 3
407 option_i64: 4
408 option_u8: 5
409 option_u16: 6
410 option_u32: 7
411 option_u64: 8
412 option_f32: 1.1
413 option_f64: 2.2
414 option_bool: true
415 option_string: 'hello'
416 },
417 User{
418 name: 'See'
419 age: 45
420 role: 'employer'
421 status: 2
422 salary: 8500
423 title: 'doctor'
424 score: 82
425 updated_at: time.now()
426 type_i8: 1
427 type_i16: 2
428 type_int: 3
429 type_i64: 4
430 type_u8: 5
431 type_u16: 6
432 type_u32: 7
433 type_u64: 8
434 type_f32: 1.1
435 type_f64: 2.2
436 type_bool: true
437 type_string: 'hello'
438 option_i8: 1
439 option_i16: 2
440 option_int: 3
441 option_i64: 4
442 option_u8: 5
443 option_u16: 6
444 option_u32: 7
445 option_u64: 8
446 option_f32: 1.1
447 option_f64: 2.2
448 option_bool: true
449 option_string: 'hello'
450 },
451 User{
452 name: 'John'
453 age: 42
454 role: 'employer'
455 status: 1
456 salary: 10000
457 title: 'doctor'
458 score: 88
459 updated_at: time.now()
460 type_i8: 1
461 type_i16: 2
462 type_int: 3
463 type_i64: 4
464 type_u8: 5
465 type_u16: 6
466 type_u32: 7
467 type_u64: 8
468 type_f32: 1.1
469 type_f64: 2.2
470 type_bool: true
471 type_string: 'hello'
472 option_i8: 1
473 option_i16: 2
474 option_int: 3
475 option_i64: 4
476 option_u8: 5
477 option_u16: 6
478 option_u32: 7
479 option_u64: 8
480 option_f32: 1.1
481 option_f64: 2.2
482 option_bool: true
483 option_string: 'hello'
484 },
485 ]
486 mut db := sqlite.connect(':memory:')!
487 defer { db.close() or {} }
488 mut qb := orm.new_query[User](db)
489
490 // create table
491 qb.create()!
492
493 // insert many records
494 qb.insert_many(users)!
495
496 // select count(*)
497 mut count := qb.count()!
498
499 // last_id
500 mut last_id := qb.last_id()
501 assert count == last_id
502 assert count == users.len
503
504 // insert a single record
505 qb.insert(users[0])!
506
507 // select * from table
508 all_users := qb.query()!
509 assert all_users.len == users.len + 1
510
511 // select `name` from table
512 only_names := qb.select('name')!.query()!
513 assert only_names[0].name != ''
514 assert only_names[0].id == 0
515 assert only_names[0].age == 0
516 assert only_names[0].role == ''
517 assert only_names[0].status == 0
518 assert only_names[0].salary == 0
519 assert only_names[0].title == ''
520 assert only_names[0].score == 0
521 assert only_names[0].created_at == none
522
523 // select distinct `role` from table
524 distinct_roles := qb.select('role')!.distinct()!.order(.asc, 'role')!.query()!
525 assert distinct_roles.len == 3
526 assert distinct_roles.map(it.role) == ['admin', 'employee', 'employer']
527 assert distinct_roles[0].id == 0
528 assert distinct_roles[0].name == ''
529
530 // update with single `set()`
531 qb.set('age = ?, title = ?', 71, 'boss')!.where('name = ?', 'John')!.update()!
532 john := qb.where('name = ?', 'John')!.query()!
533 assert john[0].name == 'John'
534 assert john[0].age == 71
535 assert john[0].title == 'boss'
536
537 // update with multiple `set()`
538
539 qb.set('age = ?', 51)!
540 .set('title = ?', 'employee')!
541 .where('name = ?', 'John')!.update()!
542 john2 := qb.where('name = ?', 'John')!.query()!
543 assert john2[0].name == 'John'
544 assert john2[0].age == 51
545 assert john2[0].title == 'employee'
546
547 // delete
548 qb.where('name = ?', 'John')!.delete()!
549 no_john := qb.where('name = ?', 'John')!.query()!
550 assert no_john.len == 0
551
552 // complex select
553 selected_users := qb.where('created_at IS NULL && ((salary > ? && age < ?) || (role LIKE ?))',
554 2000, 30, '%employee%')!.query()!
555 assert selected_users[0].name == 'Silly'
556 // Check enum
557 assert selected_users[0].level == .intermediate
558 assert selected_users.len == 1
559
560 // complex select with lowercase `is null` and `like`
561 selected_users1 := qb.where('created_at is null && ((salary > ? && age < ?) || (role like ?))',
562 2000, 30, '%employee%')!.query()!
563 assert selected_users1[0].name == 'Silly'
564 assert selected_users1.len == 1
565
566 // chain where
567 and_where := qb.where('salary > ?', 2000)!.where('age > ?', 40)!.query()!
568 assert and_where.len == 1
569 or_where := qb.where('salary > ?', 2000)!.or_where('age > ? OR score > ?', 40, 85)!.query()!
570 assert or_where.len == 9
571
572 // chain calls
573 final_users := qb
574 .drop()!
575 .create()!
576 .insert_many(users)!
577 .set('name = ?', 'haha')!.where('name = ?', 'Tom')!.update()!
578 .where('age >= ?', 30)!.delete()!
579 .order(.asc, 'age')!
580 .limit(100)!
581 .query()!
582 assert final_users.len == 5
583 assert final_users[0].age == 18
584
585 // access only part of the table
586 mut part := orm.new_query[UserPart](db)
587 part_user := part.query()!
588 // a `null` value in database, will map to default value of the require field in struct
589 assert part_user.filter(it.name == 'Silly')[0].created_at == time.Time{}
590 assert part_user.len == 5
591}
592
593struct Person {
594 age_f32 f32
595 age_f64 f64
596}
597
598fn test_orm_func_f32_f64() {
599 p := Person{
600 age_f32: 10.33
601 age_f64: 10.343
602 }
603
604 db := sqlite.connect(':memory:')!
605
606 mut qb := orm.new_query[Person](db)
607
608 data := qb
609 .create()!
610 .insert(p)!
611 .query()!
612 .first()
613
614 assert data.age_f32 == p.age_f32
615 assert data.age_f64 == p.age_f64
616}
617
618@[index: 'age_f33, age_f64']
619struct InvalidIndexFieldName1 {
620 age_f32 f32
621 age_f64 f64
622}
623
624fn test_orm_func_invalid_index_field_name1() {
625 p := InvalidIndexFieldName1{
626 age_f32: 10.33
627 age_f64: 10.343
628 }
629
630 db := sqlite.connect(':memory:')!
631
632 mut qb := orm.new_query[InvalidIndexFieldName1](db)
633
634 qb.create() or {
635 assert err.msg() == "table `invalidindexfieldname1` has no field's name: `age_f33`"
636 return
637 }
638 assert false, 'should not be here'
639}
640
641@[index: 'age_f32, age_f64']
642struct InvalidIndexFieldName2 {
643 age_f32 f32 @[sql: abc]
644 age_f64 f64
645}
646
647fn test_orm_func_invalid_index_field_name2() {
648 p := InvalidIndexFieldName2{
649 age_f32: 10.33
650 age_f64: 10.343
651 }
652
653 db := sqlite.connect(':memory:')!
654
655 mut qb := orm.new_query[InvalidIndexFieldName2](db)
656
657 qb.create() or {
658 assert err.msg() == "table `invalidindexfieldname2` has no field's name: `age_f32`"
659 return
660 }
661 assert false, 'should not be here'
662}
663
664fn test_orm_func_update_many() {
665 mut db := sqlite.connect(':memory:')!
666 defer { db.close() or {} }
667 mut qb := orm.new_query[User](db)
668
669 qb.create()!
670
671 // Insert test records
672 users := [
673 User{
674 name: 'Alice'
675 age: 25
676 role: 'developer'
677 },
678 User{
679 name: 'Bob'
680 age: 30
681 role: 'manager'
682 },
683 User{
684 name: 'Carol'
685 age: 35
686 role: 'designer'
687 },
688 ]
689 qb.insert_many(users)!
690
691 // Verify initial data
692 all_users := qb.query()!
693 assert all_users.len == 3
694 assert all_users[0].name == 'Alice'
695 assert all_users[1].name == 'Bob'
696 assert all_users[2].name == 'Carol'
697
698 // Batch update names by id
699 orm.update_many[User](mut db, [
700 User{
701 id: 1
702 name: 'Alice_updated'
703 age: 26
704 },
705 User{
706 id: 2
707 name: 'Bob_updated'
708 age: 31
709 },
710 ], 'id', 'name', 'age')!
711
712 // Verify updated data
713 updated_users := qb.query()!
714 assert updated_users.len == 3
715 for u in updated_users {
716 match u.id {
717 1 {
718 assert u.name == 'Alice_updated'
719 assert u.age == 26
720 assert u.role == 'developer'
721 }
722 2 {
723 assert u.name == 'Bob_updated'
724 assert u.age == 31
725 assert u.role == 'manager'
726 }
727 3 {
728 assert u.name == 'Carol'
729 assert u.age == 35
730 assert u.role == 'designer'
731 }
732 else {
733 assert false
734 }
735 }
736 }
737
738 // Test update_many with single record
739 orm.update_many[User](mut db, [
740 User{
741 id: 3
742 name: 'Carol_updated'
743 age: 36
744 },
745 ], 'id', 'name', 'age')!
746
747 single_result := qb.where('id = ?', 3)!.query()!
748 assert single_result.len == 1
749 assert single_result[0].name == 'Carol_updated'
750 assert single_result[0].age == 36
751 assert single_result[0].role == 'designer'
752
753 // Test update_many with empty values
754 orm.update_many[User](mut db, []User{}, 'id', 'name') or {
755 assert err.msg().contains('need at least one record')
756 return
757 }
758 assert false, 'should not be here'
759}
760