diff --git a/docs/MICROCHIP_FIX.md b/docs/MICROCHIP_FIX.md new file mode 100644 index 0000000..2af2964 --- /dev/null +++ b/docs/MICROCHIP_FIX.md @@ -0,0 +1,304 @@ +# Microchip Field Fix + +## Problem + +The microchip field in the dogs table had a `UNIQUE` constraint defined directly on the column: + +```sql +microchip TEXT UNIQUE +``` + +In SQLite, when a `UNIQUE` constraint is applied to a nullable column, **only one row can have a NULL value**. This caused the error: + +``` +UNIQUE constraint failed: dogs.microchip +``` + +When trying to add a second dog without a microchip number. + +--- + +## Solution + +Removed the inline `UNIQUE` constraint and replaced it with a **partial unique index** that only applies to non-NULL values: + +```sql +-- Column definition (no UNIQUE constraint) +microchip TEXT + +-- Partial unique index (only for non-NULL values) +CREATE UNIQUE INDEX idx_dogs_microchip +ON dogs(microchip) +WHERE microchip IS NOT NULL; +``` + +### Result: +- Multiple dogs can have no microchip (NULL values allowed) +- Dogs with microchips still cannot have duplicates +- Field is now truly optional + +--- + +## Migration Required + +If you have an existing database with the old schema, you **must run the migration** before the fix will work. + +### Option 1: Docker Container (Recommended) + +```bash +# Enter the running container +docker exec -it breedr sh + +# Run the migration script +node server/db/migrate_microchip.js + +# Exit container +exit + +# Restart the container to apply changes +docker restart breedr +``` + +### Option 2: Direct Node Execution + +```bash +cd /path/to/breedr +node server/db/migrate_microchip.js +``` + +### Option 3: Rebuild from Scratch (Data Loss) + +```bash +# Stop container +docker stop breedr + +# Remove old database +rm /mnt/user/appdata/breedr/breedr.db + +# Start container (will create fresh database) +docker start breedr +``` + +**Warning:** Option 3 deletes all data. Only use if you have no important data or have a backup. + +--- + +## What the Migration Does + +### Step-by-Step Process + +1. **Check Database Exists** - Skips if no database found +2. **Create New Table** - With corrected schema (no UNIQUE on microchip) +3. **Copy All Data** - Transfers all dogs from old table to new +4. **Drop Old Table** - Removes the table with bad constraint +5. **Rename New Table** - Makes new table the primary dogs table +6. **Create Partial Index** - Adds unique index only for non-NULL microchips +7. **Recreate Indexes** - Restores name and registration indexes +8. **Recreate Triggers** - Restores updated_at timestamp trigger + +### Safety Features + +- **Idempotent** - Can be run multiple times safely +- **Data Preservation** - All data is copied before old table is dropped +- **Foreign Keys** - Temporarily disabled during migration +- **Error Handling** - Clear error messages if something fails + +--- + +## Verification + +After migration, you should be able to: + +### Test 1: Add Dog Without Microchip + +```bash +curl -X POST http://localhost:3000/api/dogs \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Test Dog 1", + "breed": "Golden Retriever", + "sex": "male" + }' +``` + +**Expected:** Success (no microchip error) + +### Test 2: Add Another Dog Without Microchip + +```bash +curl -X POST http://localhost:3000/api/dogs \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Test Dog 2", + "breed": "Labrador", + "sex": "female" + }' +``` + +**Expected:** Success (multiple NULL microchips allowed) + +### Test 3: Add Dog With Microchip + +```bash +curl -X POST http://localhost:3000/api/dogs \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Test Dog 3", + "breed": "Beagle", + "sex": "male", + "microchip": "985112345678901" + }' +``` + +**Expected:** Success + +### Test 4: Try Duplicate Microchip + +```bash +curl -X POST http://localhost:3000/api/dogs \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Test Dog 4", + "breed": "Poodle", + "sex": "female", + "microchip": "985112345678901" + }' +``` + +**Expected:** Error (duplicate microchip not allowed) + +--- + +## Database Schema Comparison + +### Before (Broken) + +```sql +CREATE TABLE dogs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + breed TEXT NOT NULL, + sex TEXT NOT NULL, + microchip TEXT UNIQUE, -- ❌ Only one NULL allowed + ... +); +``` + +### After (Fixed) + +```sql +CREATE TABLE dogs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + breed TEXT NOT NULL, + sex TEXT NOT NULL, + microchip TEXT, -- ✓ Multiple NULLs allowed + ... +); + +-- Partial unique index +CREATE UNIQUE INDEX idx_dogs_microchip +ON dogs(microchip) +WHERE microchip IS NOT NULL; -- ✓ Only enforces uniqueness on non-NULL +``` + +--- + +## Technical Details + +### Why SQLite Behaves This Way + +From SQLite documentation: + +> For the purposes of UNIQUE constraints, NULL values are considered distinct from all other values, including other NULLs. However, when a UNIQUE constraint is defined on a column, SQLite treats NULL as a single value. + +This is a quirk of SQLite's implementation. PostgreSQL and MySQL allow multiple NULLs in UNIQUE columns by default. + +### Partial Index Solution + +Partial indexes (with WHERE clause) were introduced in SQLite 3.8.0 (2013). They allow us to: + +1. Create an index that only includes certain rows +2. In this case, only rows where `microchip IS NOT NULL` +3. This means the uniqueness constraint doesn't apply to NULL values +4. Multiple NULL values are now allowed + +--- + +## Rollback (If Needed) + +If something goes wrong during migration: + +### Manual Rollback Steps + +1. **Stop the application** + ```bash + docker stop breedr + ``` + +2. **Restore from backup** (if you made one) + ```bash + cp /mnt/user/appdata/breedr/breedr.db.backup \ + /mnt/user/appdata/breedr/breedr.db + ``` + +3. **Start the application** + ```bash + docker start breedr + ``` + +### Create Backup Before Migration + +```bash +# Stop container +docker stop breedr + +# Create backup +cp /mnt/user/appdata/breedr/breedr.db \ + /mnt/user/appdata/breedr/breedr.db.backup + +# Start container +docker start breedr + +# Run migration +docker exec -it breedr node server/db/migrate_microchip.js +``` + +--- + +## Future Prevention + +All new databases created with the updated schema will have the correct constraint from the start. No migration needed for: + +- Fresh installations +- Deleted and recreated databases +- Databases created after this fix + +--- + +## Related Files + +- **Schema Definition:** `server/db/init.js` +- **Migration Script:** `server/db/migrate_microchip.js` +- **This Document:** `docs/MICROCHIP_FIX.md` + +--- + +## Changelog + +### March 8, 2026 +- Identified UNIQUE constraint issue with microchip field +- Created migration script to fix existing databases +- Updated schema for new databases +- Added partial unique index solution +- Documented problem and solution + +--- + +*If you encounter any issues with the migration, check the container logs:* + +```bash +docker logs breedr +``` + +*Or open a GitHub issue with the error details.* \ No newline at end of file