photoprism/internal/entity
Michael Mayer 0cecb5287f Search: Match folder album_path case-insensitively #5724
Folder search lowercases the query term, but album_path is VARBINARY and
compared byte-exact (case-sensitive) on MySQL/MariaDB, so a lowercased
term no longer matched uppercase folder paths. A parent folder still
appeared via its case-insensitive title, while child folders - matchable
only by path - dropped out of the results.

Add a dialect-aware PathLike helper: MySQL converts album_path to a
case-insensitive collation for the LIKE, SQLite and other dialects fall
back to a plain LIKE (SQLite is already ASCII case-insensitive). Only the
folder search filter is affected; byte-exact album_path identity lookups
are unchanged, so emoji and case-distinct folders stay distinct.
2026-07-16 16:53:28 +00:00
..
legacy Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
migrate Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
query Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
schema Cleanup: Fix formatting of Markdown files 2026-02-14 12:10:43 +01:00
search Search: Match folder album_path case-insensitively #5724 2026-07-16 16:53:28 +00:00
sortby Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
testdata/album
album.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
album_cache.go Docs: Improve code comments in internal/entity/album*.go 2025-10-02 15:59:38 +02:00
album_cache_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
album_fixtures.go
album_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
album_test.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
album_user.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
album_yaml.go Entity: Clip album, photo, and folder paths via shared ClipPath #5615 2026-06-03 09:34:40 +00:00
album_yaml_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
auth_client.go Cluster: Add a per-instance node DisplayName field 2026-06-09 07:23:05 +00:00
auth_client_add.go
auth_client_add_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
auth_client_data.go Cluster: Declare per-node group config via instance env/flags 2026-06-12 18:52:54 +00:00
auth_client_fixtures.go
auth_client_fixtures_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
auth_client_test.go Cluster: Add a per-instance node DisplayName field 2026-06-09 07:23:05 +00:00
auth_session.go Auth: Store session login_at as NULL when no client IP is set #5707 2026-06-29 23:28:12 +00:00
auth_session_cache.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
auth_session_cache_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
auth_session_client.go
auth_session_client_test.go Auth: Store session login_at as NULL when no client IP is set #5707 2026-06-29 23:28:12 +00:00
auth_session_data.go Cluster: Collect OIDC groups at login for group-based admission 2026-06-12 16:08:38 +00:00
auth_session_data_test.go Cluster: Collect OIDC groups at login for group-based admission 2026-06-12 16:08:38 +00:00
auth_session_delete.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
auth_session_delete_test.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
auth_session_fixtures.go Auth: Store session login_at as NULL when no client IP is set #5707 2026-06-29 23:28:12 +00:00
auth_session_fixtures_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
auth_session_jwt.go Cluster: Improve usage of canonical terms in code/docs based on glossary 2026-02-23 16:16:29 +01:00
auth_session_jwt_test.go Lint: Resolve gosec findings and add embedding dist benchmark 2026-03-03 17:17:49 +01:00
auth_session_login.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
auth_session_login_test.go Lint: Resolve gosec findings and add embedding dist benchmark 2026-03-03 17:17:49 +01:00
auth_session_report.go
auth_session_report_test.go
auth_session_test.go Auth: Gate app passwords behind a settings feature flag #5647 2026-06-09 17:36:24 +00:00
auth_tokens.go Security: Add gosec fixes with shared URL and fs validation helpers 2026-03-03 16:38:41 +01:00
auth_tokens_test.go
auth_user.go Users: Apply 2FA changes for the cluster service principal 2026-06-29 21:59:48 +00:00
auth_user_add.go
auth_user_cli.go Auth: Revoke derived sessions & gate app passwords by login state #5647 2026-06-15 23:41:01 +00:00
auth_user_default.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
auth_user_details.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
auth_user_details_test.go Lint: Resolve gosec findings and add embedding dist benchmark 2026-03-03 17:17:49 +01:00
auth_user_fixtures.go
auth_user_fixtures_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
auth_user_legacy.go
auth_user_legacy_test.go
auth_user_report.go
auth_user_report_test.go
auth_user_settings.go Users: Set the user UID when creating user settings 2026-05-31 00:23:18 +00:00
auth_user_settings_test.go Users: Set the user UID when creating user settings 2026-05-31 00:23:18 +00:00
auth_user_share.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
auth_user_share_fixtures.go
auth_user_share_fixtures_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
auth_user_share_test.go Tests: Use valid fixture IDs so entity tests pass on MariaDB 2026-05-31 00:23:23 +00:00
auth_user_test.go Users: Apply 2FA changes for the cluster service principal 2026-06-29 21:59:48 +00:00
camera.go Events: UID-only camera/lens/country payloads & people refetch #5663 #1307 2026-06-15 11:37:26 +00:00
camera_cache.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
camera_fixtures.go
camera_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
camera_makes.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
camera_models.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
camera_test.go Events: UID-only camera/lens/country payloads & people refetch #5663 #1307 2026-06-15 11:37:26 +00:00
camera_types.go
camera_types_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
category.go Docs: Update code comments in internal/entity/category.go and details.go 2025-10-02 16:19:57 +02:00
category_fixtures.go
category_test.go
cell.go Entity: Fix cell lookups 2026-03-07 11:48:20 +01:00
cell_fixtures.go
cell_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
cell_test.go Entity: Fix cell lookups 2026-03-07 11:48:20 +01:00
clip.go Entity: Rename Sanitize* to Clip* and string.go to clip.go 2026-06-03 09:50:56 +00:00
clip_test.go Entity: Rename Sanitize* to Clip* and string.go to clip.go 2026-06-03 09:50:56 +00:00
country.go Events: UID-only camera/lens/country payloads & people refetch #5663 #1307 2026-06-15 11:37:26 +00:00
country_cache.go
country_fixtures.go
country_test.go Events: UID-only camera/lens/country payloads & people refetch #5663 #1307 2026-06-15 11:37:26 +00:00
db.go Entity: Store created and updated timestamps with second precision 2026-05-31 01:13:27 +00:00
db_conn.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
db_conn_test.go
deprecated.go
details.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
details_fixtures.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
details_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
details_test.go Tests: Use valid fixture IDs so entity tests pass on MariaDB 2026-05-31 00:23:23 +00:00
duplicate.go
duplicate_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
entity.go Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
entity_const.go Assets: Rename example fixtures directory to "samples" 2026-03-06 18:47:33 +01:00
entity_count.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
entity_count_test.go
entity_counts.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
entity_counts_test.go Entity: Fix nil-DB race in async count/cover updates #5551 2026-05-03 10:49:28 +00:00
entity_errors.go
entity_fixtures.go
entity_init.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
entity_save.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
entity_save_test.go Lint: Resolve gosec findings and add embedding dist benchmark 2026-03-03 17:17:49 +01:00
entity_tables.go Security: Add gosec fixes with shared URL and fs validation helpers 2026-03-03 16:38:41 +01:00
entity_test.go Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
entity_time.go Auth: Store session login_at as NULL when no client IP is set #5707 2026-06-29 23:28:12 +00:00
entity_time_test.go Auth: Store session login_at as NULL when no client IP is set #5707 2026-06-29 23:28:12 +00:00
entity_update.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
entity_update_test.go Entity: Store created and updated timestamps with second precision 2026-05-31 01:13:27 +00:00
entity_values.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
entity_values_test.go CLI: Add "photoprism vision reset" command to reset metadata #5233 2025-09-29 18:59:52 +02:00
error.go Backend: Call clip.Bytes directly and drop the txt.ClipBytes facade #5638 2026-06-03 09:50:56 +00:00
face.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
face_fixtures.go
face_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
face_test.go AI: Rename SampleRadius to ClusterRadius and increase cap to 0.42 #5167 2025-10-26 10:08:59 +01:00
faces.go
faces_test.go
file.go Entity: Rename Sanitize* to Clip* and string.go to clip.go 2026-06-03 09:50:56 +00:00
file_fixtures.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
file_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
file_json.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
file_json_test.go
file_metadata.go
file_share.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
file_share_fixtures.go
file_share_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
file_share_test.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
file_sync.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
file_sync_fixtures.go
file_sync_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
file_sync_test.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
file_test.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
fixtures.go
folder.go Entity: Clip album, photo, and folder paths via shared ClipPath #5615 2026-06-03 09:34:40 +00:00
folder_album_slug.go Folders: Fix slug collision on long ASCII paths #5614 #5615 2026-05-26 02:34:48 +00:00
folder_album_slug_test.go Folders: Fix slug collision on long ASCII paths #5614 #5615 2026-05-26 02:34:48 +00:00
folder_fixtures.go
folder_test.go Folders: Improve slug collision handling for Unicode paths #5366 2026-03-07 11:16:21 +01:00
keyword.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
keyword_cache.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
keyword_cache_test.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
keyword_fixtures.go
keyword_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
keyword_test.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
label.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
label_cache.go Entity: Fix label homophone lookups and photo-label caching #5227 #5238 2026-03-07 11:43:25 +01:00
label_cache_test.go Entity: Fix label homophone lookups and photo-label caching #5227 #5238 2026-03-07 11:43:25 +01:00
label_fixtures.go
label_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
label_search.go Labels: Reuse renamed label when re-added by previous name #5531 2026-05-04 21:13:57 +00:00
label_search_test.go Labels: Reuse renamed label when re-added by previous name #5531 2026-05-04 21:13:57 +00:00
label_slug.go Backend: Compact verbose comments in entity and photoprism workers 2026-05-15 17:50:08 +00:00
label_slug_test.go Labels: Reuse renamed label when re-added by previous name #5531 2026-05-04 21:13:57 +00:00
label_test.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
lens.go Events: UID-only camera/lens/country payloads & people refetch #5663 #1307 2026-06-15 11:37:26 +00:00
lens_cache.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
lens_fixtures.go Metadata: Add Lens Make and Model updates via CLI & API #5644 #5656 2026-06-15 09:37:32 +02:00
lens_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
lens_models.go
lens_test.go Events: UID-only camera/lens/country payloads & people refetch #5663 #1307 2026-06-15 11:37:26 +00:00
link.go Logs: Improve log messages in API and share audit paths 2026-03-08 12:53:20 +01:00
link_fixtures.go Sharing: Fix single-item access for shared smart albums #5727 2026-07-16 12:10:44 +00:00
link_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
marker.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
marker_fixtures.go
marker_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
marker_json.go
marker_json_test.go
marker_test.go Entity: Store created and updated timestamps with second precision 2026-05-31 01:13:27 +00:00
markers.go AI: Do not add portrait or people labels from face markers #5232 2025-10-05 04:37:20 +02:00
markers_test.go AI: Improve the generation, sorting, and filtering of labels #5232 2025-10-02 13:08:52 +02:00
mysql8_test.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
passcode.go Auth: Refine passcode recovery code comparison 2026-06-11 09:37:40 +00:00
passcode_fixtures.go
passcode_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
passcode_json.go Security: Add gosec fixes with shared URL and fs validation helpers 2026-03-03 16:38:41 +01:00
passcode_json_test.go
passcode_test.go Auth: Refine passcode recovery code comparison 2026-06-11 09:37:40 +00:00
password.go
password_fixtures.go
password_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
password_test.go Tests: Use valid fixture IDs so entity tests pass on MariaDB 2026-05-31 00:23:23 +00:00
photo.go Index: Improve support for metadata in XMP sidecar files #2260 #2828 #5563 2026-06-17 01:53:28 +02:00
photo_album.go Docs: Improve code comments in internal/entity/photo*.go 2025-10-02 15:52:05 +02:00
photo_album_fixtures.go Tests: Fix typo in test fixtures 2025-12-02 11:02:05 +01:00
photo_album_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
photo_album_test.go Tests: Use valid fixture IDs so entity tests pass on MariaDB 2026-05-31 00:23:23 +00:00
photo_caption.go Docs: Improve code comments in internal/entity/photo*.go 2025-10-02 15:52:05 +02:00
photo_caption_test.go
photo_datetime.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
photo_datetime_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
photo_estimate.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
photo_estimate_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
photo_fixtures.go Tests: Fix typo in test fixtures 2025-12-02 11:02:05 +01:00
photo_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
photo_interface.go
photo_keyword.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
photo_keyword_fixtures.go
photo_keyword_test.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
photo_label.go Entity: Fix photo-label caching #5227 #5238 2026-03-07 11:48:07 +01:00
photo_label_test.go Tests: Use valid fixture IDs so entity tests pass on MariaDB 2026-05-31 00:23:23 +00:00
photo_location.go Index: Resolve S2 cell in XMP branch, downgrade geocoding warn 2026-07-10 10:14:02 +02:00
photo_location_test.go Places: Update entity/photo_location_test.go #5445 2026-02-10 17:36:47 +01:00
photo_merge.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
photo_merge_test.go Lint: Resolve gosec findings and add embedding dist benchmark 2026-03-03 17:17:49 +01:00
photo_optimize.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
photo_optimize_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
photo_quality.go Media: Improve layered TIFF & PSD preview support #5383 2026-03-06 18:14:48 +01:00
photo_quality_test.go Media: Improve layered TIFF & PSD preview support #5383 2026-03-06 18:14:48 +01:00
photo_reset.go CLI: Add "photoprism vision reset" command to reset metadata #5233 2025-09-29 18:59:52 +02:00
photo_reset_test.go CLI: Add "photoprism vision reset" command to reset metadata #5233 2025-09-29 18:59:52 +02:00
photo_scope_test.go Photos: Reduce identifying metadata for shared-only sessions #1307 2026-05-29 08:20:26 +00:00
photo_test.go Tests: Add unit tests for recent metadata, auth, and config changes #5697 2026-06-26 14:12:59 +02:00
photo_title.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
photo_title_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
photo_user.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
photo_vision.go AI: Add IndexedAt time to indicate when a photo was fully indexed #5167 2025-10-07 13:57:01 +02:00
photo_yaml.go Security: Add gosec fixes with shared URL and fs validation helpers 2026-03-03 16:38:41 +01:00
photo_yaml_test.go
photos.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
photos_test.go
place.go
place_fixtures.go
place_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
place_test.go UX: Add batch edit dialog and API endpoints #271 #5324 2025-11-19 11:20:34 +01:00
reaction.go CLI: Add "photoprism vision reset" command to reset metadata #5233 2025-09-29 18:59:52 +02:00
reaction_fixtures.go
reaction_test.go
README.md Entity: Document VARBINARY index-prefix limit and byte-exact paths #5614 #5615 2026-06-01 11:42:59 +00:00
service.go Backend: Clip VARBINARY text columns by bytes to avoid write errors #5638 2026-06-03 08:04:40 +00:00
service_fixtures.go
service_test.go Entity: Store created and updated timestamps with second precision 2026-05-31 01:13:27 +00:00
src.go Faces: Change search to return all face markers, regardless of src #5167 2025-10-07 20:01:43 +02:00
src_test.go Faces: Change search to return all face markers, regardless of src #5167 2025-10-07 20:01:43 +02:00
string_keyvalue.go
string_map.go
string_map_test.go
subject.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
subject_fixtures.go API: Update Thumb/ThumbSrc for subjects and labels #4151 2025-10-03 23:17:07 +02:00
subject_fixtures_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
subject_names.go
subject_person.go
subject_person_test.go
subject_test.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
subjects.go
subjects_test.go

