mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	fix(server): search duplicates of the same asset type (#9747)
* search by type * make sql --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
		
							parent
							
								
									50f9b2d44e
								
							
						
					
					
						commit
						e7c8501930
					
				@ -155,8 +155,9 @@ export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
 | 
				
			|||||||
export interface AssetDuplicateSearch {
 | 
					export interface AssetDuplicateSearch {
 | 
				
			||||||
  assetId: string;
 | 
					  assetId: string;
 | 
				
			||||||
  embedding: Embedding;
 | 
					  embedding: Embedding;
 | 
				
			||||||
  userIds: string[];
 | 
					 | 
				
			||||||
  maxDistance?: number;
 | 
					  maxDistance?: number;
 | 
				
			||||||
 | 
					  type: AssetType;
 | 
				
			||||||
 | 
					  userIds: string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface FaceSearchResult {
 | 
					export interface FaceSearchResult {
 | 
				
			||||||
 | 
				
			|||||||
@ -204,6 +204,7 @@ WITH
 | 
				
			|||||||
        "asset"."ownerId" IN ($2)
 | 
					        "asset"."ownerId" IN ($2)
 | 
				
			||||||
        AND "asset"."id" != $3
 | 
					        AND "asset"."id" != $3
 | 
				
			||||||
        AND "asset"."isVisible" = $4
 | 
					        AND "asset"."isVisible" = $4
 | 
				
			||||||
 | 
					        AND "asset"."type" = $5
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      AND ("asset"."deletedAt" IS NULL)
 | 
					      AND ("asset"."deletedAt" IS NULL)
 | 
				
			||||||
    ORDER BY
 | 
					    ORDER BY
 | 
				
			||||||
@ -216,7 +217,7 @@ SELECT
 | 
				
			|||||||
FROM
 | 
					FROM
 | 
				
			||||||
  "cte" "res"
 | 
					  "cte" "res"
 | 
				
			||||||
WHERE
 | 
					WHERE
 | 
				
			||||||
  res.distance <= $5
 | 
					  res.distance <= $6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- SearchRepository.searchFaces
 | 
					-- SearchRepository.searchFaces
 | 
				
			||||||
START TRANSACTION
 | 
					START TRANSACTION
 | 
				
			||||||
 | 
				
			|||||||
@ -160,6 +160,7 @@ export class SearchRepository implements ISearchRepository {
 | 
				
			|||||||
    assetId,
 | 
					    assetId,
 | 
				
			||||||
    embedding,
 | 
					    embedding,
 | 
				
			||||||
    maxDistance,
 | 
					    maxDistance,
 | 
				
			||||||
 | 
					    type,
 | 
				
			||||||
    userIds,
 | 
					    userIds,
 | 
				
			||||||
  }: AssetDuplicateSearch): Promise<AssetDuplicateResult[]> {
 | 
					  }: AssetDuplicateSearch): Promise<AssetDuplicateResult[]> {
 | 
				
			||||||
    const cte = this.assetRepository.createQueryBuilder('asset');
 | 
					    const cte = this.assetRepository.createQueryBuilder('asset');
 | 
				
			||||||
@ -171,18 +172,22 @@ export class SearchRepository implements ISearchRepository {
 | 
				
			|||||||
      .where('asset.ownerId IN (:...userIds )')
 | 
					      .where('asset.ownerId IN (:...userIds )')
 | 
				
			||||||
      .andWhere('asset.id != :assetId')
 | 
					      .andWhere('asset.id != :assetId')
 | 
				
			||||||
      .andWhere('asset.isVisible = :isVisible')
 | 
					      .andWhere('asset.isVisible = :isVisible')
 | 
				
			||||||
 | 
					      .andWhere('asset.type = :type')
 | 
				
			||||||
      .orderBy('search.embedding <=> :embedding')
 | 
					      .orderBy('search.embedding <=> :embedding')
 | 
				
			||||||
      .limit(64)
 | 
					      .limit(64)
 | 
				
			||||||
      .setParameters({ assetId, embedding: asVector(embedding), isVisible: true, userIds });
 | 
					      .setParameters({ assetId, embedding: asVector(embedding), isVisible: true, type, userIds });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const builder = this.assetRepository.manager
 | 
					    const builder = this.assetRepository.manager
 | 
				
			||||||
      .createQueryBuilder()
 | 
					      .createQueryBuilder()
 | 
				
			||||||
      .addCommonTableExpression(cte, 'cte')
 | 
					      .addCommonTableExpression(cte, 'cte')
 | 
				
			||||||
      .from('cte', 'res')
 | 
					      .from('cte', 'res')
 | 
				
			||||||
      .select('res.*')
 | 
					      .select('res.*');
 | 
				
			||||||
      .where('res.distance <= :maxDistance', { maxDistance });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return builder.getRawMany() as any as Promise<AssetDuplicateResult[]>;
 | 
					    if (maxDistance) {
 | 
				
			||||||
 | 
					      builder.where('res.distance <= :maxDistance', { maxDistance });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return builder.getRawMany() as Promise<AssetDuplicateResult[]>;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @GenerateSql({
 | 
					  @GenerateSql({
 | 
				
			||||||
 | 
				
			|||||||
@ -215,6 +215,7 @@ describe(SearchService.name, () => {
 | 
				
			|||||||
        assetId: assetStub.hasEmbedding.id,
 | 
					        assetId: assetStub.hasEmbedding.id,
 | 
				
			||||||
        embedding: assetStub.hasEmbedding.smartSearch!.embedding,
 | 
					        embedding: assetStub.hasEmbedding.smartSearch!.embedding,
 | 
				
			||||||
        maxDistance: 0.03,
 | 
					        maxDistance: 0.03,
 | 
				
			||||||
 | 
					        type: assetStub.hasEmbedding.type,
 | 
				
			||||||
        userIds: [assetStub.hasEmbedding.ownerId],
 | 
					        userIds: [assetStub.hasEmbedding.ownerId],
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
 | 
					      expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
 | 
				
			||||||
@ -240,6 +241,7 @@ describe(SearchService.name, () => {
 | 
				
			|||||||
        assetId: assetStub.hasEmbedding.id,
 | 
					        assetId: assetStub.hasEmbedding.id,
 | 
				
			||||||
        embedding: assetStub.hasEmbedding.smartSearch!.embedding,
 | 
					        embedding: assetStub.hasEmbedding.smartSearch!.embedding,
 | 
				
			||||||
        maxDistance: 0.03,
 | 
					        maxDistance: 0.03,
 | 
				
			||||||
 | 
					        type: assetStub.hasEmbedding.type,
 | 
				
			||||||
        userIds: [assetStub.hasEmbedding.ownerId],
 | 
					        userIds: [assetStub.hasEmbedding.ownerId],
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
 | 
					      expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
 | 
				
			||||||
 | 
				
			|||||||
@ -94,6 +94,7 @@ export class DuplicateService {
 | 
				
			|||||||
      assetId: asset.id,
 | 
					      assetId: asset.id,
 | 
				
			||||||
      embedding: asset.smartSearch.embedding,
 | 
					      embedding: asset.smartSearch.embedding,
 | 
				
			||||||
      maxDistance: machineLearning.duplicateDetection.maxDistance,
 | 
					      maxDistance: machineLearning.duplicateDetection.maxDistance,
 | 
				
			||||||
 | 
					      type: asset.type,
 | 
				
			||||||
      userIds: [asset.ownerId],
 | 
					      userIds: [asset.ownerId],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user