DataVersionEntity (data_version_v2)
Singleton entity that tracks the currently imported metadata package version. Stores version information, import metadata, and statistics about the imported dataset.
Purpose
DataVersionEntity provides version tracking:
- Version identifier - Which metadata package is installed
- Import metadata - When imported, git commit, build info
- Statistics - Total shows, venues, files imported
- Update detection - Determine if newer version available
- Audit trail - Track data provenance
Why Singleton: Only one version can be active at a time. Fixed id = 1 ensures single row.
Schema
Table Definition
CREATE TABLE data_version_v2 (
id INTEGER PRIMARY KEY DEFAULT 1,
-- Version info
data_version TEXT NOT NULL,
package_name TEXT NOT NULL,
version_type TEXT NOT NULL,
description TEXT,
-- Import info
imported_at INTEGER NOT NULL,
git_commit TEXT,
git_tag TEXT,
build_timestamp TEXT,
-- Statistics
total_shows INTEGER NOT NULL DEFAULT 0,
total_venues INTEGER NOT NULL DEFAULT 0,
total_files INTEGER NOT NULL DEFAULT 0,
total_size_bytes INTEGER NOT NULL DEFAULT 0,
CHECK (id = 1) -- Enforce singleton
);
Singleton Constraint: id = 1 and PRIMARY KEY ensure only one row exists
No Indexes: Single-row table doesn't need indexes
Fields
Primary Key
id (INTEGER, PRIMARY KEY, DEFAULT 1, CHECK = 1)
Singleton identifier - always 1.
Value: 1 (only value allowed)
Constraint: CHECK (id = 1) prevents other values
Usage: Enforces single-row table pattern
Version Information
data_version (TEXT, NOT NULL)
Semantic version of metadata package.
Format: Semantic versioning (MAJOR.MINOR.PATCH)
Example: "2.0.0", "2.1.3"
Source: manifest.json from dead-metadata package
Usage: - Display "Data version: 2.0.0" - Compare with remote version for updates - Breaking change detection (MAJOR version changes)
package_name (TEXT, NOT NULL)
Name of metadata package.
Value: "Deadly Metadata" (constant)
Source: manifest.json
Usage: Display, logging
version_type (TEXT, NOT NULL)
Release type identifier.
Values: "release", "beta", "dev", "snapshot"
Example: "release"
Source: manifest.json
Usage: - Display badge ("Beta" warning) - Update channel selection (beta users get beta updates) - Analytics
description (TEXT, NULLABLE)
Human-readable description of this version.
Example: "September 2024 update: Added 50 new shows from Archive.org, updated ratings, fixed venue data for Fillmore East."
Source: manifest.json (optional field)
Nullable: Older packages may not have description
Usage: Display in "About" screen, changelog
Import Metadata
imported_at (INTEGER, NOT NULL)
Timestamp when metadata was imported.
Format: Unix timestamp (milliseconds)
Example: 1678901234000
Source: System.currentTimeMillis() during import
Usage: - Display "Data imported March 15, 2023" - Stale data detection ("Last updated 90 days ago") - Analytics
git_commit (TEXT, NULLABLE)
Git commit SHA from dead-metadata repository.
Format: 40-character hexadecimal SHA
Example: "a3f2d8b1c4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
Source: manifest.json (optional field)
Nullable: May not be present in all builds
Usage: - Exact provenance tracking - Link to GitHub commit - Debugging (reproduce exact dataset)
git_tag (TEXT, NULLABLE)
Git tag from dead-metadata repository.
Format: Tag name
Example: "v2.0.0", "v2.1.3-beta"
Source: manifest.json (optional field)
Nullable: Dev builds may not have tag
Usage:
- Display "Release v2.0.0"
- Link to GitHub release notes
- Correspondence with data_version
build_timestamp (TEXT, NULLABLE)
ISO timestamp when metadata package was built.
Format: ISO 8601 timestamp
Example: "2024-03-15T10:30:45Z"
Source: manifest.json (optional field)
Nullable: May not be present
Usage: - Display "Built March 15, 2024" - Provenance tracking - Debugging
Statistics
total_shows (INTEGER, NOT NULL, DEFAULT 0)
Total number of shows imported.
Example: 2400
Source: Counted during import from shows.json
Usage: - Display "2,400 shows in catalog" - Sanity check (detect incomplete imports) - Analytics
total_venues (INTEGER, NOT NULL, DEFAULT 0)
Total number of unique venues in dataset.
Example: 900
Source: Counted during import (distinct venue IDs)
Usage: - Display "900 venues" - Statistics page - Analytics
total_files (INTEGER, NOT NULL, DEFAULT 0)
Total number of files in metadata ZIP.
Example: 12
Source: Counted during ZIP extraction
Usage: - Import verification - Debugging (detect corrupted ZIPs)
total_size_bytes (INTEGER, NOT NULL, DEFAULT 0)
Total uncompressed size of metadata files in bytes.
Example: 52428800 (50 MB)
Source: Sum of file sizes from ZIP
Usage: - Display "50 MB of data" - Storage statistics - Download progress calculation
Singleton Pattern
Why Singleton?
Requirement: Only one metadata version active at a time
Alternatives: - Multiple rows with "active" flag → complexity, potential bugs - No table, store in SharedPreferences → inconsistent with database schema - Separate table per version → schema migration nightmare
Chosen Approach: Single row with id = 1 constraint
Enforcement
PRIMARY KEY: Prevents duplicate id = 1 rows
CHECK Constraint: CHECK (id = 1) prevents other IDs
Insert Logic:
INSERT OR REPLACE INTO data_version_v2 (id, ...) VALUES (1, ...);
Always uses id = 1, so REPLACE strategy updates existing row
Common Queries
Get Current Version
SELECT * FROM data_version_v2 WHERE id = 1;
Performance: O(1) via PRIMARY KEY
Returns: Single row or NULL if no data imported
Get Version String
SELECT data_version FROM data_version_v2 WHERE id = 1;
Performance: O(1)
Returns: String like "2.0.0" or NULL
Check if Data Exists
SELECT COUNT(*) > 0 FROM data_version_v2;
Performance: Fast
Returns: true if any data imported, false otherwise
Usage: First-run detection
Update Version (Singleton Insert)
INSERT OR REPLACE INTO data_version_v2 (
id, data_version, package_name, version_type, description,
imported_at, git_commit, git_tag, build_timestamp,
total_shows, total_venues, total_files, total_size_bytes
) VALUES (
1, '2.0.0', 'Deadly Metadata', 'release', 'September 2024 update',
1678901234000, 'a3f2d8b...', 'v2.0.0', '2024-03-15T10:30:45Z',
2400, 900, 12, 52428800
);
Effect: Updates existing row (due to REPLACE strategy)
Usage: Import new metadata version
Delete Version (for reimport)
DELETE FROM data_version_v2;
Effect: Removes version record
Usage: Clear before reimporting, factory reset
Common Operations
Record Import
suspend fun recordImport(manifest: ManifestJson, stats: ImportStats) {
val entity = DataVersionEntity(
id = 1,
dataVersion = manifest.version,
packageName = manifest.packageName,
versionType = manifest.versionType,
description = manifest.description,
importedAt = System.currentTimeMillis(),
gitCommit = manifest.gitCommit,
gitTag = manifest.gitTag,
buildTimestamp = manifest.buildTimestamp,
totalShows = stats.showCount,
totalVenues = stats.venueCount,
totalFiles = stats.fileCount,
totalSizeBytes = stats.totalBytes
)
dataVersionDao.insertOrUpdate(entity)
}
Check for Updates
suspend fun checkForUpdate(remoteVersion: String): Boolean {
val currentVersion = dataVersionDao.getCurrentVersion() ?: return true
return compareVersions(currentVersion, remoteVersion) < 0
}
fun compareVersions(v1: String, v2: String): Int {
val parts1 = v1.split(".").map { it.toInt() }
val parts2 = v2.split(".").map { it.toInt() }
for (i in 0 until maxOf(parts1.size, parts2.size)) {
val p1 = parts1.getOrElse(i) { 0 }
val p2 = parts2.getOrElse(i) { 0 }
if (p1 != p2) return p1.compareTo(p2)
}
return 0
}
Display Version Info
suspend fun getVersionInfo(): String {
val version = dataVersionDao.getCurrentDataVersion() ?: return "No data imported"
return buildString {
append("Version: ${version.dataVersion}")
if (version.versionType != "release") {
append(" (${version.versionType})")
}
append("\n")
append("Imported: ${formatDate(version.importedAt)}")
append("\n")
append("Shows: ${version.totalShows.format()}")
append("\n")
append("Venues: ${version.totalVenues.format()}")
version.description?.let {
append("\n\n$it")
}
}
}
Detect First Run
suspend fun isFirstRun(): Boolean {
return !dataVersionDao.hasDataVersion()
}
// Usage
if (isFirstRun()) {
// Show onboarding, trigger initial data import
startDataImport()
}
Manifest Format
The manifest.json file in dead-metadata package provides version information:
Example manifest.json
{
"version": "2.0.0",
"packageName": "Deadly Metadata",
"versionType": "release",
"description": "September 2024 update: Added 50 new shows, updated ratings.",
"buildTimestamp": "2024-09-15T10:30:45Z",
"gitCommit": "a3f2d8b1c4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9",
"gitTag": "v2.0.0",
"files": [
{"name": "shows.json", "size": 5242880, "sha256": "abc123..."},
{"name": "recordings.json", "size": 3145728, "sha256": "def456..."}
],
"statistics": {
"totalShows": 2400,
"totalRecordings": 16800,
"totalVenues": 900,
"dateRange": {
"earliest": "1965-11-03",
"latest": "1995-07-09"
}
}
}
Parsing Manifest
@Serializable
data class ManifestJson(
val version: String,
val packageName: String,
val versionType: String,
val description: String? = null,
val buildTimestamp: String? = null,
val gitCommit: String? = null,
val gitTag: String? = null,
val files: List<FileInfo>? = null,
val statistics: Statistics? = null
)
@Serializable
data class FileInfo(
val name: String,
val size: Long,
val sha256: String
)
@Serializable
data class Statistics(
val totalShows: Int,
val totalRecordings: Int,
val totalVenues: Int
)
Update Flow
Checking for Updates
- Fetch remote
manifest.jsonfrom dead-metadata GitHub releases - Parse remote version
- Compare with local version
- If remote > local, prompt user to update
Performing Update
- Download new metadata ZIP
- Verify ZIP integrity (SHA256)
- Transaction begin
- Delete old data (shows, recordings, etc.)
- Import new data from ZIP
- Update
data_version_v2table with new version - Transaction commit
- Notify user of completion
Rollback on Failure
If import fails mid-transaction: - Transaction rolls back - Old data remains intact - Old version record unchanged - User can retry
Code Locations
Android
- Entity:
androidApp/v2/core/database/src/main/java/com/deadly/v2/core/database/entities/DataVersionEntity.kt:7 - DAO:
androidApp/v2/core/database/src/main/java/com/deadly/v2/core/database/dao/DataVersionDao.kt:10
iOS
TBD (to be implemented)
Implementation Notes
Why data_version_v2 Table Name?
Suffix _v2: Distinguishes from old V1 database schema
Migration Path: Allows V1 and V2 tables to coexist during migration
Future: If V3 schema needed, can use data_version_v3
Singleton vs Configuration
Alternative: Store version in app configuration/SharedPreferences
Why Database: - Transactional consistency (version updated atomically with data) - Schema documentation (version is part of data model) - Queryable (can JOIN with other tables if needed) - Backup/restore (version exported with data)
Version Comparison
Semantic Versioning: MAJOR.MINOR.PATCH
Breaking Changes: MAJOR version increment signals schema changes
Example:
- 2.0.0 → 2.1.0: Compatible update (new shows added)
- 2.1.0 → 3.0.0: Breaking change (schema changed, requires migration)
Implementation:
data class SemanticVersion(val major: Int, val minor: Int, val patch: Int) {
companion object {
fun parse(version: String): SemanticVersion {
val parts = version.split(".").map { it.toInt() }
return SemanticVersion(
major = parts.getOrElse(0) { 0 },
minor = parts.getOrElse(1) { 0 },
patch = parts.getOrElse(2) { 0 }
)
}
}
fun isBreakingChange(other: SemanticVersion): Boolean {
return other.major > this.major
}
operator fun compareTo(other: SemanticVersion): Int {
if (major != other.major) return major.compareTo(other.major)
if (minor != other.minor) return minor.compareTo(other.minor)
return patch.compareTo(other.patch)
}
}
See Also
- Data Sources - Manifest format details
- Design Philosophy - Why database-first approach
- ShowEntity - Main entity managed by version tracking