-- 0058_track_fingerprints.up.sql — an acoustic identity per track (Scribe -- milestone #400: #3905, #3906). -- -- A table of its own rather than columns on tracks, for the hot path's sake: -- tracks is read with SELECT * by eight queries, among them ListTracksByAlbum, -- SearchTracks and GetTracksByIDs — album pages, search, the Subsonic surface. -- A ~4 KB chromaprint column on tracks would be de-TOASTed on every one of -- those reads to carry a value only the duplicate sweep ever looks at. -- -- What a row means, which the backfill depends on: -- no row never fingerprinted -- fingerprint_version < current derived by an older method; re-derive it -- fingerprint_version = current attempted; a NULL value means that tool -- failed on this file, and it is not retried -- until the file changes -- A failure that says nothing about the file — a timeout, a cancelled scan, a -- missing binary — writes no row at all, so the backfill tries again. CREATE TABLE track_fingerprints ( -- CASCADE is right here, unlike for the likes and play history M400's -- merge has to carry across: a fingerprint describes one file's bytes and -- means nothing once that file's row is gone. track_id uuid PRIMARY KEY REFERENCES tracks (id) ON DELETE CASCADE, -- SHA-256 of the ENCODED audio packets (ffmpeg -c:a copy -f hash), not of -- decoded samples. internal/library/fingerprint.go says why. audio_stream_sha256 bytea CHECK (audio_stream_sha256 IS NULL OR octet_length(audio_stream_sha256) = 32), -- fpcalc -raw -signed: the same 32 bits per item, stored signed because -- integer is. chromaprint integer[], fingerprint_version smallint NOT NULL, computed_at timestamptz NOT NULL DEFAULT now() ); -- The exact duplicate tier is an equality match on this column. Partial -- because a NULL is never looked up — it only means the hash was not taken. CREATE INDEX track_fingerprints_audio_stream_sha256 ON track_fingerprints (audio_stream_sha256) WHERE audio_stream_sha256 IS NOT NULL;