mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
add count
option to withStack
This commit is contained in:
parent
e3d08f0856
commit
45cd21a7b3
@ -179,39 +179,42 @@ export class AssetEntity {
|
|||||||
duplicateId!: string | null;
|
duplicateId!: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const withExif = <O>(qb: SelectQueryBuilder<DB, 'assets', O>) => {
|
export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
|
||||||
return qb
|
return qb
|
||||||
.leftJoin('exif', 'assets.id', 'exif.assetId')
|
.leftJoin('exif', 'assets.id', 'exif.assetId')
|
||||||
.select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo'));
|
.select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo'));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const withExifInner = <O>(qb: SelectQueryBuilder<DB, 'assets', O>) => {
|
export function withExifInner<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
|
||||||
return qb
|
return qb
|
||||||
.innerJoin('exif', 'assets.id', 'exif.assetId')
|
.innerJoin('exif', 'assets.id', 'exif.assetId')
|
||||||
.select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo'));
|
.select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo'));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const withSmartSearch = <O>(qb: SelectQueryBuilder<DB, 'assets', O>, options?: { inner: boolean }) => {
|
export function withSmartSearch<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
|
||||||
const join = options?.inner
|
return qb
|
||||||
? qb.innerJoin('smart_search', 'assets.id', 'smart_search.assetId')
|
.leftJoin('smart_search', 'assets.id', 'smart_search.assetId')
|
||||||
: qb.leftJoin('smart_search', 'assets.id', 'smart_search.assetId');
|
.select(sql<number[]>`smart_search.embedding`.as('embedding'));
|
||||||
return join.select(sql<number[]>`smart_search.embedding`.as('embedding'));
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const withFaces = (eb: ExpressionBuilder<DB, 'assets'>) =>
|
export function withFaces(eb: ExpressionBuilder<DB, 'assets'>) {
|
||||||
jsonArrayFrom(eb.selectFrom('asset_faces').selectAll().whereRef('asset_faces.assetId', '=', 'assets.id')).as('faces');
|
return jsonArrayFrom(eb.selectFrom('asset_faces').selectAll().whereRef('asset_faces.assetId', '=', 'assets.id')).as(
|
||||||
|
'faces',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const withFiles = (eb: ExpressionBuilder<DB, 'assets'>, type?: AssetFileType) =>
|
export function withFiles(eb: ExpressionBuilder<DB, 'assets'>, type?: AssetFileType) {
|
||||||
jsonArrayFrom(
|
return jsonArrayFrom(
|
||||||
eb
|
eb
|
||||||
.selectFrom('asset_files')
|
.selectFrom('asset_files')
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.whereRef('asset_files.assetId', '=', 'assets.id')
|
.whereRef('asset_files.assetId', '=', 'assets.id')
|
||||||
.$if(!!type, (qb) => qb.where('type', '=', type!)),
|
.$if(!!type, (qb) => qb.where('type', '=', type!)),
|
||||||
).as('files');
|
).as('files');
|
||||||
|
}
|
||||||
|
|
||||||
export const withFacesAndPeople = (eb: ExpressionBuilder<DB, 'assets'>) =>
|
export function withFacesAndPeople(eb: ExpressionBuilder<DB, 'assets'>) {
|
||||||
eb
|
return eb
|
||||||
.selectFrom('asset_faces')
|
.selectFrom('asset_faces')
|
||||||
.leftJoin('person', 'person.id', 'asset_faces.personId')
|
.leftJoin('person', 'person.id', 'asset_faces.personId')
|
||||||
.whereRef('asset_faces.assetId', '=', 'assets.id')
|
.whereRef('asset_faces.assetId', '=', 'assets.id')
|
||||||
@ -234,10 +237,11 @@ export const withFacesAndPeople = (eb: ExpressionBuilder<DB, 'assets'>) =>
|
|||||||
.as('faces'),
|
.as('faces'),
|
||||||
)
|
)
|
||||||
.as('faces');
|
.as('faces');
|
||||||
|
}
|
||||||
|
|
||||||
/** Adds a `has_people` CTE that can be inner joined on to filter out assets */
|
/** Adds a `has_people` CTE that can be inner joined on to filter out assets */
|
||||||
export const hasPeopleCte = (db: Kysely<DB>, personIds: string[]) =>
|
export function hasPeopleCte(db: Kysely<DB>, personIds: string[]) {
|
||||||
db.with('has_people', (qb) =>
|
return db.with('has_people', (qb) =>
|
||||||
qb
|
qb
|
||||||
.selectFrom('asset_faces')
|
.selectFrom('asset_faces')
|
||||||
.select('assetId')
|
.select('assetId')
|
||||||
@ -245,66 +249,67 @@ export const hasPeopleCte = (db: Kysely<DB>, personIds: string[]) =>
|
|||||||
.groupBy('assetId')
|
.groupBy('assetId')
|
||||||
.having((eb) => eb.fn.count('personId'), '>=', personIds.length),
|
.having((eb) => eb.fn.count('personId'), '>=', personIds.length),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const hasPeople = (db: Kysely<DB>, personIds?: string[]) =>
|
export function hasPeople(db: Kysely<DB>, personIds?: string[]) {
|
||||||
personIds && personIds.length > 0
|
return personIds && personIds.length > 0
|
||||||
? hasPeopleCte(db, personIds).selectFrom('assets').innerJoin('has_people', 'has_people.assetId', 'assets.id')
|
? hasPeopleCte(db, personIds).selectFrom('assets').innerJoin('has_people', 'has_people.assetId', 'assets.id')
|
||||||
: db.selectFrom('assets');
|
: db.selectFrom('assets');
|
||||||
|
}
|
||||||
|
|
||||||
export const withOwner = (eb: ExpressionBuilder<DB, 'assets'>) =>
|
export function withOwner(eb: ExpressionBuilder<DB, 'assets'>) {
|
||||||
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');
|
||||||
|
}
|
||||||
|
|
||||||
export const withLibrary = (eb: ExpressionBuilder<DB, 'assets'>) =>
|
export function withLibrary(eb: ExpressionBuilder<DB, 'assets'>) {
|
||||||
jsonObjectFrom(eb.selectFrom('libraries').selectAll().whereRef('libraries.id', '=', 'assets.libraryId')).as(
|
return jsonObjectFrom(eb.selectFrom('libraries').selectAll().whereRef('libraries.id', '=', 'assets.libraryId')).as(
|
||||||
'library',
|
'library',
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type Stacked = SelectQueryBuilder<
|
export function withStackedAssets<O>(qb: SelectQueryBuilder<DB, 'assets' | 'asset_stack', O>) {
|
||||||
DB & { stacked: Selectable<Assets> },
|
return qb
|
||||||
'assets' | 'asset_stack' | 'stacked',
|
.innerJoinLateral(
|
||||||
{ assets: Selectable<Assets>[] }
|
(eb: ExpressionBuilder<DB, 'assets' | 'asset_stack'>) =>
|
||||||
>;
|
eb
|
||||||
|
.selectFrom('assets as stacked')
|
||||||
|
.select((eb) => eb.fn<Selectable<Assets>[]>('array_agg', [eb.table('stacked')]).as('assets'))
|
||||||
|
.whereRef('asset_stack.id', '=', 'stacked.stackId')
|
||||||
|
.whereRef('asset_stack.primaryAssetId', '!=', 'stacked.id')
|
||||||
|
.as('s'),
|
||||||
|
(join) =>
|
||||||
|
join.on((eb) =>
|
||||||
|
eb.or([eb('asset_stack.primaryAssetId', '=', eb.ref('assets.id')), eb('assets.stackId', 'is', null)]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.select('s.assets');
|
||||||
|
}
|
||||||
|
|
||||||
type StackExpression = (eb: Stacked) => Stacked;
|
export function withStack<O>(
|
||||||
|
|
||||||
export const withStack = <O>(
|
|
||||||
qb: SelectQueryBuilder<DB, 'assets', O>,
|
qb: SelectQueryBuilder<DB, 'assets', O>,
|
||||||
{ assets }: { assets?: boolean | StackExpression },
|
{ assets, count }: { assets: boolean; count: boolean },
|
||||||
) =>
|
) {
|
||||||
qb
|
return qb
|
||||||
.leftJoinLateral(
|
.leftJoinLateral(
|
||||||
(eb) =>
|
(eb) =>
|
||||||
eb
|
eb
|
||||||
.selectFrom('asset_stack')
|
.selectFrom('asset_stack')
|
||||||
.selectAll('asset_stack')
|
.selectAll('asset_stack')
|
||||||
.whereRef('assets.stackId', '=', 'asset_stack.id')
|
.whereRef('assets.stackId', '=', 'asset_stack.id')
|
||||||
.$if(!!assets, (qb) =>
|
.$if(assets, withStackedAssets)
|
||||||
qb
|
.$if(count, (qb) =>
|
||||||
.innerJoinLateral(
|
// There is no `selectNoFrom` method for expression builders
|
||||||
(eb: ExpressionBuilder<DB, 'assets' | 'asset_stack'>) =>
|
qb.select(
|
||||||
eb
|
sql`(select count(*) as "assetCount" where "asset_stack"."id" = "assets"."stackId")`.as('assetCount'),
|
||||||
.selectFrom('assets as stacked')
|
),
|
||||||
.select((eb) => eb.fn<Selectable<Assets>[]>('array_agg', [eb.table('stacked')]).as('assets'))
|
|
||||||
.whereRef('asset_stack.id', '=', 'stacked.stackId')
|
|
||||||
.whereRef('asset_stack.primaryAssetId', '!=', 'stacked.id')
|
|
||||||
.$if(typeof assets === 'function', assets as StackExpression)
|
|
||||||
.as('s'),
|
|
||||||
(join) =>
|
|
||||||
join.on((eb) =>
|
|
||||||
eb.or([
|
|
||||||
eb('asset_stack.primaryAssetId', '=', eb.ref('assets.id')),
|
|
||||||
eb('assets.stackId', 'is', null),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.select('s.assets'),
|
|
||||||
)
|
)
|
||||||
.as('stacked_assets'),
|
.as('stacked_assets'),
|
||||||
(join) => join.onTrue(),
|
(join) => join.onTrue(),
|
||||||
)
|
)
|
||||||
.select((eb) => eb.fn('to_jsonb', [eb.table('stacked_assets')]).as('stack'));
|
.select((eb) => eb.fn('to_jsonb', [eb.table('stacked_assets')]).as('stack'));
|
||||||
|
}
|
||||||
|
|
||||||
export const withAlbums = <O>(qb: SelectQueryBuilder<DB, 'assets', O>, { albumId }: { albumId?: string }) => {
|
export function withAlbums<O>(qb: SelectQueryBuilder<DB, 'assets', O>, { albumId }: { albumId?: string }) {
|
||||||
return qb
|
return qb
|
||||||
.select((eb) =>
|
.select((eb) =>
|
||||||
jsonArrayFrom(
|
jsonArrayFrom(
|
||||||
@ -330,16 +335,17 @@ export const withAlbums = <O>(qb: SelectQueryBuilder<DB, 'assets', O>, { albumId
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const withTags = (eb: ExpressionBuilder<DB, 'assets'>) =>
|
export function withTags(eb: ExpressionBuilder<DB, 'assets'>) {
|
||||||
jsonArrayFrom(
|
return jsonArrayFrom(
|
||||||
eb
|
eb
|
||||||
.selectFrom('tags')
|
.selectFrom('tags')
|
||||||
.selectAll('tags')
|
.selectAll('tags')
|
||||||
.innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId')
|
.innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId')
|
||||||
.whereRef('assets.id', '=', 'tag_asset.assetsId'),
|
.whereRef('assets.id', '=', 'tag_asset.assetsId'),
|
||||||
).as('tags');
|
).as('tags');
|
||||||
|
}
|
||||||
|
|
||||||
const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
|
const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ where
|
|||||||
and "assets"."isVisible" = $2
|
and "assets"."isVisible" = $2
|
||||||
and "deletedAt" is null
|
and "deletedAt" is null
|
||||||
order by
|
order by
|
||||||
"createdAt" asc
|
"createdAt"
|
||||||
limit
|
limit
|
||||||
$3
|
$3
|
||||||
offset
|
offset
|
||||||
@ -367,33 +367,60 @@ limit
|
|||||||
|
|
||||||
-- AssetRepository.getAllForUserFullSync
|
-- AssetRepository.getAllForUserFullSync
|
||||||
select
|
select
|
||||||
|
"assets".*,
|
||||||
|
to_jsonb("exif") as "exifInfo",
|
||||||
|
to_jsonb("stacked_assets") as "stack"
|
||||||
from
|
from
|
||||||
"assets"
|
"assets"
|
||||||
|
left join "exif" on "assets"."id" = "exif"."assetId"
|
||||||
|
left join lateral (
|
||||||
|
select
|
||||||
|
"asset_stack".*,
|
||||||
|
(
|
||||||
|
select
|
||||||
|
count(*) as "assetCount"
|
||||||
|
where
|
||||||
|
"asset_stack"."id" = "assets"."stackId"
|
||||||
|
) as "assetCount"
|
||||||
|
from
|
||||||
|
"asset_stack"
|
||||||
|
where
|
||||||
|
"assets"."stackId" = "asset_stack"."id"
|
||||||
|
) as "stacked_assets" on true
|
||||||
where
|
where
|
||||||
"ownerId" = $1::uuid
|
"assets"."ownerId" = $1::uuid
|
||||||
and "isVisible" = $2
|
and "isVisible" = $2
|
||||||
and "updatedAt" <= $3
|
and "updatedAt" <= $3
|
||||||
and "id" > $4
|
and "assets"."id" > $4
|
||||||
order by
|
order by
|
||||||
"id" asc
|
"assets"."id"
|
||||||
limit
|
limit
|
||||||
$5
|
$5
|
||||||
|
|
||||||
-- AssetRepository.getChangedDeltaSync
|
-- AssetRepository.getChangedDeltaSync
|
||||||
select
|
select
|
||||||
"assets".*,
|
"assets".*,
|
||||||
(
|
to_jsonb("exif") as "exifInfo",
|
||||||
|
to_jsonb("stacked_assets") as "stack"
|
||||||
|
from
|
||||||
|
"assets"
|
||||||
|
left join "exif" on "assets"."id" = "exif"."assetId"
|
||||||
|
left join lateral (
|
||||||
select
|
select
|
||||||
count(*) as "stackedAssetsCount"
|
"asset_stack".*,
|
||||||
|
(
|
||||||
|
select
|
||||||
|
count(*) as "assetCount"
|
||||||
|
where
|
||||||
|
"asset_stack"."id" = "assets"."stackId"
|
||||||
|
) as "assetCount"
|
||||||
from
|
from
|
||||||
"asset_stack"
|
"asset_stack"
|
||||||
where
|
where
|
||||||
"asset_stack"."id" = "assets"."stackId"
|
"assets"."stackId" = "asset_stack"."id"
|
||||||
) as "stackedAssetsCount"
|
) as "stacked_assets" on true
|
||||||
from
|
|
||||||
"assets"
|
|
||||||
where
|
where
|
||||||
"ownerId" = any ($1::uuid [])
|
"assets"."ownerId" = any ($1::uuid [])
|
||||||
and "isVisible" = $2
|
and "isVisible" = $2
|
||||||
and "updatedAt" > $3
|
and "updatedAt" > $3
|
||||||
limit
|
limit
|
||||||
|
@ -164,8 +164,8 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.$if(!!files, (qb) => qb.select(withFiles))
|
.$if(!!files, (qb) => qb.select(withFiles))
|
||||||
.$if(!!library, (qb) => qb.select(withLibrary))
|
.$if(!!library, (qb) => qb.select(withLibrary))
|
||||||
.$if(!!owner, (qb) => qb.select(withOwner))
|
.$if(!!owner, (qb) => qb.select(withOwner))
|
||||||
.$if(!!smartSearch, (qb) => withSmartSearch(qb))
|
.$if(!!smartSearch, withSmartSearch)
|
||||||
.$if(!!stack, (qb) => withStack(qb, { assets: stack!.assets }))
|
.$if(!!stack, (qb) => withStack(qb, { assets: !!stack!.assets, count: false }))
|
||||||
.$if(!!tags, (qb) => qb.select(withTags))
|
.$if(!!tags, (qb) => qb.select(withTags))
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.select(withFacesAndPeople)
|
.select(withFacesAndPeople)
|
||||||
.select(withTags)
|
.select(withTags)
|
||||||
.$call(withExif)
|
.$call(withExif)
|
||||||
.$call((qb) => withStack(qb, { assets: true }))
|
.$call((qb) => withStack(qb, { assets: true, count: false }))
|
||||||
.where('assets.id', '=', anyUuid(ids))
|
.where('assets.id', '=', anyUuid(ids))
|
||||||
.execute() as any as Promise<AssetEntity[]>;
|
.execute() as any as Promise<AssetEntity[]>;
|
||||||
}
|
}
|
||||||
@ -290,7 +290,7 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.$if(!!library, (qb) => qb.select(withLibrary))
|
.$if(!!library, (qb) => qb.select(withLibrary))
|
||||||
.$if(!!owner, (qb) => qb.select(withOwner))
|
.$if(!!owner, (qb) => qb.select(withOwner))
|
||||||
.$if(!!smartSearch, withSmartSearch)
|
.$if(!!smartSearch, withSmartSearch)
|
||||||
.$if(!!stack, (qb) => withStack(qb, { assets: stack!.assets }))
|
.$if(!!stack, (qb) => withStack(qb, { assets: !!stack!.assets, count: false }))
|
||||||
.$if(!!files, (qb) => qb.select(withFiles))
|
.$if(!!files, (qb) => qb.select(withFiles))
|
||||||
.$if(!!tags, (qb) => qb.select(withTags))
|
.$if(!!tags, (qb) => qb.select(withTags))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
@ -455,7 +455,7 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.limit(pagination.take + 1)
|
.limit(pagination.take + 1)
|
||||||
.offset(pagination.skip ?? 0)
|
.offset(pagination.skip ?? 0)
|
||||||
.orderBy('createdAt', 'asc')
|
.orderBy('createdAt')
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return paginationHelper(items as any as AssetEntity[], pagination.take);
|
return paginationHelper(items as any as AssetEntity[], pagination.take);
|
||||||
@ -586,7 +586,7 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.$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!))
|
||||||
.$if(!!options.withStacked, (qb) => withStack(qb, { assets: true })) // TODO: optimize this; it's a huge performance hit
|
.$if(!!options.withStacked, (qb) => withStack(qb, { assets: true, count: false })) // TODO: optimize this; it's a huge performance hit
|
||||||
.$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
|
.$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
|
||||||
.$if(options.isDuplicate !== undefined, (qb) =>
|
.$if(options.isDuplicate !== undefined, (qb) =>
|
||||||
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
||||||
@ -687,12 +687,12 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
.selectAll('assets')
|
.selectAll('assets')
|
||||||
.$call(withExif)
|
.$call(withExif)
|
||||||
.$call((qb) => withStack(qb, { assets: false }))
|
.$call((qb) => withStack(qb, { assets: false, count: true }))
|
||||||
.where('ownerId', '=', asUuid(ownerId))
|
.where('assets.ownerId', '=', asUuid(ownerId))
|
||||||
.where('isVisible', '=', true)
|
.where('isVisible', '=', true)
|
||||||
.where('updatedAt', '<=', updatedUntil)
|
.where('updatedAt', '<=', updatedUntil)
|
||||||
.$if(!!lastId, (qb) => qb.where('id', '>', lastId!))
|
.$if(!!lastId, (qb) => qb.where('assets.id', '>', lastId!))
|
||||||
.orderBy('id', 'asc')
|
.orderBy('assets.id')
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.execute() as any as Promise<AssetEntity[]>;
|
.execute() as any as Promise<AssetEntity[]>;
|
||||||
}
|
}
|
||||||
@ -703,8 +703,8 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
.selectAll('assets')
|
.selectAll('assets')
|
||||||
.$call(withExif)
|
.$call(withExif)
|
||||||
.$call((qb) => withStack(qb, { assets: false }))
|
.$call((qb) => withStack(qb, { assets: false, count: true }))
|
||||||
.where('ownerId', '=', anyUuid(options.userIds))
|
.where('assets.ownerId', '=', anyUuid(options.userIds))
|
||||||
.where('isVisible', '=', true)
|
.where('isVisible', '=', true)
|
||||||
.where('updatedAt', '>', options.updatedAfter)
|
.where('updatedAt', '>', options.updatedAfter)
|
||||||
.limit(options.limit)
|
.limit(options.limit)
|
||||||
|
Loading…
Reference in New Issue
Block a user