feat(db): hash_conflicts table and backfill/conflict queries
Content duplicates (same library + file_sha256 at different paths, e.g. the same book imported twice under two names on a preexisting database) cannot be auto-collapsed the way path duplicates were: keeping both copies may be intentional. Surface them for an explicit admin decision instead. Schema: - new hash_conflicts table keyed (library_id, file_sha256) with a status/resolution lifecycle: 'pending' until an admin resolves via 'keep_all' or 'kept:<uuid>' (which copy was kept after merging) - resolution is VARCHAR(50) - 'kept:<uuid>' is 41 chars; include a widening ALTER for databases created with the initial 30-char width - resolution/resolved_by/resolved_at record who decided what and when Queries: - ListMediaItemsMissingHash: items imported before hashing existed (file_sha256 IS NULL), ordered oldest-first for the backfill pass - FindHashConflictGroups: the content-duplicate group detection (GROUP BY library_id, file_sha256 HAVING COUNT(*) > 1) - ListMediaItemsBySHA256AndLibrary: full membership of one group - CreateHashConflict: upsert with DO NOTHING so already-tracked groups are untouched - critical behavior: a group an admin resolved as 'keep both' is never re-flagged by later sweeps - ListPendingHashConflicts: admin listing with library name and live item counts (items may have been deleted since flagging) - GetHashConflict / ResolveHashConflict: lifecycle - GetMediaItemUsageCounts: per-item progress/highlight/bookmark/note/ collection counts so the admin can make an informed keep choice - ReparentMediaItemChildren: sqlc binding for the existing reparent_media_item_children() migration function, used to merge a losing copy's child rows into the kept copy
This commit is contained in:
@@ -53,6 +53,10 @@ type Querier interface {
|
||||
// Create device shelf mapping
|
||||
CreateDeviceShelfMapping(ctx context.Context, arg CreateDeviceShelfMappingParams) (DeviceShelfMappings, error)
|
||||
CreateDictionaryEntry(ctx context.Context, arg CreateDictionaryEntryParams) (DictionaryCache, error)
|
||||
// HASH CONFLICTS QUERIES
|
||||
// Record a pending hash conflict (no-op if the group is already tracked, so
|
||||
// resolved groups stay resolved and are never re-flagged)
|
||||
CreateHashConflict(ctx context.Context, arg CreateHashConflictParams) error
|
||||
// Libraries queries
|
||||
CreateLibrary(ctx context.Context, arg CreateLibraryParams) (Libraries, error)
|
||||
CreateMediaBookmark(ctx context.Context, arg CreateMediaBookmarkParams) (MediaBookmarks, error)
|
||||
@@ -129,6 +133,8 @@ type Querier interface {
|
||||
DeleteUnlinkedBook(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteUser(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteUserSystemCollection(ctx context.Context, arg DeleteUserSystemCollectionParams) error
|
||||
// Find content-duplicate groups (same library + SHA-256, more than one row)
|
||||
FindHashConflictGroups(ctx context.Context) ([]FindHashConflictGroupsRow, error)
|
||||
GenerateKoboEntitlementId(ctx context.Context) (interface{}, error)
|
||||
// ============================================
|
||||
// ANNOTATION SERVE QUERIES
|
||||
@@ -184,6 +190,7 @@ type Querier interface {
|
||||
GetFailedSyncQueueItems(ctx context.Context, limit int32) ([]SyncQueue, error)
|
||||
GetFirstAdmin(ctx context.Context) (pgtype.UUID, error)
|
||||
GetFirstAdminExclude(ctx context.Context, id pgtype.UUID) (GetFirstAdminExcludeRow, error)
|
||||
GetHashConflict(ctx context.Context, id pgtype.UUID) (HashConflicts, error)
|
||||
GetKoboEntitlementByContentId(ctx context.Context, arg GetKoboEntitlementByContentIdParams) (GetKoboEntitlementByContentIdRow, error)
|
||||
GetKoboEntitlementByEntitlementId(ctx context.Context, arg GetKoboEntitlementByEntitlementIdParams) (GetKoboEntitlementByEntitlementIdRow, error)
|
||||
GetKoboEntitlementsForDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboEntitlementsForDeviceRow, error)
|
||||
@@ -240,6 +247,8 @@ type Querier interface {
|
||||
GetMediaItemFormatByType(ctx context.Context, arg GetMediaItemFormatByTypeParams) (MediaItemFormats, error)
|
||||
// Get media item formats
|
||||
GetMediaItemFormats(ctx context.Context, mediaItemID pgtype.UUID) ([]MediaItemFormats, error)
|
||||
// Per-item user-data counts, used when choosing which duplicate copy to keep
|
||||
GetMediaItemUsageCounts(ctx context.Context, mediaItemID pgtype.UUID) (GetMediaItemUsageCountsRow, error)
|
||||
GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error)
|
||||
// ============================================
|
||||
// ANNOTATION SYNC QUERIES (notes)
|
||||
@@ -323,7 +332,12 @@ type Querier interface {
|
||||
ListLibraries(ctx context.Context) ([]ListLibrariesRow, error)
|
||||
ListMediaItems(ctx context.Context, arg ListMediaItemsParams) ([]ListMediaItemsRow, error)
|
||||
ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListMediaItemsByLibraryRow, error)
|
||||
// List all media items sharing a SHA-256 hash within a library (hash conflict group)
|
||||
ListMediaItemsBySHA256AndLibrary(ctx context.Context, arg ListMediaItemsBySHA256AndLibraryParams) ([]MediaItems, error)
|
||||
// List media items that have no stored SHA-256 (imported before hashing existed)
|
||||
ListMediaItemsMissingHash(ctx context.Context) ([]MediaItems, error)
|
||||
ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSortedParams) ([]ListMediaItemsSortedRow, error)
|
||||
ListPendingHashConflicts(ctx context.Context) ([]ListPendingHashConflictsRow, error)
|
||||
ListPendingSyncQueueItems(ctx context.Context, arg ListPendingSyncQueueItemsParams) ([]SyncQueue, error)
|
||||
ListProcessingIssuesByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListProcessingIssuesByLibraryRow, error)
|
||||
ListSyncConflictsByMediaItem(ctx context.Context, arg ListSyncConflictsByMediaItemParams) ([]SyncConflicts, error)
|
||||
@@ -341,7 +355,10 @@ type Querier interface {
|
||||
// Remove book from collection
|
||||
RemoveBookFromCollection(ctx context.Context, arg RemoveBookFromCollectionParams) error
|
||||
RemoveBookFromKoboShelf(ctx context.Context, arg RemoveBookFromKoboShelfParams) error
|
||||
// Re-parent all child rows of p_source onto p_target (defined in schema.sql)
|
||||
ReparentMediaItemChildren(ctx context.Context, arg ReparentMediaItemChildrenParams) error
|
||||
ResetSystemCollectionMetadata(ctx context.Context, arg ResetSystemCollectionMetadataParams) error
|
||||
ResolveHashConflict(ctx context.Context, arg ResolveHashConflictParams) error
|
||||
ResolveProcessingIssue(ctx context.Context, arg ResolveProcessingIssueParams) (ProcessingIssues, error)
|
||||
ResolveSyncConflict(ctx context.Context, arg ResolveSyncConflictParams) (SyncConflicts, error)
|
||||
// Resolve unlinked book
|
||||
|
||||
Reference in New Issue
Block a user