PhotoPrism — Database Entities

Last Updated: June 1, 2026

Overview

internal/entity holds the GORM models (Photo, File, Album, Label, Face, User, Client, Session, Service, Marker, …), their query and create/update helpers, the test fixtures (*_fixtures.go), and the migration helpers under migrate/. Models map to the database via GORM v1 (github.com/jinzhu/gorm) and are shared by the API, workers, and CLI.

Timestamps

Created and updated timestamps are stored as SQL DATETIME without fractional seconds (DATETIME_PRECISION = 0). To keep in-memory and persisted values in sync, the package sets GORM's timestamp source to second precision in db.go:

gorm.NowFunc = Now // entity.Now() == UTC().Truncate(time.Second)

Time helpers in entity_time.go:

  • UTC() — current time in UTC, full sub-second precision. Use for elapsed-time measurements, not for values that get persisted.
  • Now() — UTC truncated to whole seconds. This is what GORM writes to created_at / updated_at.
  • TimeStamp() — pointer to Now(), for nullable *time.Time columns.
  • Time(s) — parses an RFC 3339 string to a second-precision UTC time, or nil.

Implications:

  • Do not rely on sub-second ordering of persisted timestamps. Two rows created and updated within the same wall-clock second compare equal, so created_at / updated_at cannot disambiguate them. There is no monotonic auto-increment ID on UID-keyed models (e.g. Client), so there is no reliable intra-second tiebreaker — give rows distinct times when ordering must be deterministic.
  • Because both SQLite and MariaDB now receive second-precision values, timestamp behavior is identical across drivers. A timestamp assertion that passes on SQLite will pass on MariaDB.

