perf(audiobooks): skip audiobook_series upsert when unchanged
Reads the current row first; skips the INSERT/UPDATE when name and index already match. NULL = NULL handled via floatPtrEqual.
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/Silo-Server/silo-server/internal/idgen"
|
||||
"github.com/Silo-Server/silo-server/internal/models"
|
||||
"github.com/Silo-Server/silo-server/internal/titleutil"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// audiobookDiskFile is the on-disk projection used by audiobookFolderUnchanged.
|
||||
@@ -689,33 +690,64 @@ func (s *Scanner) upsertAudiobookSeries(ctx context.Context, contentID string, b
|
||||
if s.fileRepo == nil {
|
||||
return fmt.Errorf("fileRepo not configured on Scanner")
|
||||
}
|
||||
name := strings.TrimSpace(book.Series)
|
||||
if name == "" {
|
||||
_, err := s.fileRepo.Pool().Exec(ctx,
|
||||
`DELETE FROM audiobook_series WHERE content_id = $1`, contentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete audiobook_series row: %w", err)
|
||||
desiredName := strings.TrimSpace(book.Series)
|
||||
desiredIdx := parseSeriesIndex(book.SeriesPosition)
|
||||
|
||||
// Read current row.
|
||||
var currentName *string
|
||||
var currentIdx *float64
|
||||
err := s.fileRepo.Pool().QueryRow(ctx, `
|
||||
SELECT series_name, series_index FROM audiobook_series WHERE content_id = $1
|
||||
`, contentID).Scan(¤tName, ¤tIdx)
|
||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||
return fmt.Errorf("query audiobook_series: %w", err)
|
||||
}
|
||||
|
||||
if desiredName == "" {
|
||||
if currentName == nil {
|
||||
return nil // already absent
|
||||
}
|
||||
if _, delErr := s.fileRepo.Pool().Exec(ctx,
|
||||
`DELETE FROM audiobook_series WHERE content_id = $1`, contentID); delErr != nil {
|
||||
return fmt.Errorf("delete audiobook_series row: %w", delErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var idx any
|
||||
if v := parseSeriesIndex(book.SeriesPosition); v != nil {
|
||||
idx = *v
|
||||
|
||||
if currentName != nil && *currentName == desiredName && floatPtrEqual(currentIdx, desiredIdx) {
|
||||
return nil // identical row, skip the write
|
||||
}
|
||||
_, err := s.fileRepo.Pool().Exec(ctx, `
|
||||
|
||||
var idx any
|
||||
if desiredIdx != nil {
|
||||
idx = *desiredIdx
|
||||
}
|
||||
if _, err := s.fileRepo.Pool().Exec(ctx, `
|
||||
INSERT INTO audiobook_series (content_id, series_name, series_index, updated_at)
|
||||
VALUES ($1, $2, $3, NOW())
|
||||
ON CONFLICT (content_id) DO UPDATE SET
|
||||
series_name = EXCLUDED.series_name,
|
||||
series_index = EXCLUDED.series_index,
|
||||
updated_at = NOW()
|
||||
`, contentID, name, idx)
|
||||
if err != nil {
|
||||
`, contentID, desiredName, idx); err != nil {
|
||||
return fmt.Errorf("upsert audiobook_series row: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// floatPtrEqual returns true when two *float64 values represent the same
|
||||
// state — both nil, or both non-nil and equal. Used to skip audiobook_series
|
||||
// re-upserts when only the series_index needs to be NULL=NULL compared.
|
||||
func floatPtrEqual(a, b *float64) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
|
||||
// parseSeriesIndex extracts a leading numeric value from a freeform tag
|
||||
// like "5", "1.5", "2 of 8", or "1a". Returns nil when no leading number
|
||||
// is present so the audiobook_series.series_index column stays NULL rather
|
||||
|
||||
@@ -232,3 +232,27 @@ func TestAudiobookPeopleCreditsEqual(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloatPtrEqual(t *testing.T) {
|
||||
a := 1.5
|
||||
b := 1.5
|
||||
c := 2.0
|
||||
cases := []struct {
|
||||
name string
|
||||
x, y *float64
|
||||
want bool
|
||||
}{
|
||||
{"both nil", nil, nil, true},
|
||||
{"left nil", nil, &a, false},
|
||||
{"right nil", &a, nil, false},
|
||||
{"equal", &a, &b, true},
|
||||
{"unequal", &a, &c, false},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := floatPtrEqual(tt.x, tt.y); got != tt.want {
|
||||
t.Errorf("got %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user