ShowEntity (shows)
The core entity representing a single Grateful Dead concert. This is the atomic unit of the application - everything else relates to or extends shows.
Purpose
ShowEntity stores comprehensive metadata about a Grateful Dead concert including:
- Date and venue - When and where the show happened
- Setlist and lineup - What was played and who played it
- Recording information - Available recordings and quality ratings
- Library status - Whether user has saved this show
Why Denormalized: Venue information is stored directly in show records (not a separate venues table) to eliminate JOINs for the most common query patterns. See Design Philosophy.
Schema
Table Definition
CREATE TABLE shows (
show_id TEXT PRIMARY KEY,
-- Date components
date TEXT NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
year_month TEXT NOT NULL,
-- Show metadata
band TEXT NOT NULL,
url TEXT,
-- Venue (denormalized)
venue_name TEXT NOT NULL,
city TEXT,
state TEXT,
country TEXT NOT NULL DEFAULT 'USA',
location_raw TEXT,
-- Setlist
setlist_status TEXT,
setlist_raw TEXT,
song_list TEXT,
-- Lineup
lineup_status TEXT,
lineup_raw TEXT,
member_list TEXT,
-- Multiple shows same date
show_sequence INTEGER NOT NULL DEFAULT 1,
-- Recording data
recordings_raw TEXT,
recording_count INTEGER NOT NULL DEFAULT 0,
best_recording_id TEXT,
average_rating REAL,
total_reviews INTEGER NOT NULL DEFAULT 0,
-- Library status (denormalized)
is_in_library INTEGER NOT NULL DEFAULT 0,
library_added_at INTEGER,
-- Timestamps
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
Indexes
CREATE INDEX idx_shows_date ON shows(date);
CREATE INDEX idx_shows_year ON shows(year);
CREATE INDEX idx_shows_year_month ON shows(year_month);
CREATE INDEX idx_shows_venue_name ON shows(venue_name);
CREATE INDEX idx_shows_city ON shows(city);
CREATE INDEX idx_shows_state ON shows(state);
Fields
Primary Key
show_id (TEXT, PRIMARY KEY)
Unique identifier for the show.
Format: {date}-{venue-slug}-{city-slug}-{state}-{country}
Example: "1977-05-08-barton-hall-cornell-u-ithaca-ny-usa"
Why This Format: - Self-describing (contains date and location) - URL-friendly (no special characters) - Globally unique (date + venue + location) - Human-readable for debugging
Generation: Created by metadata package during compilation, not by app.
Date Fields
date (TEXT, NOT NULL, INDEXED)
Full ISO date of the show.
Format: "YYYY-MM-DD"
Example: "1977-05-08"
Indexed: Yes - chronological browsing and date range queries
Usage: - Primary sorting field (chronological ordering) - Date range filters - "On this day in history" features - Next/previous show navigation
year (INTEGER, NOT NULL, INDEXED)
Year extracted from date.
Example: 1977
Why Separate Field: Fast filtering by year without string parsing.
Indexed: Yes - year-based browsing (e.g., "all 1977 shows")
Usage: - Browse by decade or specific year - Year-based statistics - "Best shows of 1977" queries
month (INTEGER, NOT NULL)
Month extracted from date (1-12).
Example: 5 (for May)
Why Separate Field: Fast month-based grouping and filtering.
Not Indexed: Typically queried with year (year_month index)
Usage: - Month-based statistics - "Shows in May" queries - Combined with year for "May 1977" queries
year_month (TEXT, NOT NULL, INDEXED)
Year and month combined for efficient monthly grouping.
Format: "YYYY-MM"
Example: "1977-05"
Why Separate Field: Faster than WHERE year = 1977 AND month = 5
Indexed: Yes - monthly browsing (e.g., "all May 1977 shows")
Usage: - Browse by month within year - "All shows in May 1977" - Monthly statistics and grouping
Show Metadata
band (TEXT, NOT NULL)
Band name.
Example: "Grateful Dead"
Note: Currently always "Grateful Dead" but field exists for potential future expansion (Jerry Garcia Band, side projects, etc.)
url (TEXT, NULLABLE)
External URL for the show (typically Jerry Garcia website).
Example: "https://jerrygarcia.com/show/1977-05-08/"
Nullable: Some shows don't have URLs
Usage: Deep link to external show information
Venue Fields (Denormalized)
These fields are denormalized from what could be a separate venues table. See Design Philosophy for rationale.
venue_name (TEXT, NOT NULL, INDEXED)
Full venue name.
Example: "Barton Hall, Cornell University"
Indexed: Yes - venue-based searches
Usage: - "All shows at Barton Hall" - Venue-based browsing - LIKE queries for venue search
city (TEXT, NULLABLE, INDEXED)
City where venue is located.
Example: "Ithaca"
Nullable: Some venues don't have city data
Indexed: Yes - city-based browsing
Usage: - "All shows in Ithaca" - City-based filtering - Regional browsing
state (TEXT, NULLABLE, INDEXED)
State/province abbreviation.
Example: "NY"
Format: Two-letter US state codes or full names for international
Nullable: Some venues outside US don't have state
Indexed: Yes - state-based browsing
Usage: - "All shows in New York" - State-based filtering - Regional statistics
country (TEXT, NOT NULL, DEFAULT 'USA')
Country code or name.
Example: "USA", "Canada", "England"
Default: "USA" (most shows are in US)
Not Indexed: Rarely queried (most shows are US)
location_raw (TEXT, NULLABLE)
Original location string from source data.
Example: "Ithaca, NY", "San Francisco, CA"
Why Stored: Preserves original formatting for display
Usage: - Display to user (more natural than "Ithaca NY USA") - Fallback if city/state parsing failed
Setlist Fields
setlist_status (TEXT, NULLABLE)
Status of setlist data availability.
Values: "found", "not_found", "partial", null
Usage: - UI indicator (show icon if setlist available) - Filter shows with complete setlists
setlist_raw (TEXT, NULLABLE)
Full setlist as JSON string.
Format: JSON array of sets with songs
Example:
[
{
"set": "Set 1",
"songs": [
{"name": "Scarlet Begonias", "segue": true},
{"name": "Fire on the Mountain", "segue": false}
]
}
]
Why JSON: Variable structure (2 sets, 3 sets, encores). See Design Philosophy.
Usage: - Parsed in app and displayed to user - Rich setlist UI with segues
song_list (TEXT, NULLABLE)
Comma-separated list of song names extracted from setlist.
Example: "Scarlet Begonias,Fire on the Mountain,Estimated Prophet"
Why Separate: Indexed by FTS4 for song-based searches
Usage: - FTS4 search: "find shows with Scarlet Begonias" - Quick song presence check
Not Queried Directly: Use FTS4 show_search table instead
Lineup Fields
lineup_status (TEXT, NULLABLE)
Status of lineup data availability.
Values: "found", "missing", null
Usage: UI indicator
lineup_raw (TEXT, NULLABLE)
Full lineup as JSON string.
Format: JSON array of members with instruments
Example:
[
{"name": "Jerry Garcia", "instruments": "guitar, vocals"},
{"name": "Bob Weir", "instruments": "guitar, vocals"}
]
Why JSON: Variable structure (guest musicians, substitutions)
Usage: Parsed and displayed to user
member_list (TEXT, NULLABLE)
Comma-separated list of member names extracted from lineup.
Example: "Jerry Garcia,Bob Weir,Phil Lesh"
Why Separate: Indexed by FTS4 for member-based searches
Usage: FTS4 search: "find shows with Donna Jean"
Multiple Shows Same Date
show_sequence (INTEGER, NOT NULL, DEFAULT 1)
Sequence number for multiple shows on same date at same venue.
Values: 1, 2, 3, ...
Example: If there were two shows at Fillmore on 1969-02-27:
- Morning show: show_sequence = 1
- Evening show: show_sequence = 2
Rare: Most dates have only one show (show_sequence = 1)
Usage: Distinguish between multiple shows on same date
Recording Fields
recordings_raw (TEXT, NULLABLE)
JSON array of recording IDs for this show.
Format: JSON string array
Example: ["gd1977-05-08.sbd.miller.97065.flac16", "gd1977-05-08.aud.unknown.89459.sbeok.flac16"]
Why JSON: Variable number of recordings per show (some have 1, some 20+)
Usage:
- Know which recordings exist
- Display count to user
- Not queried (use recordings table for detailed queries)
recording_count (INTEGER, NOT NULL, DEFAULT 0)
Total number of recordings available.
Example: 8
Why Precomputed: Avoid COUNT query on recordings table
Usage: - Display to user: "8 recordings available" - Filter shows with recordings vs without
best_recording_id (TEXT, NULLABLE)
Identifier of highest-rated recording for this show.
Example: "gd1977-05-08.sbd.miller.97065.flac16"
Why Precomputed: Default recording to play when user taps show
Nullable: Some shows have no recordings
Usage: - Auto-select best recording on show load - Quick access to recommended recording
average_rating (REAL, NULLABLE)
Average rating across all recordings for this show.
Example: 4.8 (out of 5.0)
Why Precomputed: Show quality indicator without querying recordings
Nullable: Shows with no rated recordings
Usage: - Display star rating to user - Sort shows by quality - "Best shows of 1977" queries
total_reviews (INTEGER, NOT NULL, DEFAULT 0)
Total review count across all recordings.
Example: 156
Why Precomputed: Avoid SUM query on recordings
Usage: - Display review count to user - Filter shows with many reviews
Library Status (Denormalized)
These fields are denormalized from library_shows table for performance. They must be kept in sync with library_shows table.
is_in_library (INTEGER, NOT NULL, DEFAULT 0)
Boolean flag (0/1) indicating if show is in user's library.
Values: 0 (false), 1 (true)
Why Denormalized: Fast library membership checks without JOIN
Usage: - Display heart icon in show list - Filter library shows - Quick membership check
Sync Required: When user adds/removes from library, update both:
1. INSERT/DELETE in library_shows table
2. UPDATE is_in_library flag in shows table
library_added_at (INTEGER, NULLABLE)
Timestamp when show was added to library.
Format: Unix timestamp (milliseconds)
Example: 1678901234000
Why Denormalized: Sort library shows without JOIN
Nullable: Null if not in library
Usage: - Sort library by "recently added" - Display "added on" date
Sync Required: Update when user adds to library
Timestamps
created_at (INTEGER, NOT NULL)
Timestamp when show record was created in database.
Format: Unix timestamp (milliseconds)
Example: 1678901234000
Set Once: During initial import, never changed
Usage: Debugging, data provenance
updated_at (INTEGER, NOT NULL)
Timestamp when show record was last updated.
Format: Unix timestamp (milliseconds)
Example: 1678901234567
Updated: When any show field changes (rare after initial import)
Usage: Debugging, change tracking
Relationships
Outgoing (Foreign Keys from Other Tables)
To RecordingEntity (1:N)
-- In recordings table
FOREIGN KEY (show_id) REFERENCES shows(show_id) ON DELETE CASCADE
Cardinality: One show has many recordings (average 6-8, some have 20+)
Cascade Behavior: DELETE CASCADE - if show deleted, all recordings deleted
Why: Recordings are meaningless without their show
Query Pattern:
SELECT * FROM recordings WHERE show_id = '1977-05-08-...' ORDER BY rating DESC;
See RecordingEntity for details.
To LibraryShowEntity (1:0..1)
-- In library_shows table
FOREIGN KEY (show_id) REFERENCES shows(show_id) ON DELETE CASCADE
Cardinality: One show has zero or one library entry
Cascade Behavior: DELETE CASCADE - if show deleted, library entry deleted
Why: Library entry is orphaned if show deleted
Query Pattern:
SELECT EXISTS(SELECT 1 FROM library_shows WHERE show_id = '1977-05-08-...');
See LibraryShowEntity for details.
To ShowSearchEntity (1:1)
Note: No formal foreign key (FTS4 tables don't support FKs), but conceptually 1:1 relationship.
Cardinality: One show has one FTS4 search entry
Linked By: show_id field in both tables
Query Pattern:
-- Search returns show IDs
SELECT show_id FROM show_search WHERE show_search MATCH 'Cornell';
-- Then fetch shows
SELECT * FROM shows WHERE show_id IN (...);
See ShowSearchEntity for details.
To RecentShowEntity (1:0..1)
Note: No foreign key (intentionally, to preserve history if show deleted)
Cardinality: One show has zero or one recent history entry
Linked By: show_id field in both tables
Query Pattern:
SELECT * FROM recent_shows WHERE show_id = '1977-05-08-...';
See RecentShowEntity for details.
Referenced By Collections (N:M via JSON)
Collections reference shows via JSON array in show_ids_json field.
No foreign key: JSON-based relationship, not enforced by database
Query Pattern:
-- Find collections containing show (inefficient, but rare query)
SELECT * FROM dead_collections WHERE show_ids_json LIKE '%1977-05-08-...%';
See DeadCollectionEntity for details.
Common Queries
Get Show by ID
SELECT * FROM shows WHERE show_id = '1977-05-08-barton-hall-cornell-u-ithaca-ny-usa';
Performance: O(1) via primary key index
Browse by Year
SELECT * FROM shows WHERE year = 1977 ORDER BY date;
Performance: Fast via year index
Browse by Month
SELECT * FROM shows WHERE year_month = '1977-05' ORDER BY date;
Performance: Fast via year_month index
Browse by Venue
SELECT * FROM shows WHERE venue_name LIKE '%Cornell%' ORDER BY date DESC;
Performance: Decent with venue_name index (prefix matches best)
Browse by Location
-- By city
SELECT * FROM shows WHERE city = 'Ithaca' ORDER BY date DESC;
-- By state
SELECT * FROM shows WHERE state = 'NY' ORDER BY date DESC;
Performance: Fast via city/state indexes
Get Top Rated Shows
SELECT * FROM shows
WHERE average_rating IS NOT NULL
ORDER BY average_rating DESC
LIMIT 20;
Performance: Table scan (no index on average_rating)
Note: Could add index if this query is frequent
Chronological Navigation
-- Next show
SELECT * FROM shows WHERE date > '1977-05-08' ORDER BY date ASC LIMIT 1;
-- Previous show
SELECT * FROM shows WHERE date < '1977-05-08' ORDER BY date DESC LIMIT 1;
Performance: Fast via date index with range scan
Code Locations
Android
- Entity:
androidApp/v2/core/database/src/main/java/com/deadly/v2/core/database/entities/ShowEntity.kt:19 - DAO:
androidApp/v2/core/database/src/main/java/com/deadly/v2/core/database/dao/ShowDao.kt:10 - Mappers:
androidApp/v2/core/database/src/main/java/com/deadly/v2/core/database/mappers/ShowMappers.kt
iOS
TBD (to be implemented)
Implementation Notes
JSON Parsing
setlist_raw and lineup_raw must be parsed with appropriate error handling:
// Android example
val setlist: Setlist? = try {
show.setlistRaw?.let { Json.decodeFromString<Setlist>(it) }
} catch (e: Exception) {
Log.e(TAG, "Failed to parse setlist for ${show.showId}", e)
null
}
iOS: Use JSONDecoder with similar error handling
Library Sync
When updating library status, update both tables:
// Add to library
transaction {
libraryDao.addToLibrary(LibraryShowEntity(showId = showId, ...))
showDao.updateLibraryStatus(showId, isInLibrary = true, addedAt = now)
}
// Remove from library
transaction {
libraryDao.removeFromLibraryById(showId)
showDao.updateLibraryStatus(showId, isInLibrary = false, addedAt = null)
}
See Also
- Design Philosophy - Why denormalized, why JSON
- RecordingEntity - Related recordings
- ShowSearchEntity - FTS4 search
- LibraryShowEntity - User library
- Data Sources - Where show data comes from