When a test needs to prove a write advanced a timestamp, prefer one of:

  • Seed the starting value clearly in the past (e.g. Now().Add(-time.Hour)) and assert the new value is greater. This stays meaningful and distinguishes a real bump from a no-op.

  • Compare with Time.Sub() and assert the difference falls in a sane range, rather than a strict Before/After. A same-second save legitimately yields a zero delta:

    elapsed := after.Sub(before)
    assert.GreaterOrEqual(t, elapsed, time.Duration(0))
    assert.Less(t, elapsed, time.Minute)
    

Testing

Tests default to SQLite. To exercise the models against MariaDB (which is stricter and is the production database for some subsystems such as the cluster registry):

mysql < scripts/sql/reset-acceptance.sql
PHOTOPRISM_TEST_DRIVER="mysql" \
PHOTOPRISM_TEST_DSN="root:photoprism@tcp(mariadb:4001)/acceptance?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true" \
go test ./internal/entity/... -count=1 -tags="slow,develop"

MariaDB strict mode rejects inserts that SQLite quietly accepts, so a test that only ran on SQLite can fail here:

  • Primary keys must be set. An empty PK ("" UID, zero ID) triggers Error 1364: Field '<col>' doesn't have a default value. Use a valid ID/UID, not a placeholder like "1234".
  • Values must fit the column. Oversized strings give Error 1406: Data too long; out-of-range integers give Error 1264: Out of range value (e.g. photo_id is INT UNSIGNED, max 4294967295).
  • UID format (see pkg/rnd/uid.go): a one-byte prefix + 6 base36 time chars + 9 base36 random, 16 chars total (p… photo, a… album, c… client, u… user, l… label). Reuse existing fixtures for foreign-key safety; use a throwaway but in-range value only where a real reference would overwrite seeded data (e.g. a synthetic photo_id so a Details row does not attach to a real photo).
  • Fixtures live in *_fixtures.go, but some join rows are created indirectly from a parent fixture's embedded slice (e.g. a photos_labels row from a Photo fixture's Labels). Verify a combination is free against the seeded database, not just the fixtures file.
  • List-style global queries (WHERE … <> '' with no per-test scope) are not isolated on the shared acceptance database: rows from other tests in the same run leak in, so a len(list) == N assertion that holds on a per-test SQLite file can fail on MariaDB.

