Files
breedr/docs/MICROCHIP_FIX.md

6.6 KiB

Microchip Field Fix

Problem

The microchip field in the dogs table had a UNIQUE constraint defined directly on the column:

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:

-- 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.

# 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

cd /path/to/breedr
node server/db/migrate_microchip.js

Option 3: Rebuild from Scratch (Data Loss)

# 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

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

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

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

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)

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)

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

    docker stop breedr
    
  2. Restore from backup (if you made one)

    cp /mnt/user/appdata/breedr/breedr.db.backup \
       /mnt/user/appdata/breedr/breedr.db
    
  3. Start the application

    docker start breedr
    

Create Backup Before Migration

# 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

  • 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:

docker logs breedr

Or open a GitHub issue with the error details.