an ORM-free SQL experience

more query tests using sqlite3 in-memory db

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 4be9bb62 dcc34f84

verified
Changed files
+562
+2
go.mod
··· 1 module tangled.sh/oppi.li/norm 2 3 go 1.24.1
··· 1 module tangled.sh/oppi.li/norm 2 3 go 1.24.1 4 + 5 + require github.com/mattn/go-sqlite3 v1.14.28
+2
go.sum
···
··· 1 + github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= 2 + github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+558
query_test.go
··· 1 package norm 2 3 import ( 4 "testing" 5 ) 6 7 func TestIdentExpr(t *testing.T) { ··· 379 } 380 } 381 }
··· 1 package norm 2 3 import ( 4 + "database/sql" 5 "testing" 6 + "time" 7 + 8 + _ "github.com/mattn/go-sqlite3" 9 ) 10 11 func TestIdentExpr(t *testing.T) { ··· 383 } 384 } 385 } 386 + 387 + // setupTestDB creates an in-memory SQLite database with test data 388 + func setupTestDB(t *testing.T) *sql.DB { 389 + db, err := sql.Open("sqlite3", ":memory:") 390 + if err != nil { 391 + t.Fatalf("Failed to open database: %v", err) 392 + } 393 + 394 + // Create users table 395 + _, err = db.Exec(` 396 + CREATE TABLE users ( 397 + id INTEGER PRIMARY KEY AUTOINCREMENT, 398 + name TEXT NOT NULL, 399 + age INTEGER NOT NULL, 400 + email TEXT UNIQUE NOT NULL, 401 + department TEXT, 402 + active BOOLEAN DEFAULT TRUE, 403 + salary REAL, 404 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP 405 + ) 406 + `) 407 + if err != nil { 408 + t.Fatalf("Failed to create users table: %v", err) 409 + } 410 + 411 + // Create departments table 412 + _, err = db.Exec(` 413 + CREATE TABLE departments ( 414 + id INTEGER PRIMARY KEY AUTOINCREMENT, 415 + name TEXT NOT NULL, 416 + budget REAL 417 + ) 418 + `) 419 + if err != nil { 420 + t.Fatalf("Failed to create departments table: %v", err) 421 + } 422 + 423 + // Insert test data 424 + users := []struct { 425 + name, email, department string 426 + age int 427 + active bool 428 + salary float64 429 + }{ 430 + {"John Doe", "john@example.com", "Engineering", 30, true, 75000.0}, 431 + {"Jane Smith", "jane@example.com", "Marketing", 25, true, 65000.0}, 432 + {"Bob Johnson", "bob@example.com", "Engineering", 35, false, 80000.0}, 433 + {"Alice Brown", "alice@example.com", "Sales", 28, true, 70000.0}, 434 + {"Charlie Wilson", "charlie@example.com", "Engineering", 32, true, 85000.0}, 435 + {"Diana Prince", "diana@example.com", "Marketing", 29, false, 68000.0}, 436 + } 437 + 438 + for _, user := range users { 439 + _, err = db.Exec(` 440 + INSERT INTO users (name, email, department, age, active, salary) 441 + VALUES (?, ?, ?, ?, ?, ?) 442 + `, user.name, user.email, user.department, user.age, user.active, user.salary) 443 + if err != nil { 444 + t.Fatalf("Failed to insert user %s: %v", user.name, err) 445 + } 446 + } 447 + 448 + // Insert departments 449 + departments := []struct { 450 + name string 451 + budget float64 452 + }{ 453 + {"Engineering", 500000.0}, 454 + {"Marketing", 300000.0}, 455 + {"Sales", 400000.0}, 456 + } 457 + 458 + for _, dept := range departments { 459 + _, err = db.Exec(` 460 + INSERT INTO departments (name, budget) 461 + VALUES (?, ?) 462 + `, dept.name, dept.budget) 463 + if err != nil { 464 + t.Fatalf("Failed to insert department %s: %v", dept.name, err) 465 + } 466 + } 467 + 468 + return db 469 + } 470 + 471 + func TestSelectIntegration_BasicQueries(t *testing.T) { 472 + db := setupTestDB(t) 473 + defer db.Close() 474 + 475 + tests := []struct { 476 + name string 477 + builder func() select_ 478 + expectedRows int 479 + }{ 480 + { 481 + name: "Select all users", 482 + builder: func() select_ { 483 + return Select("*").From("users") 484 + }, 485 + expectedRows: 6, 486 + }, 487 + { 488 + name: "Select active users only", 489 + builder: func() select_ { 490 + return Select("name", "email"). 491 + From("users"). 492 + Where(Eq("active", true)) 493 + }, 494 + expectedRows: 4, 495 + }, 496 + { 497 + name: "Select users in Engineering", 498 + builder: func() select_ { 499 + return Select("name", "age"). 500 + From("users"). 501 + Where(Eq("department", "Engineering")) 502 + }, 503 + expectedRows: 3, 504 + }, 505 + { 506 + name: "Select users with age > 30", 507 + builder: func() select_ { 508 + return Select("name", "age"). 509 + From("users"). 510 + Where(Gt("age", 30)) 511 + }, 512 + expectedRows: 2, 513 + }, 514 + { 515 + name: "Select users with salary between 70000 and 80000", 516 + builder: func() select_ { 517 + return Select("name", "salary"). 518 + From("users"). 519 + Where(Gte("salary", 70000.0).And(Lte("salary", 80000.0))) 520 + }, 521 + expectedRows: 3, 522 + }, 523 + } 524 + 525 + for _, test := range tests { 526 + t.Run(test.name, func(t *testing.T) { 527 + s := test.builder() 528 + sql, args, err := s.Build() 529 + if err != nil { 530 + t.Fatalf("Failed to build query: %v", err) 531 + } 532 + 533 + rows, err := db.Query(sql, args...) 534 + if err != nil { 535 + t.Fatalf("Failed to execute query: %v", err) 536 + } 537 + defer rows.Close() 538 + 539 + count := 0 540 + for rows.Next() { 541 + count++ 542 + } 543 + 544 + if count != test.expectedRows { 545 + t.Errorf("Expected %d rows, got %d", test.expectedRows, count) 546 + } 547 + }) 548 + } 549 + } 550 + 551 + func TestSelectIntegration_ComplexQueries(t *testing.T) { 552 + db := setupTestDB(t) 553 + defer db.Close() 554 + 555 + t.Run("Complex WHERE with AND/OR", func(t *testing.T) { 556 + s := Select("name", "department", "age"). 557 + From("users"). 558 + Where( 559 + Eq("department", "Engineering"). 560 + And(Gt("age", 30)). 561 + Or(Eq("department", "Marketing").And(Lt("age", 30))), 562 + ) 563 + 564 + sql, args, err := s.Build() 565 + if err != nil { 566 + t.Fatalf("Failed to build query: %v", err) 567 + } 568 + 569 + rows, err := db.Query(sql, args...) 570 + if err != nil { 571 + t.Fatalf("Failed to execute query: %v", err) 572 + } 573 + defer rows.Close() 574 + 575 + var results []struct { 576 + name, department string 577 + age int 578 + } 579 + 580 + for rows.Next() { 581 + var name, department string 582 + var age int 583 + if err := rows.Scan(&name, &department, &age); err != nil { 584 + t.Fatalf("Failed to scan row: %v", err) 585 + } 586 + results = append(results, struct { 587 + name, department string 588 + age int 589 + }{name, department, age}) 590 + } 591 + 592 + // Should get: 593 + // - Bob Johnson (Engineering, 35) 594 + // - Charlie Wilson (Engineering, 32) 595 + // - Jane Smith (Marketing, 25) 596 + // - Diana Prince (Marketing, 29) 597 + if len(results) != 4 { 598 + t.Errorf("Expected 4 results, got %d", len(results)) 599 + } 600 + }) 601 + 602 + t.Run("ORDER BY multiple columns", func(t *testing.T) { 603 + s := Select("name", "department", "age"). 604 + From("users"). 605 + OrderBy("department", Ascending). 606 + OrderBy("age", Descending) 607 + 608 + sql, args, err := s.Build() 609 + if err != nil { 610 + t.Fatalf("Failed to build query: %v", err) 611 + } 612 + 613 + rows, err := db.Query(sql, args...) 614 + if err != nil { 615 + t.Fatalf("Failed to execute query: %v", err) 616 + } 617 + defer rows.Close() 618 + 619 + var results []struct { 620 + name, department string 621 + age int 622 + } 623 + 624 + for rows.Next() { 625 + var name, department string 626 + var age int 627 + if err := rows.Scan(&name, &department, &age); err != nil { 628 + t.Fatalf("Failed to scan row: %v", err) 629 + } 630 + results = append(results, struct { 631 + name, department string 632 + age int 633 + }{name, department, age}) 634 + } 635 + 636 + if len(results) != 6 { 637 + t.Fatalf("Expected 6 results, got %d", len(results)) 638 + } 639 + 640 + // First result should be from Engineering (alphabetically first) with highest age 641 + if results[0].department != "Engineering" { 642 + t.Errorf("Expected first result to be from Engineering, got %s", results[0].department) 643 + } 644 + if results[0].age != 35 { 645 + t.Errorf("Expected first result age to be 35, got %d", results[0].age) 646 + } 647 + }) 648 + 649 + t.Run("GROUP BY with COUNT", func(t *testing.T) { 650 + s := Select("department", "COUNT(*) as user_count"). 651 + From("users"). 652 + GroupBy("department"). 653 + OrderBy("user_count", Descending) 654 + 655 + sql, args, err := s.Build() 656 + if err != nil { 657 + t.Fatalf("Failed to build query: %v", err) 658 + } 659 + 660 + rows, err := db.Query(sql, args...) 661 + if err != nil { 662 + t.Fatalf("Failed to execute query: %v", err) 663 + } 664 + defer rows.Close() 665 + 666 + var results []struct { 667 + department string 668 + count int 669 + } 670 + 671 + for rows.Next() { 672 + var department string 673 + var count int 674 + if err := rows.Scan(&department, &count); err != nil { 675 + t.Fatalf("Failed to scan row: %v", err) 676 + } 677 + results = append(results, struct { 678 + department string 679 + count int 680 + }{department, count}) 681 + } 682 + 683 + if len(results) != 3 { 684 + t.Errorf("Expected 3 departments, got %d", len(results)) 685 + } 686 + 687 + // Engineering should have the most users (3) 688 + if results[0].department != "Engineering" || results[0].count != 3 { 689 + t.Errorf("Expected Engineering with 3 users first, got %s with %d users", results[0].department, results[0].count) 690 + } 691 + }) 692 + 693 + t.Run("LIMIT with ORDER BY", func(t *testing.T) { 694 + s := Select("name", "salary"). 695 + From("users"). 696 + Where(Eq("active", true)). 697 + OrderBy("salary", Descending). 698 + Limit(2) 699 + 700 + sql, args, err := s.Build() 701 + if err != nil { 702 + t.Fatalf("Failed to build query: %v", err) 703 + } 704 + 705 + rows, err := db.Query(sql, args...) 706 + if err != nil { 707 + t.Fatalf("Failed to execute query: %v", err) 708 + } 709 + defer rows.Close() 710 + 711 + var results []struct { 712 + name string 713 + salary float64 714 + } 715 + 716 + for rows.Next() { 717 + var name string 718 + var salary float64 719 + if err := rows.Scan(&name, &salary); err != nil { 720 + t.Fatalf("Failed to scan row: %v", err) 721 + } 722 + results = append(results, struct { 723 + name string 724 + salary float64 725 + }{name, salary}) 726 + } 727 + 728 + if len(results) != 2 { 729 + t.Errorf("Expected 2 results, got %d", len(results)) 730 + } 731 + 732 + // Should get Charlie Wilson (85000) and John Doe (75000) 733 + if results[0].salary != 85000.0 { 734 + t.Errorf("Expected highest salary to be 85000, got %f", results[0].salary) 735 + } 736 + if results[1].salary != 75000.0 { 737 + t.Errorf("Expected second highest salary to be 75000, got %f", results[1].salary) 738 + } 739 + }) 740 + } 741 + 742 + func TestSelectIntegration_DataTypes(t *testing.T) { 743 + db := setupTestDB(t) 744 + defer db.Close() 745 + 746 + t.Run("String comparisons", func(t *testing.T) { 747 + s := Select("name", "email"). 748 + From("users"). 749 + Where(Eq("name", "John Doe")) 750 + 751 + sql, args, err := s.Build() 752 + if err != nil { 753 + t.Fatalf("Failed to build query: %v", err) 754 + } 755 + 756 + rows, err := db.Query(sql, args...) 757 + if err != nil { 758 + t.Fatalf("Failed to execute query: %v", err) 759 + } 760 + defer rows.Close() 761 + 762 + count := 0 763 + for rows.Next() { 764 + count++ 765 + } 766 + 767 + if count != 1 { 768 + t.Errorf("Expected 1 result, got %d", count) 769 + } 770 + }) 771 + 772 + t.Run("Boolean comparisons", func(t *testing.T) { 773 + s := Select("name"). 774 + From("users"). 775 + Where(Eq("active", false)) 776 + 777 + sql, args, err := s.Build() 778 + if err != nil { 779 + t.Fatalf("Failed to build query: %v", err) 780 + } 781 + 782 + rows, err := db.Query(sql, args...) 783 + if err != nil { 784 + t.Fatalf("Failed to execute query: %v", err) 785 + } 786 + defer rows.Close() 787 + 788 + count := 0 789 + for rows.Next() { 790 + count++ 791 + } 792 + 793 + if count != 2 { 794 + t.Errorf("Expected 2 inactive users, got %d", count) 795 + } 796 + }) 797 + 798 + t.Run("Float comparisons", func(t *testing.T) { 799 + s := Select("name", "salary"). 800 + From("users"). 801 + Where(Gte("salary", 80000.0)) 802 + 803 + sql, args, err := s.Build() 804 + if err != nil { 805 + t.Fatalf("Failed to build query: %v", err) 806 + } 807 + 808 + rows, err := db.Query(sql, args...) 809 + if err != nil { 810 + t.Fatalf("Failed to execute query: %v", err) 811 + } 812 + defer rows.Close() 813 + 814 + count := 0 815 + for rows.Next() { 816 + count++ 817 + } 818 + 819 + if count != 2 { 820 + t.Errorf("Expected 2 users with salary >= 80000, got %d", count) 821 + } 822 + }) 823 + } 824 + 825 + func TestSelectIntegration_ErrorHandling(t *testing.T) { 826 + db := setupTestDB(t) 827 + defer db.Close() 828 + 829 + t.Run("Invalid table name", func(t *testing.T) { 830 + s := Select("*").From("nonexistent_table") 831 + 832 + sql, args, err := s.Build() 833 + if err != nil { 834 + t.Fatalf("Failed to build query: %v", err) 835 + } 836 + 837 + _, err = db.Query(sql, args...) 838 + if err == nil { 839 + t.Error("Expected error for nonexistent table, got nil") 840 + } 841 + }) 842 + 843 + t.Run("Invalid column name", func(t *testing.T) { 844 + s := Select("nonexistent_column").From("users") 845 + 846 + sql, args, err := s.Build() 847 + if err != nil { 848 + t.Fatalf("Failed to build query: %v", err) 849 + } 850 + 851 + _, err = db.Query(sql, args...) 852 + if err == nil { 853 + t.Error("Expected error for nonexistent column, got nil") 854 + } 855 + }) 856 + } 857 + 858 + func TestSelectIntegration_PerformanceBaseline(t *testing.T) { 859 + db := setupTestDB(t) 860 + defer db.Close() 861 + 862 + // Add more test data for performance testing 863 + start := time.Now() 864 + for i := 0; i < 1000; i++ { 865 + _, err := db.Exec(` 866 + INSERT INTO users (name, email, department, age, active, salary) 867 + VALUES (?, ?, ?, ?, ?, ?) 868 + `, "User"+string(rune(i)), "user"+string(rune(i))+"@example.com", "Engineering", 25+i%15, i%2 == 0, 50000.0+float64(i*100)) 869 + if err != nil { 870 + t.Fatalf("Failed to insert test user: %v", err) 871 + } 872 + } 873 + insertTime := time.Since(start) 874 + t.Logf("Inserted 1000 users in %v", insertTime) 875 + 876 + t.Run("Simple select performance", func(t *testing.T) { 877 + s := Select("name", "email"). 878 + From("users"). 879 + Where(Eq("active", true)) 880 + 881 + sql, args, err := s.Build() 882 + if err != nil { 883 + t.Fatalf("Failed to build query: %v", err) 884 + } 885 + 886 + start := time.Now() 887 + rows, err := db.Query(sql, args...) 888 + if err != nil { 889 + t.Fatalf("Failed to execute query: %v", err) 890 + } 891 + defer rows.Close() 892 + 893 + count := 0 894 + for rows.Next() { 895 + count++ 896 + } 897 + queryTime := time.Since(start) 898 + 899 + t.Logf("Selected %d rows in %v", count, queryTime) 900 + if queryTime > time.Millisecond*100 { 901 + t.Logf("Query took longer than expected: %v", queryTime) 902 + } 903 + }) 904 + 905 + t.Run("Complex query performance", func(t *testing.T) { 906 + s := Select("name", "department", "salary"). 907 + From("users"). 908 + Where( 909 + Eq("active", true). 910 + And(Gt("salary", 60000.0)). 911 + Or(Eq("department", "Engineering")), 912 + ). 913 + OrderBy("salary", Descending). 914 + Limit(50) 915 + 916 + sql, args, err := s.Build() 917 + if err != nil { 918 + t.Fatalf("Failed to build query: %v", err) 919 + } 920 + 921 + start := time.Now() 922 + rows, err := db.Query(sql, args...) 923 + if err != nil { 924 + t.Fatalf("Failed to execute query: %v", err) 925 + } 926 + defer rows.Close() 927 + 928 + count := 0 929 + for rows.Next() { 930 + count++ 931 + } 932 + queryTime := time.Since(start) 933 + 934 + t.Logf("Complex query returned %d rows in %v", count, queryTime) 935 + if queryTime > time.Millisecond*200 { 936 + t.Logf("Complex query took longer than expected: %v", queryTime) 937 + } 938 + }) 939 + }