database
This commit is contained in:
260
DATABASE.md
260
DATABASE.md
@@ -2,221 +2,135 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the clean database schema for BREEDR. **NO migrations** - fresh installs create the correct schema automatically.
|
||||
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 the separate `parents` table. This design:
|
||||
- Keeps the schema clean and normalized
|
||||
- Allows flexible parent relationships
|
||||
- Supports future extensions (multiple sires, surrogates, etc.)
|
||||
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).
|
||||
|
||||
Core registry for all dogs.
|
||||
|
||||
```sql
|
||||
CREATE TABLE dogs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
registration_number TEXT UNIQUE,
|
||||
breed TEXT NOT NULL,
|
||||
sex TEXT NOT NULL CHECK(sex IN ('male', 'female')),
|
||||
birth_date DATE,
|
||||
color TEXT,
|
||||
microchip TEXT,
|
||||
photo_urls TEXT, -- JSON array
|
||||
notes TEXT,
|
||||
litter_id INTEGER, -- Links to litters table
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (litter_id) REFERENCES litters(id) ON DELETE SET NULL
|
||||
);
|
||||
```
|
||||
|
||||
**Important:** NO `sire_id` or `dam_id` columns!
|
||||
| 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.
|
||||
Stores sire/dam relationships for every dog.
|
||||
|
||||
```sql
|
||||
CREATE TABLE parents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
dog_id INTEGER NOT NULL, -- The puppy
|
||||
parent_id INTEGER NOT NULL, -- The parent
|
||||
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) ON DELETE CASCADE,
|
||||
FOREIGN KEY (parent_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
||||
UNIQUE(dog_id, parent_type) -- One sire, one dam per dog
|
||||
FOREIGN KEY (dog_id) REFERENCES dogs(id),
|
||||
FOREIGN KEY (parent_id) REFERENCES dogs(id),
|
||||
UNIQUE(dog_id, parent_type)
|
||||
);
|
||||
```
|
||||
|
||||
### litters
|
||||
### breeding_records
|
||||
Tracks mating attempts/plans.
|
||||
|
||||
Breeding records and litter tracking.
|
||||
| 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,
|
||||
breeding_date DATE NOT NULL,
|
||||
whelping_date DATE,
|
||||
puppy_count INTEGER DEFAULT 0,
|
||||
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,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (sire_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (dam_id) REFERENCES dogs(id) ON DELETE CASCADE
|
||||
FOREIGN KEY (breeding_id) REFERENCES breeding_records(id),
|
||||
FOREIGN KEY (sire_id) REFERENCES dogs(id),
|
||||
FOREIGN KEY (dam_id) REFERENCES dogs(id)
|
||||
);
|
||||
```
|
||||
|
||||
### health_records
|
||||
### health_records (OFA & General)
|
||||
Tracks medical tests and certifications (Hips, Elbows, Heart, Eyes, etc).
|
||||
|
||||
Health tests, vaccinations, exams, treatments.
|
||||
| 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 health_records (
|
||||
CREATE TABLE genetic_tests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
dog_id INTEGER NOT NULL,
|
||||
record_type TEXT NOT NULL CHECK(record_type IN ('test', 'vaccination', 'exam', 'treatment', 'certification')),
|
||||
test_name TEXT,
|
||||
test_date DATE NOT NULL,
|
||||
result TEXT,
|
||||
document_url TEXT,
|
||||
notes TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE
|
||||
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.
|
||||
|
||||
Female heat cycle tracking for breeding timing.
|
||||
### settings
|
||||
Global kennel configuration (Single-row table).
|
||||
- `kennel_name`, `kennel_tagline`, `kennel_address`, `owner_name`, etc.
|
||||
|
||||
```sql
|
||||
CREATE TABLE heat_cycles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
dog_id INTEGER NOT NULL,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE,
|
||||
progesterone_peak_date DATE,
|
||||
breeding_date DATE,
|
||||
breeding_successful INTEGER DEFAULT 0,
|
||||
notes TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
---
|
||||
|
||||
### traits
|
||||
## Migration Policy
|
||||
|
||||
Genetic trait tracking and inheritance.
|
||||
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.
|
||||
|
||||
```sql
|
||||
CREATE TABLE traits (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
dog_id INTEGER NOT NULL,
|
||||
trait_category TEXT NOT NULL,
|
||||
trait_name TEXT NOT NULL,
|
||||
trait_value TEXT NOT NULL,
|
||||
inherited_from INTEGER, -- Parent dog ID
|
||||
notes TEXT,
|
||||
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (inherited_from) REFERENCES dogs(id) ON DELETE SET NULL
|
||||
);
|
||||
```
|
||||
|
||||
## API Usage
|
||||
|
||||
### Creating a Dog with Parents
|
||||
|
||||
```javascript
|
||||
POST /api/dogs
|
||||
{
|
||||
"name": "Puppy Name",
|
||||
"breed": "Breed Name",
|
||||
"sex": "male",
|
||||
"sire_id": 5, // Parent male dog ID
|
||||
"dam_id": 8, // Parent female dog ID
|
||||
"litter_id": 2 // Optional: link to litter
|
||||
}
|
||||
```
|
||||
|
||||
The API route automatically:
|
||||
1. Inserts the dog into `dogs` table (without sire/dam columns)
|
||||
2. Creates entries in `parents` table linking to sire and dam
|
||||
|
||||
### Querying Parents
|
||||
|
||||
```sql
|
||||
-- Get a dog's parents
|
||||
SELECT p.parent_type, d.*
|
||||
FROM parents p
|
||||
JOIN dogs d ON p.parent_id = d.id
|
||||
WHERE p.dog_id = ?;
|
||||
|
||||
-- Get a dog's offspring
|
||||
SELECT d.*
|
||||
FROM dogs d
|
||||
JOIN parents p ON d.id = p.dog_id
|
||||
WHERE p.parent_id = ?;
|
||||
```
|
||||
|
||||
## Fresh Install
|
||||
|
||||
For a fresh install:
|
||||
|
||||
1. **Delete the old database** (if upgrading):
|
||||
```bash
|
||||
rm data/breedr.db
|
||||
```
|
||||
|
||||
2. **Start the server** - it will create the correct schema automatically:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. **Verify the schema**:
|
||||
```bash
|
||||
sqlite3 data/breedr.db ".schema dogs"
|
||||
```
|
||||
|
||||
You should see `litter_id` but **NO** `sire_id` or `dam_id` columns.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "no such column: weight" or "no such column: sire_id"
|
||||
|
||||
**Solution:** Your database has an old schema. Delete it and let the app recreate it:
|
||||
|
||||
```bash
|
||||
rm data/breedr.db
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Parent relationships not saving
|
||||
|
||||
Check server logs. You should see:
|
||||
```
|
||||
✓ Dog inserted with ID: 123
|
||||
Adding sire relationship: dog 123 -> sire 5
|
||||
✓ Sire relationship added
|
||||
Adding dam relationship: dog 123 -> dam 8
|
||||
✓ Dam relationship added
|
||||
```
|
||||
|
||||
If relationships aren't being created, check that `sire_id` and `dam_id` are being sent in the API request.
|
||||
|
||||
## Database Files
|
||||
|
||||
- `server/db/init.js` - Creates clean schema, no migrations
|
||||
- `server/routes/dogs.js` - Handles parent relationships via `parents` table
|
||||
- `server/index.js` - Initializes database on startup
|
||||
|
||||
**NO MIGRATIONS!** The init file is the source of truth.
|
||||
---
|
||||
*Last Updated: March 12, 2026*
|
||||
|
||||
Reference in New Issue
Block a user