photoprism/internal/entity/migrate
2026-06-24 17:20:30 +02:00
..
mysql Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
sqlite3 AI: Add IndexedAt time to indicate when a photo was fully indexed #5167 2025-10-07 13:57:01 +02:00
testdata Backend: Move migrate package to /internal/entity/migrate 2024-07-02 06:40:03 +02:00
AGENTS.md Entity: Document VARBINARY index-prefix limit and byte-exact paths #5614 #5615 2026-06-01 11:42:59 +00:00
dbms_authid_migration.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
dialect_mysql.go Meta: Update go-mp4, regenerate NOTICE, fix XMP doc spelling #2260 #5563 2026-06-17 00:14:33 +00:00
dialect_mysql_test.go Develop: Honor MARIADB_PORT and POSTGRES_PORT overrides #5590 2026-05-20 09:44:12 +00:00
dialect_sqlite3.go AI: Add IndexedAt time to indicate when a photo was fully indexed #5167 2025-10-07 13:57:01 +02:00
dialect_sqlite3_test.go Backend: Improve Copy()/Move() and increase pkg/internal test coverage 2025-09-22 03:07:51 +02:00
dialects.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
errors.go Metadata: Set labels based on matching keywords in the caption #4603 2025-01-17 06:24:29 +01:00
generate.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
migrate.go Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
migration.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
migrations.go Backend: Move migrate package to /internal/entity/migrate 2024-07-02 06:40:03 +02:00
options.go Backend: Move migrate package to /internal/entity/migrate 2024-07-02 06:40:03 +02:00
README.md Entity: Document VARBINARY index-prefix limit and byte-exact paths #5614 #5615 2026-06-01 11:42:59 +00:00
run.go Backend: Move migrate package to /internal/entity/migrate 2024-07-02 06:40:03 +02:00
stages.go Backend: Move migrate package to /internal/entity/migrate 2024-07-02 06:40:03 +02:00
status.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
version.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00

Database Migrations

Last Updated: June 1, 2026

This package contains the dialect-specific SQL migrations that complement GORM's schema auto-migration during database initialization. The SQL source files live in mysql/ and sqlite3/, and generate.go embeds them into the generated dialect_mysql.go and dialect_sqlite3.go files.

Files ending in .pre.sql run in the pre stage before ORM auto-migration. All other migration files run in the main stage afterward.

Index Prefix Limits (VARBINARY Columns)

InnoDB caps an index key prefix at 767 bytes on the COMPACT/REDUNDANT row formats, and only allows up to 3072 bytes on DYNAMIC/COMPRESSED. On a VARBINARY column the prefix is measured in bytes (on a utf8mb4 column it is measured in characters, i.e. up to 4 bytes each), so a CREATE INDEX … (col(N)) with N > 767 fails with "Specified key was too long; max key length is 767 bytes" on older or non-DYNAMIC installs.

When a migration converts a long text column to VARBINARY and re-creates its prefix index, keep the prefix at ≤ 767 bytes — the project convention is 512 (see mysql/20220329-040000.sql for album_filter(512) and mysql/20260601-000001.sql for album_path(512)). A prefix index only narrows candidate rows; the full-column comparison is still exact, so a shorter prefix has no correctness cost. SQLite has no equivalent limit and is not migrated for VARBINARY type changes (its TEXT comparisons are already byte-exact).

When Migrations Run

PhotoPrism initializes the database through Config.InitDb() and Config.MigrateDb(). This happens during photoprism start and in other commands that need a ready schema.

The migration flow is:

  1. Open the configured database connection and register it as the active provider.
  2. Load or create a row in the versions table for the current PhotoPrism Version and Edition.
  3. Run pre SQL migrations from this package.
  4. If the current release still NeedsMigration(), run the one-time schema work in entity.InitDb() and Tables.Migrate():
    • drop deprecated tables when enabled
    • run GORM AutoMigrate(...) for all registered entities
    • apply one-off compatibility fixes that are also tracked through versions
  5. Run main SQL migrations from this package.
  6. Mark the current release as migrated by setting versions.migrated_at.

The important distinction is that the versions table gates the expensive release-level schema initialization, while the migrations table tracks each named SQL migration in this package.

Why Migrations Are Not Repeated

PhotoPrism uses two persistence layers to avoid rerunning the same startup work and to keep broken migrations from producing the same error on every launch.

versions: Once Per Release

The versions table stores one row per PhotoPrism Version and Edition. After a successful release-level initialization, MigrateDb() sets migrated_at. On later startups of the same release, NeedsMigration() returns false, so the full ORM auto-migration path is skipped. This reduces startup time and avoids repeating the same broad schema work over and over again.

migrations: Once Per Migration ID

Each SQL migration in this package has a stable timestamp-based ID, for example 20240709-000001. Before running a migration, PhotoPrism looks for an existing row in the migrations table.

  • If finished_at is set, the migration already succeeded and is skipped.
  • If error is set, the migration previously failed and is skipped during normal startup.
  • If the migration was started but not finished and has been "running" for less than 60 minutes, it is treated as still in progress and is skipped.
  • If the migration was started but not finished and has been "running" for 60 minutes or more, it is treated as stale and may be repeated automatically.

This is the reason broken migrations do not spam the logs on every startup: the failure is recorded once in migrations.error, and future runs see that row and skip it unless you explicitly ask to retry it.

Troubleshooting & Testing

Use the CLI commands below when you need to inspect migration state, retry failed work, or test specific migration IDs. These commands use the normal PhotoPrism configuration, so point them at a disposable or copied database first when you are testing reruns.

Inspecting Status

photoprism migrations
photoprism migrations ls
photoprism migrations status
photoprism migrations show

photoprism migrations shows the available subcommands. photoprism migrations ls displays the current status of known migrations without executing them.

For automation or diffing, ls also supports machine-readable output:

photoprism migrations ls --json
photoprism migrations ls --md
photoprism migrations ls --csv
photoprism migrations ls --tsv

The status output maps to the runtime logic in migration.go:

Status Meaning
OK The migration finished successfully.
- The migration has not been started yet.
Repeat The migration looks stale and is eligible to run again automatically.
Running? The migration started recently and is assumed to still be in progress.
<error text> The last attempt failed and will be skipped on normal startup.

Running Migrations

photoprism migrations run
photoprism migrations run --trace
photoprism migrations run --failed
photoprism migrations run --failed --trace
photoprism migrate

photoprism migrations run executes pending schema migrations. --trace enables trace logging for debugging. --failed retries migrations that have an error recorded in the migrations table. photoprism migrate is a top-level alias for photoprism migrations run.

Running Specific Migration IDs

You can limit status checks or runs to one or more specific migration IDs:

photoprism migrations ls 20240709-000001
photoprism migrations run 20240709-000001
photoprism migrations run --failed "20240709-000001 20250315-000001"

When you pass a specific ID, PhotoPrism selects only that migration. When you pass multiple IDs, quote them as a single whitespace-separated argument so the CLI can split them correctly.

Named migrations are allowed to run again even if they already succeeded or previously failed. That makes targeted troubleshooting possible, but it also means you should prefer a test database before forcing reruns.