Collation & Emoji

MariaDB's utf8mb4_unicode_ci assigns most emoji the same collation weight, so an SQL =, <>, or LIKE on a utf8mb4 column treats distinct emoji as equal (e.g. test/🪞 matches test/🎃). SQLite compares text byte-exact, so this only reproduces on MariaDB.

  • utf8mb4 columns that collapse: albums.album_title, display/name text (*_name, *_title).
  • VARBINARY columns that stay byte-exact: albums.album_slug, albums.album_filter, albums.album_path, photos.photo_path, and every *_uid. A utf8mb4 column compared against a VARBINARY column is byte-exact (the binary operand wins).

The durable fix for an identity/path column is to make it VARBINARYalbum_path is VARBINARY(1024) so it matches photos.photo_path and album_path = ? lookups are byte-exact at the database. Where a utf8mb4 column must stay, keep the SQL but re-verify the match byte-exact in Go before accepting it (see FindFolderAlbum / findFolderAlbumByPath, whose Go re-check is retained as defense-in-depth even now that album_path is VARBINARY). For self-join SQL where a Go re-check is awkward, HEX(col) = HEX(col) compares byte-exact on both MariaDB and SQLite. Legacy folder slugs drop emoji entirely (slug.Make("ins/🪞") == "ins") and long paths truncate to ClipSlug runes, so distinct folders can still collide on album_slug; folder albums are therefore deduplicated by album_filter (the byte-exact serialized path), not by slug (see query.RemoveDuplicateMoments).

VARBINARY Index Prefix Limit

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 counted in bytes (on utf8mb4 it is counted in characters, i.e. up to 4 bytes each), so converting a long text column to VARBINARY can push an existing prefix index over the limit on older or non-DYNAMIC installs. Keep prefix indexes on long VARBINARY path/filter columns at ≤ 767 bytes; the project convention is 512 (albums.album_filter(512), albums.album_path(512)). A prefix index only narrows candidate rows — the full-column comparison stays exact — so a shorter prefix costs nothing for correctness.