# BREEDR Database Schema ## Overview This document describes the database schema for BREEDR. The application uses SQLite via `better-sqlite3`. **Safe Migrations:** The schema is maintained in `server/db/init.js`. On startup, the application automatically ensures all tables exist and all necessary columns are present using safe `ALTER TABLE` guards. ## Schema Design ### Core Principle: Parents Table Approach The `dogs` table **does NOT have sire/dam columns**. Parent relationships are stored in a separate `parents` table. This design: - Keeps the core dog data normalized. - Allows for flexible parent relationships. - Supports historical mapping where ancestors might not have full profiles initially. --- ## Tables ### dogs Core registry for all dogs (both in-kennel and external). | Column | Type | Description | |---|---|---| | id | INTEGER | Primary Key | | name | TEXT | Dog's call name or registered name | | registration_number | TEXT | Unique registration (AKC, etc.) | | breed | TEXT | Breed name | | sex | TEXT | 'male' or 'female' | | birth_date | TEXT | Date string | | color | TEXT | Coat color/markings | | microchip | TEXT | Identification number | | litter_id | INTEGER | FK to `litters.id` | | is_active | INTEGER | 1 = Active in kennel, 0 = Retired/Old | | is_champion | INTEGER | 1 = Titled champion | | is_external | INTEGER | 1 = External dog (pedigree only), 0 = Kennel dog | | chic_number | TEXT | CHIC certification number | | age_at_death | TEXT | Age if deceased | | cause_of_death | TEXT | Cause if deceased | | photo_urls | TEXT | JSON array of photo paths | | notes | TEXT | General observations | ### parents Stores sire/dam relationships for every dog. ```sql CREATE TABLE parents ( id INTEGER PRIMARY KEY AUTOINCREMENT, dog_id INTEGER NOT NULL, -- The puppy/child parent_id INTEGER NOT NULL, -- The parent dog parent_type TEXT NOT NULL CHECK(parent_type IN ('sire', 'dam')), FOREIGN KEY (dog_id) REFERENCES dogs(id), FOREIGN KEY (parent_id) REFERENCES dogs(id), UNIQUE(dog_id, parent_type) ); ``` ### breeding_records Tracks mating attempts/plans. | Column | Type | Description | |---|---|---| | sire_id | INTEGER | FK to `dogs.id` | | dam_id | INTEGER | FK to `dogs.id` | | breeding_date | TEXT | Date of mating | | due_date | TEXT | Estimated whelp date | | conception_method | TEXT | 'natural', 'ai', 'frozen', 'surgical' | ### litters Tracks successfully produced litters. ```sql CREATE TABLE litters ( id INTEGER PRIMARY KEY AUTOINCREMENT, breeding_id INTEGER, -- Optional link to breeding_record sire_id INTEGER NOT NULL, dam_id INTEGER NOT NULL, whelp_date TEXT, total_count INTEGER DEFAULT 0, male_count INTEGER DEFAULT 0, female_count INTEGER DEFAULT 0, stillborn_count INTEGER DEFAULT 0, notes TEXT, FOREIGN KEY (breeding_id) REFERENCES breeding_records(id), FOREIGN KEY (sire_id) REFERENCES dogs(id), FOREIGN KEY (dam_id) REFERENCES dogs(id) ); ``` ### health_records (OFA & General) Tracks medical tests and certifications (Hips, Elbows, Heart, Eyes, etc). | Column | Type | Description | |---|---|---| | record_type | TEXT | 'test', 'vaccination', 'exam', 'treatment', 'certification' | | test_type | TEXT | 'hip_ofa', 'eye_caer', etc. | | ofa_result | TEXT | Official rating (Excellent, Good, etc.) | | ofa_number | TEXT | Certification number | | expires_at | TEXT | Expiry for annual tests | | document_url | TEXT | Link to PDF or image scan | ### genetic_tests DNA panel results (Embark, etc). ```sql CREATE TABLE genetic_tests ( id INTEGER PRIMARY KEY AUTOINCREMENT, dog_id INTEGER NOT NULL, test_provider TEXT, -- Embark, PawPrint, etc. marker TEXT NOT NULL, -- PRA1, ICH1, etc. result TEXT NOT NULL CHECK(result IN ('clear', 'carrier', 'affected', 'not_tested')), FOREIGN KEY (dog_id) REFERENCES dogs(id) ); ``` ### heat_cycles Female heat cycle tracking. ### settings Global kennel configuration (Single-row table). - `kennel_name`, `kennel_tagline`, `kennel_address`, `owner_name`, etc. --- ## Migration Policy BREEDR uses a **Migration-Free** sync approach: 1. `init.js` defines the latest `CREATE TABLE` statements. 2. An `ADD COLUMN` loop checks for existing columns. 3. If a column is missing, it is injected via `ALTER TABLE`. 4. This ensures users can pull latest code and restart without losing data. --- *Last Updated: March 12, 2026*