diff --git a/README.md b/README.md index 0b33873..a33943e 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ pgm -version ### 1. Create Your Schema -Create a SQL schema file `schema.sql`: +Create a SQL schema file `schema.sql` or use the one created by [dbmate](https://github.com/amacneil/dbmate): ```sql CREATE TABLE users ( @@ -174,16 +174,49 @@ func main() { ## Important: Query Builder Lifecycle -⚠️ **Query builders are single-use and should not be reused:** +### ✅ Conditional Building (CORRECT) + +Query builders are **mutable by design** to support conditional query building: ```go -// ❌ WRONG - Don't reuse query builders -baseQuery := users.User.Select(users.ID, users.Email) -baseQuery.Where(users.ID.Eq(1)).First(ctx, &id1, &email1) -baseQuery.Where(users.Status.Eq(2)).First(ctx, &id2, &email2) -// Second query has BOTH WHERE clauses - incorrect behavior! +// ✅ CORRECT - Conditional building pattern +query := users.User.Select(users.ID, users.Email, users.Name) -// ✅ CORRECT - Create new query each time +// Add conditions based on filters +if nameFilter != "" { + query = query.Where(users.Name.Like("%" + nameFilter + "%")) +} + +if statusFilter > 0 { + query = query.Where(users.Status.Eq(statusFilter)) +} + +if sortByName { + query = query.OrderBy(users.Name.Asc()) +} + +// Execute the final query with all accumulated conditions +err := query.First(ctx, &id, &email, &name) +``` + +**This is the intended use!** The builder accumulates your conditions, which is powerful and flexible. + +### ❌ Unintentional Reuse (INCORRECT) + +Don't try to create a "base query" and reuse it for **multiple different queries**: + +```go +// ❌ WRONG - Trying to reuse for multiple separate queries +baseQuery := users.User.Select(users.ID, users.Email) + +// First query - adds ID condition +baseQuery.Where(users.ID.Eq(1)).First(ctx, &id1, &email1) + +// Second query - ALSO has ID=1 from above PLUS Status=2! +baseQuery.Where(users.Status.Eq(2)).First(ctx, &id2, &email2) +// This executes: WHERE users.id = 1 AND users.status = 2 (WRONG!) + +// ✅ CORRECT - Each separate query gets its own builder users.User.Select(users.ID, users.Email).Where(users.ID.Eq(1)).First(ctx, &id1, &email1) users.User.Select(users.ID, users.Email).Where(users.Status.Eq(2)).First(ctx, &id2, &email2) ``` diff --git a/pgm_test.go b/pgm_test.go index 7d03732..a2ee2fd 100644 --- a/pgm_test.go +++ b/pgm_test.go @@ -383,42 +383,6 @@ func TestConditionActionTypes(t *testing.T) { } } -// TestQueryBuilderReuse documents query builder reuse behavior -// This test verifies that query builders accumulate state and should NOT be reused -func TestQueryBuilderReuse(t *testing.T) { - t.Skip("Query builders are not designed for reuse - this test documents the limitation") - - // This test would require actual database tables to demonstrate the issue - // The behavior is: - // 1. Creating a base query builder - // 2. Adding a WHERE clause - // 3. Reusing the same builder with another WHERE clause - // 4. The second query would have BOTH WHERE clauses - // - // Example: - // baseQuery := users.User.Select(user.ID, user.Email) - // baseQuery.Where(user.ID.Eq(1)) // First query - // baseQuery.Where(user.Status.Eq(2)) // Second query has BOTH conditions! - // - // This is by design - query builders are mutable and single-use. - // Each query should create a new builder instance. -} - -// TestQueryBuilderThreadSafety documents that query builders are not thread-safe -func TestQueryBuilderThreadSafety(t *testing.T) { - t.Skip("Query builders are not thread-safe by design - this test documents the limitation") - - // Query builders accumulate state in their internal fields (where, args, etc.) - // and do not use any synchronization primitives. - // - // Sharing a query builder across goroutines will cause race conditions. - // - // CORRECT usage: Create a new query builder in each goroutine - // INCORRECT usage: Share a query builder variable across goroutines - // - // The connection pool itself IS thread-safe and can be used concurrently. -} - // TestSelectQueryBuilderBasics tests basic query building func TestSelectQueryBuilderBasics(t *testing.T) { // Create a test table