Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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]
}
}

Expand Down
30 changes: 30 additions & 0 deletions tests/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down