From 71f1daab6eb916b025ac2ea6361fc82564ba28ac Mon Sep 17 00:00:00 2001 From: Nikolaj Vorobiev Date: Wed, 15 Apr 2026 14:31:50 +0200 Subject: [PATCH] fix(table): keep external pointer aliases valid after saveAll inserts --- table.go | 10 ++++++++-- tests/table_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/table.go b/table.go index 3e96fd5..b185f46 100644 --- a/table.go +++ b/table.go @@ -321,6 +321,10 @@ func (t *Table[T, P, I]) saveAll(ctx context.Context, records []P) error { for start := 0; start < len(insertRecords); start += chunkSize { end := min(start+chunkSize, len(insertRecords)) + // Preserve original pointers before GetAll replaces them with newly allocated structs. + originals := make([]P, end-start) + copy(originals, insertRecords[start:end]) + chunk := insertRecords[start:end] q := t.SQL. InsertRecords(chunk). @@ -331,9 +335,11 @@ func (t *Table[T, P, I]) saveAll(ctx context.Context, records []P) error { return fmt.Errorf("insert records: %w", err) } - // update original slice + // Copy DB-returned values into the original structs so external pointer + // aliases taken before Save remain valid (same behaviour as saveOne/GetOne). for i, rr := range chunk { - records[insertIndices[start+i]] = rr + *originals[i] = *rr + records[insertIndices[start+i]] = originals[i] } } diff --git a/tests/table_test.go b/tests/table_test.go index b3356a6..c1b0d02 100644 --- a/tests/table_test.go +++ b/tests/table_test.go @@ -68,6 +68,36 @@ func TestTable(t *testing.T) { } }) + t.Run("Save external alias updated", func(t *testing.T) { + t.Parallel() + account := &Account{Name: "Alias Account"} + err := db.Accounts.Save(ctx, account) + require.NoError(t, err) + + // Take aliases BEFORE Save — simulates real-world usage where callers + // keep a reference to the original pointer expecting it to be mutated. + slice := []*Article{ + {Author: "AliasFirst", AccountID: account.ID}, + {Author: "AliasSecond", AccountID: account.ID}, + } + alias0 := slice[0] + alias1 := slice[1] + + err = db.Articles.Save(ctx, slice...) + require.NoError(t, err) + + require.NotZero(t, slice[0].ID) + require.NotZero(t, slice[1].ID) + + // The alias pointer must be the same object as the slice element. + require.Same(t, slice[0], alias0, "alias0 should still point to the same struct as slice[0]") + require.Same(t, slice[1], alias1, "alias1 should still point to the same struct as slice[1]") + + // And it must carry the DB-assigned ID. + require.Equal(t, slice[0].ID, alias0.ID, "alias0.ID should reflect the DB-assigned value") + require.Equal(t, slice[1].ID, alias1.ID, "alias1.ID should reflect the DB-assigned value") + }) + t.Run("Save multiple", func(t *testing.T) { t.Parallel() // Create account.