Overview
PFP Checker uses SQLite as its embedded database, managed through SQLx with compile-time query verification. The schema tracks users, servers, profile pictures, server icons, and username changes with full historical records.All migrations are located in the
migrations/ directory and are automatically applied on application startup.Database Tables
User Table
Tracks Discord users being monitored for profile picture and username changes. Migration:20240506183228_initial.sql
| Column | Type | Description | Constraints |
|---|---|---|---|
discordId | INTEGER | Discord user ID (snowflake) | PRIMARY KEY |
trackedSince | INTEGER | Unix timestamp when tracking started |
- Check if a user is being monitored
- Query all monitored users for updates
- Store when monitoring began
ProfilePicture Table
Stores historical profile picture data for tracked users. Migrations:- Initial:
20240506183228_initial.sql - Updated PK:
20241118130945_update_primary_key_of_profile_pictures.sql
| Column | Type | Description | Constraints |
|---|---|---|---|
checksum | TEXT | SHA-1 hash of image data | Part of PRIMARY KEY |
userId | INTEGER | Discord user ID | Part of PRIMARY KEY, FOREIGN KEY |
changedAt | INTEGER | Unix timestamp of change | Part of PRIMARY KEY |
link | TEXT | ImgBB URL to hosted image |
- Composite Primary Key:
(checksum, changedAt, userId)allows the same user to reuse a previous profile picture at different times - Cascade Deletion: When a user is removed from monitoring, all their profile pictures are deleted automatically
- Checksum-based Deduplication: SHA-1 checksums prevent storing duplicate image URLs for identical images
- Track profile picture history
- Calculate statistics (change frequency)
- Display historical images
- Detect when users revert to previous pictures
UsernameChange Table
Records username/display name changes for tracked users. Migration:20240908172815_add_username_changes.sql
| Column | Type | Description | Constraints |
|---|---|---|---|
userId | INTEGER | Discord user ID | FOREIGN KEY |
changedAt | INTEGER | Unix timestamp of change | |
username | TEXT | Display name at this time |
- No Primary Key: Allows tracking multiple records at the same timestamp if needed
- Cascade Deletion: Usernames are deleted when user monitoring stops
- Nullable Columns: SQLite allows NULL values for tracking flexibility
- Display username history
- Track how often users change names
- Show usernames in chronological order
Server Table
Tracks Discord servers (guilds) being monitored for icon changes. Migration:20251119225006_add_server_tracking.sql
| Column | Type | Description | Constraints |
|---|---|---|---|
serverId | INTEGER | Discord guild ID (snowflake) | PRIMARY KEY |
trackedSince | INTEGER | Unix timestamp when tracking started |
- Check if a server is being monitored
- Query all monitored servers for updates
- Store when monitoring began
ServerPicture Table
Stores historical server icon data for tracked servers. Migrations:- Table creation:
20251119225006_add_server_tracking.sql - Index optimization:
20251119233447_add_server_picture_index.sql
| Column | Type | Description | Constraints |
|---|---|---|---|
checksum | TEXT | SHA-1 hash of icon image | Part of PRIMARY KEY |
serverId | INTEGER | Discord guild ID | Part of PRIMARY KEY, FOREIGN KEY |
changedAt | INTEGER | Unix timestamp of change | Part of PRIMARY KEY |
link | TEXT | ImgBB URL to hosted icon |
idx_ServerPicture_serverId_changedAt: Optimizes queries byserverIdwithORDER BY changedAt DESC
- Mirrors ProfilePicture Design: Same composite primary key pattern for consistency
- Performance Index: Dedicated index for common query pattern (history ordered by date)
- Cascade Deletion: Icons deleted when server monitoring stops
- Track server icon history
- Calculate icon change statistics
- Display historical server icons
- Optimize queries with descending date order
Entity Relationships
Relationships:- User → ProfilePicture: One-to-Many with cascade delete
- User → UsernameChange: One-to-Many with cascade delete
- Server → ServerPicture: One-to-Many with cascade delete
All foreign keys use
ON DELETE CASCADE to automatically clean up historical records when monitoring stops.Migration System
PFP Checker uses SQLx’s migration system for managing database schema changes.Migration Files
Migrations are stored inmigrations/ with timestamp-based naming:
- Format:
{timestamp}_{description}.sql - Timestamp:
YYYYMMDDHHMMSS - Description: Snake_case summary of changes
Migration History
20240506183228_initial.sql - Initial Schema
20240506183228_initial.sql - Initial Schema
Creates the foundational tables:
Usertable for tracking monitored usersProfilePicturetable with initial primary key design
(checksum, userId)20240908172815_add_username_changes.sql - Username Tracking
20240908172815_add_username_changes.sql - Username Tracking
Adds username/display name tracking:
UsernameChangetable for historical username records- Foreign key relationship to User table
- Cascade deletion for cleanup
20241118130945_update_primary_key_of_profile_pictures.sql - Enhanced PK
20241118130945_update_primary_key_of_profile_pictures.sql - Enhanced PK
Updates ProfilePicture primary key to support reusing previous images:New Primary Key:
(checksum, changedAt, userId)This migration:- Creates new table with updated schema
- Copies all existing data
- Drops old table
- Renames new table to original name
20251119225006_add_server_tracking.sql - Server Monitoring
20251119225006_add_server_tracking.sql - Server Monitoring
Adds server/guild icon tracking:
Servertable for monitored guildsServerPicturetable mirroring ProfilePicture design- Composite primary key from the start:
(checksum, changedAt, serverId)
20251119233447_add_server_picture_index.sql - Query Optimization
20251119233447_add_server_picture_index.sql - Query Optimization
Adds performance index for common query pattern:Purpose: Optimizes
WHERE serverId = ? ORDER BY changedAt DESC queries used in history commands.Automatic Migration on Startup
Migrations are automatically applied when the bot starts (src/db/connection.rs:16-17):
- Database schema is always up to date
- No manual migration steps required
- Safe deployment of new versions
Working with Migrations
Creating a New Migration
Generate migration file
Use SQLx CLI to create a timestamped migration:This creates a new file:
migrations/{timestamp}_your_migration_name.sqlWrite SQL changes
Edit the generated file and add your schema changes:For complex changes, consider:
- Creating temporary tables
- Copying data
- Dropping old tables
- Renaming tables
Test the migration
Reset the database and run all migrations:Verify:
- Migration applies successfully
- Data is preserved (if applicable)
- Indexes are created
- Foreign keys work correctly
Running Migrations Manually
Apply pending migrations:Migration Best Practices
-
Make migrations idempotent: Use
IF NOT EXISTSwhere possible - Preserve data: When restructuring tables, copy data to temporary tables first
- Test thoroughly: Run migrations on test databases before production
- Document complex changes: Add comments explaining why changes were made
- Keep migrations focused: One logical change per migration
- Consider backwards compatibility: Plan for rollback scenarios
Database Queries in Code
SQLx provides compile-time verified queries:Macro-based Queries
Type-safe queries with automatic result types:Parameterized Queries
Prevent SQL injection with bound parameters:Dynamic Queries
For runtime-constructed queries (use sparingly):Database Performance
Connection Pooling
The bot uses a connection pool with 5 connections (src/db/connection.rs:7-8):
- Balance between concurrency and resource usage
- Sufficient for command handlers + background updates
- SQLite handles concurrent reads well
Indexing Strategy
Currently one explicit index:idx_ServerPicture_serverId_changedAt: Optimizes server history queries
- Primary keys are automatically indexed
- Foreign keys benefit from primary key indexes
Query Optimization Tips
- Use prepared statements: SQLx automatically prepares queries
- Limit result sets: Use
LIMITfor paginated results - Order in SQL: Let the database handle sorting
- Leverage indexes: Design queries to use existing indexes
- Batch operations: Fetch multiple records in one query when possible
Backup and Maintenance
Backing Up the Database
SQLite database is a single file:Database Size Management
The database grows as more images are tracked. Consider:- Monitoring disk usage
- Implementing data retention policies
- Archiving old records
- Vacuuming to reclaim space:
Next Steps
Architecture Guide
Learn how the database integrates with the codebase
Setup Guide
Set up your development environment with database
Contributing
Learn how to contribute database changes
Commands
See how commands interact with the database