Document microchip field fix and migration steps
This commit is contained in:
304
docs/MICROCHIP_FIX.md
Normal file
304
docs/MICROCHIP_FIX.md
Normal file
@@ -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.*
|
||||
Reference in New Issue
Block a user