mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	refactor(server): filter assets by people using a subquery instead of a cte (#15768)
This commit is contained in:
		
							parent
							
								
									098bab7c9b
								
							
						
					
					
						commit
						1b141d5ca9
					
				@ -238,24 +238,20 @@ export function withFacesAndPeople(eb: ExpressionBuilder<DB, 'assets'>) {
 | 
				
			|||||||
    .as('faces');
 | 
					    .as('faces');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Adds a `has_people` CTE that can be inner joined on to filter out assets */
 | 
					export function hasPeople<O>(qb: SelectQueryBuilder<DB, 'assets', O>, personIds: string[]) {
 | 
				
			||||||
export function hasPeopleCte(db: Kysely<DB>, personIds: string[]) {
 | 
					  return qb.innerJoin(
 | 
				
			||||||
  return db.with('has_people', (qb) =>
 | 
					    (eb) =>
 | 
				
			||||||
    qb
 | 
					      eb
 | 
				
			||||||
        .selectFrom('asset_faces')
 | 
					        .selectFrom('asset_faces')
 | 
				
			||||||
        .select('assetId')
 | 
					        .select('assetId')
 | 
				
			||||||
        .where('personId', '=', anyUuid(personIds!))
 | 
					        .where('personId', '=', anyUuid(personIds!))
 | 
				
			||||||
        .groupBy('assetId')
 | 
					        .groupBy('assetId')
 | 
				
			||||||
      .having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length),
 | 
					        .having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length)
 | 
				
			||||||
 | 
					        .as('has_people'),
 | 
				
			||||||
 | 
					    (join) => join.onRef('has_people.assetId', '=', 'assets.id'),
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function hasPeople(db: Kysely<DB>, personIds?: string[]) {
 | 
					 | 
				
			||||||
  return personIds && personIds.length > 0
 | 
					 | 
				
			||||||
    ? hasPeopleCte(db, personIds).selectFrom('assets').innerJoin('has_people', 'has_people.assetId', 'assets.id')
 | 
					 | 
				
			||||||
    : db.selectFrom('assets');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function withOwner(eb: ExpressionBuilder<DB, 'assets'>) {
 | 
					export function withOwner(eb: ExpressionBuilder<DB, 'assets'>) {
 | 
				
			||||||
  return jsonObjectFrom(eb.selectFrom('users').selectAll().whereRef('users.id', '=', 'assets.ownerId')).as('owner');
 | 
					  return jsonObjectFrom(eb.selectFrom('users').selectAll().whereRef('users.id', '=', 'assets.ownerId')).as('owner');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -326,8 +322,11 @@ const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
 | 
				
			|||||||
export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuilderOptions) {
 | 
					export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuilderOptions) {
 | 
				
			||||||
  options.isArchived ??= options.withArchived ? undefined : false;
 | 
					  options.isArchived ??= options.withArchived ? undefined : false;
 | 
				
			||||||
  options.withDeleted ||= !!(options.trashedAfter || options.trashedBefore);
 | 
					  options.withDeleted ||= !!(options.trashedAfter || options.trashedBefore);
 | 
				
			||||||
  return hasPeople(kysely.withPlugin(joinDeduplicationPlugin), options.personIds)
 | 
					  return kysely
 | 
				
			||||||
 | 
					    .withPlugin(joinDeduplicationPlugin)
 | 
				
			||||||
 | 
					    .selectFrom('assets')
 | 
				
			||||||
    .selectAll('assets')
 | 
					    .selectAll('assets')
 | 
				
			||||||
 | 
					    .$if(!!options.personIds && options.personIds.length > 0, (qb) => hasPeople(qb, options.personIds!))
 | 
				
			||||||
    .$if(!!options.createdBefore, (qb) => qb.where('assets.createdAt', '<=', options.createdBefore!))
 | 
					    .$if(!!options.createdBefore, (qb) => qb.where('assets.createdAt', '<=', options.createdBefore!))
 | 
				
			||||||
    .$if(!!options.createdAfter, (qb) => qb.where('assets.createdAt', '>=', options.createdAfter!))
 | 
					    .$if(!!options.createdAfter, (qb) => qb.where('assets.createdAt', '>=', options.createdAfter!))
 | 
				
			||||||
    .$if(!!options.updatedBefore, (qb) => qb.where('assets.updatedAt', '<=', options.updatedBefore!))
 | 
					    .$if(!!options.updatedBefore, (qb) => qb.where('assets.updatedAt', '<=', options.updatedBefore!))
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,6 @@ import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
  AssetEntity,
 | 
					  AssetEntity,
 | 
				
			||||||
  hasPeople,
 | 
					  hasPeople,
 | 
				
			||||||
  hasPeopleCte,
 | 
					 | 
				
			||||||
  searchAssetBuilder,
 | 
					  searchAssetBuilder,
 | 
				
			||||||
  truncatedDate,
 | 
					  truncatedDate,
 | 
				
			||||||
  withAlbums,
 | 
					  withAlbums,
 | 
				
			||||||
@ -576,7 +575,7 @@ export class AssetRepository implements IAssetRepository {
 | 
				
			|||||||
  @GenerateSql({ params: [{ size: TimeBucketSize.MONTH }] })
 | 
					  @GenerateSql({ params: [{ size: TimeBucketSize.MONTH }] })
 | 
				
			||||||
  async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
 | 
					  async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      ((options.personId ? hasPeopleCte(this.db, [options.personId]) : this.db) as Kysely<DB>)
 | 
					      this.db
 | 
				
			||||||
        .with('assets', (qb) =>
 | 
					        .with('assets', (qb) =>
 | 
				
			||||||
          qb
 | 
					          qb
 | 
				
			||||||
            .selectFrom('assets')
 | 
					            .selectFrom('assets')
 | 
				
			||||||
@ -589,11 +588,7 @@ export class AssetRepository implements IAssetRepository {
 | 
				
			|||||||
                .innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId')
 | 
					                .innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId')
 | 
				
			||||||
                .where('albums_assets_assets.albumsId', '=', asUuid(options.albumId!)),
 | 
					                .where('albums_assets_assets.albumsId', '=', asUuid(options.albumId!)),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .$if(!!options.personId, (qb) =>
 | 
					            .$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
 | 
				
			||||||
              qb.innerJoin(sql.table('has_people').as('has_people'), (join) =>
 | 
					 | 
				
			||||||
                join.onRef(sql`has_people."assetId"`, '=', 'assets.id'),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .$if(!!options.withStacked, (qb) =>
 | 
					            .$if(!!options.withStacked, (qb) =>
 | 
				
			||||||
              qb
 | 
					              qb
 | 
				
			||||||
                .leftJoin('asset_stack', (join) =>
 | 
					                .leftJoin('asset_stack', (join) =>
 | 
				
			||||||
@ -628,10 +623,12 @@ export class AssetRepository implements IAssetRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }] })
 | 
					  @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }] })
 | 
				
			||||||
  async getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]> {
 | 
					  async getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]> {
 | 
				
			||||||
    return hasPeople(this.db, options.personId ? [options.personId] : undefined)
 | 
					    return this.db
 | 
				
			||||||
 | 
					      .selectFrom('assets')
 | 
				
			||||||
      .selectAll('assets')
 | 
					      .selectAll('assets')
 | 
				
			||||||
      .$call(withExif)
 | 
					      .$call(withExif)
 | 
				
			||||||
      .$if(!!options.albumId, (qb) => withAlbums(qb, { albumId: options.albumId }))
 | 
					      .$if(!!options.albumId, (qb) => withAlbums(qb, { albumId: options.albumId }))
 | 
				
			||||||
 | 
					      .$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
 | 
				
			||||||
      .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
 | 
					      .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
 | 
				
			||||||
      .$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
 | 
					      .$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
 | 
				
			||||||
      .$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
 | 
					      .$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user