duckdb.md (2879B)
1 # DuckDB in Corpus 2 3 Corpus uses [DuckDB](https://duckdb.org/) as its primary analytical database. DuckDB's columnar storage and efficient query engine allow Corpus to provide fast filtering, pagination, and statistics over large sets of listening history data. 4 5 ## Database Schema 6 7 The database consists of two main tables: 8 9 ### `scrobbles` 10 Stores the raw listening history synced from ListenBrainz and/or Last.fm. 11 12 | Column | Type | Description | 13 | :--- | :--- | :--- | 14 | `listened_at` | BIGINT | Unix timestamp (Primary Key) | 15 | `track_name` | VARCHAR | Name of the track | 16 | `artist_name` | VARCHAR | Name of the artist | 17 | `release_name` | VARCHAR | Name of the album/release | 18 | `release_mbid` | VARCHAR | MusicBrainz Release ID | 19 | `caa_release_mbid` | VARCHAR | Cover Art Archive Release ID | 20 21 ### `release_metadata` 22 Stores enriched metadata fetched from MusicBrainz, Last.fm, and Discogs. 23 24 | Column | Type | Description | 25 | :--- | :--- | :--- | 26 | `release_mbid` | VARCHAR | MusicBrainz Release ID (Primary Key) | 27 | `genre` | VARCHAR | Primary genre | 28 | `label` | VARCHAR | Record label | 29 | `release_year` | INTEGER | Year of release | 30 | `genre_checked_at` | INTEGER | Timestamp of last enrichment attempt | 31 32 ### `api_tokens` 33 Stores hashed user API tokens for scrobble submission. 34 35 | Column | Type | Description | 36 | :--- | :--- | :--- | 37 | `slug` | VARCHAR | User slug (Primary Key) | 38 | `hashed_token` | VARCHAR | SHA-256 hash of the API token (Unique) | 39 40 ## Application Usage 41 42 The application interacts with DuckDB via a PureScript FFI layer (`src/Db.js` and `src/Db.purs`). 43 - **BigInt Handling**: Since DuckDB returns `BIGINT` as JavaScript `BigInt`, the FFI layer converts these to `Number` to ensure compatibility with standard JSON serialization. 44 - **Background Enrichment**: The server identifies "unenriched" scrobbles (those with an MBID but no metadata) and performs background updates to the `release_metadata` table. 45 46 ## Common Analytical Queries 47 48 You can run these queries directly against your `corpus.db` file using the DuckDB CLI or any compatible tool. 49 50 ### Top 10 Artists of All Time 51 ```sql 52 SELECT artist_name, count(*) as play_count 53 FROM scrobbles 54 GROUP BY artist_name 55 ORDER BY play_count DESC 56 LIMIT 10; 57 ``` 58 59 ### Listening Activity by Hour 60 ```sql 61 SELECT 62 extract('hour' from to_timestamp(listened_at)) as hour, 63 count(*) as count 64 FROM scrobbles 65 GROUP BY hour 66 ORDER BY hour; 67 ``` 68 69 ### Genre Distribution 70 ```sql 71 SELECT rm.genre, count(*) as count 72 FROM scrobbles s 73 JOIN release_metadata rm ON s.release_mbid = rm.release_mbid 74 WHERE rm.genre IS NOT NULL 75 GROUP BY rm.genre 76 ORDER BY count DESC; 77 ``` 78 79 ### MBID Enrichment Coverage 80 ```sql 81 SELECT 82 count(*) as total, 83 count(release_mbid) FILTER (WHERE release_mbid != '') as with_mbid, 84 (count(release_mbid) FILTER (WHERE release_mbid != '')::FLOAT / count(*)) * 100 as percentage 85 FROM scrobbles; 86 ```