From bd525771f7bc229348ccee5d0e6e0a021ca4a81c Mon Sep 17 00:00:00 2001 From: Denis Denisov Date: Mon, 9 Jun 2025 12:50:07 +0300 Subject: [PATCH 1/2] test(gorm-pg): Add comprehensive tests for PostgreSQL full-text search with various scenarios and workarounds to address known @@ operator issues; --- go.mod | 18 ++++---- main_test.go | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d1ba4316..c9b48131 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.23.0 toolchain go1.24.3 require ( - gorm.io/driver/mysql v1.5.7 - gorm.io/driver/postgres v1.5.11 - gorm.io/driver/sqlite v1.5.7 + gorm.io/driver/mysql v1.6.0 + gorm.io/driver/postgres v1.6.0 + gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlserver v1.6.0 gorm.io/gen v0.3.27 gorm.io/gorm v1.30.0 @@ -26,12 +26,12 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect - github.com/microsoft/go-mssqldb v1.8.1 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/tools v0.33.0 // indirect + github.com/microsoft/go-mssqldb v1.8.2 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/tools v0.34.0 // indirect gorm.io/datatypes v1.2.5 // indirect gorm.io/hints v1.1.2 // indirect gorm.io/plugin/dbresolver v1.6.0 // indirect diff --git a/main_test.go b/main_test.go index 60a388f7..10455e43 100644 --- a/main_test.go +++ b/main_test.go @@ -18,3 +18,123 @@ func TestGORM(t *testing.T) { t.Errorf("Failed, got error: %v", err) } } + +// TestPostgresFullTextSearch tests the issue with @@ operator in Raw SQL +func TestPostgresFullTextSearch(t *testing.T) { + // Skip for non-postgres drivers + if DB.Dialector.Name() != "postgres" { + t.Skip("Skipping test for non-postgres driver") + } + + // Create test data + user1 := User{Name: "search test user"} + user2 := User{Name: "another user"} + DB.Create(&user1) + DB.Create(&user2) + + // Test 1: Using Raw with @@ operator and parameters - this will fail due to the issue + t.Run("Raw with @@ operator and parameters", func(t *testing.T) { + var users []User + searchTerm := "search" + + // This query contains the @@ operator with parameters which causes issues + err := DB.Raw(` + SELECT * FROM users + WHERE ($1::text IS NULL OR to_tsvector('english', name) @@ plainto_tsquery('english', $1)) + `, searchTerm).Scan(&users).Error + + if err != nil { + t.Errorf("Full-text search with @@ operator and parameters failed: %v", err) + } else { + t.Logf("Found %d users with full-text search", len(users)) + if len(users) == 0 { + t.Error("Expected to find at least one user with full-text search") + } + } + }) + + // Test 2: Using Raw with @@ in SELECT clause - this will also fail + t.Run("Raw with @@ in SELECT clause", func(t *testing.T) { + var results []struct { + ID uint + Name string + Matches bool + } + searchTerm := "search" + + // This query contains the @@ operator in the SELECT clause + err := DB.Raw(` + SELECT id, name, + to_tsvector('english', name) @@ plainto_tsquery('english', $1) as matches + FROM users + `, searchTerm).Scan(&results).Error + + if err != nil { + t.Errorf("Full-text search with @@ in SELECT clause failed: %v", err) + } else { + t.Logf("Found %d results with @@ in SELECT", len(results)) + for _, r := range results { + t.Logf("User: %s, Matches: %v", r.Name, r.Matches) + } + } + }) + + // Test 3: Using Where with direct condition - this should work + t.Run("Where with direct condition", func(t *testing.T) { + var users []User + + err := DB.Where("name LIKE ?", "%search%").Find(&users).Error + + if err != nil { + t.Errorf("Simple LIKE search failed: %v", err) + } else { + t.Logf("Found %d users with LIKE search", len(users)) + if len(users) == 0 { + t.Error("Expected to find at least one user with LIKE search") + } + } + }) + + // Test 4: Using Where with full-text search - this should work + t.Run("Where with full-text search", func(t *testing.T) { + var users []User + searchTerm := "search" + + // Using Where with the @@ operator should work + err := DB.Where("to_tsvector('english', name) @@ plainto_tsquery('english', ?)", searchTerm).Find(&users).Error + + if err != nil { + t.Errorf("Full-text search with Where failed: %v", err) + } else { + t.Logf("Found %d users with Where full-text search", len(users)) + if len(users) == 0 { + t.Error("Expected to find at least one user with Where full-text search") + } + } + }) + + // Test 5: Workaround using ILIKE instead of @@ - this should work + t.Run("ILIKE workaround", func(t *testing.T) { + var users []User + searchTerm := "search" + + // Using ILIKE as a workaround + err := DB.Raw(` + SELECT * FROM users + WHERE ($1::text IS NULL OR name ILIKE '%' || $1 || '%') + `, searchTerm).Scan(&users).Error + + if err != nil { + t.Errorf("ILIKE workaround failed: %v", err) + } else { + t.Logf("Found %d users with ILIKE workaround", len(users)) + if len(users) == 0 { + t.Error("Expected to find at least one user with ILIKE workaround") + } + } + }) + + // Cleanup + DB.Unscoped().Delete(&user1) + DB.Unscoped().Delete(&user2) +} From 6b6957796b89401bfd6b1e5d0a440578a4e94a8a Mon Sep 17 00:00:00 2001 From: Denis Denisov Date: Mon, 9 Jun 2025 13:04:26 +0300 Subject: [PATCH 2/2] chore(workflows): Update GitHub Actions to use latest versions of setup-go, checkout, and cache actions --- .github/workflows/tests.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a1a33bf9..06d94333 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,12 +18,12 @@ jobs: steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: go mod pakcage cache uses: actions/cache@v4 @@ -62,15 +62,15 @@ jobs: steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: go mod pakcage cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('go.mod') }} @@ -106,15 +106,15 @@ jobs: steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: go mod pakcage cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('go.mod') }} @@ -150,15 +150,15 @@ jobs: steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: go mod pakcage cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('go.mod') }}