diff --git a/server/src/domain/audit/audit.service.ts b/server/src/domain/audit/audit.service.ts index 887b72e2cda5c..5e4529fbdcbc7 100644 --- a/server/src/domain/audit/audit.service.ts +++ b/server/src/domain/audit/audit.service.ts @@ -167,7 +167,7 @@ export class AuditService { `Found ${libraryFiles.size} original files, ${thumbFiles.size} thumbnails, ${videoFiles.size} encoded videos, ${profileFiles.size} profile files`, ); const pagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (options) => - this.assetRepository.getAll(options, { withDeleted: true }), + this.assetRepository.getAll(options, { status: { withDeleted: true } }), ); let assetCount = 0; diff --git a/server/src/domain/download/download.service.ts b/server/src/domain/download/download.service.ts index 03bd6fee60f21..4dcb67e687c12 100644 --- a/server/src/domain/download/download.service.ts +++ b/server/src/domain/download/download.service.ts @@ -122,7 +122,7 @@ export class DownloadService { const userId = dto.userId; await this.access.requirePermission(auth, Permission.TIMELINE_DOWNLOAD, userId); return usePagination(PAGINATION_SIZE, (pagination) => - this.assetRepository.getByUserId(pagination, userId, { isVisible: true }), + this.assetRepository.getByUserId(pagination, userId, { status: { isVisible: true } }), ); } diff --git a/server/src/domain/media/media.service.ts b/server/src/domain/media/media.service.ts index 68f861d7e2d55..0461738701ab5 100644 --- a/server/src/domain/media/media.service.ts +++ b/server/src/domain/media/media.service.ts @@ -240,7 +240,7 @@ export class MediaService { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force - ? this.assetRepository.getAll(pagination, { type: AssetType.VIDEO }) + ? this.assetRepository.getAll(pagination, { status: { type: AssetType.VIDEO } }) : this.assetRepository.getWithout(pagination, WithoutProperty.ENCODED_VIDEO); }); diff --git a/server/src/domain/trash/trash.service.ts b/server/src/domain/trash/trash.service.ts index b1a38f72c9983..3c421162da55e 100644 --- a/server/src/domain/trash/trash.service.ts +++ b/server/src/domain/trash/trash.service.ts @@ -33,7 +33,7 @@ export class TrashService { async restore(auth: AuthDto): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }), + this.assetRepository.getByUserId(pagination, auth.user.id, { date: { trashedBefore: DateTime.now().toJSDate() } }), ); for await (const assets of assetPagination) { @@ -44,7 +44,7 @@ export class TrashService { async empty(auth: AuthDto): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }), + this.assetRepository.getByUserId(pagination, auth.user.id, { date: { trashedBefore: DateTime.now().toJSDate() } }), ); for await (const assets of assetPagination) { diff --git a/server/src/infra/infra.utils.ts b/server/src/infra/infra.utils.ts index 86f33b89c5ea7..fee1a5f5e6330 100644 --- a/server/src/infra/infra.utils.ts +++ b/server/src/infra/infra.utils.ts @@ -124,10 +124,8 @@ export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator { export function searchAssetBuilder( builder: SelectQueryBuilder, - options: AssetSearchBuilderOptions, + { date, id, exif, path, relation, status }: AssetSearchBuilderOptions, ): SelectQueryBuilder { - const { date, id, exif, path, relation, status } = options; - if (date) { builder.andWhere( _.omitBy( diff --git a/server/src/infra/repositories/search.repository.ts b/server/src/infra/repositories/search.repository.ts index 7e89fa457323e..e0ec24c83710a 100644 --- a/server/src/infra/repositories/search.repository.ts +++ b/server/src/infra/repositories/search.repository.ts @@ -18,9 +18,9 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import _ from 'lodash'; import { Repository } from 'typeorm'; +import { vectorExt } from '../database.config'; import { DummyValue, GenerateSql } from '../infra.util'; import { asVector, isValidInteger, paginatedBuilder, searchAssetBuilder } from '../infra.utils'; -import { vectorExt } from '../database.config'; @Injectable() export class SearchRepository implements ISearchRepository { @@ -70,23 +70,37 @@ export class SearchRepository implements ISearchRepository { } @GenerateSql({ - params: [{ userIds: [DummyValue.UUID], embedding: Array.from({ length: 512 }, Math.random), numResults: 100 }], + params: [ + { + pagination: { page: 0, size: 100 }, + options: { + date: { takenAfter: DummyValue.DATE }, + embedding: Array.from({ length: 512 }, Math.random), + exif: { cameraModel: DummyValue.STRING }, + relation: { withStacked: true }, + status: { isFavorite: true }, + userIds: [DummyValue.UUID], + }, + }, + ], }) - async searchCLIP(pagination: SearchPaginationOptions, options: SmartSearchOptions): Paginated { + async searchCLIP( + pagination: SearchPaginationOptions, + { embedding, userIds, ...options }: SmartSearchOptions, + ): Paginated { let results: PaginationResult = { items: [], hasNextPage: false }; await this.assetRepository.manager.transaction(async (manager) => { - await manager.query(`SET LOCAL vectors.search_mode=vbase`); let builder = manager.createQueryBuilder(AssetEntity, 'asset'); builder = searchAssetBuilder(builder, options); builder - .innerJoin('a.smartSearch', 's') - .andWhere('a.ownerId IN (:...userIds )') - .orderBy('s.embedding <=> :embedding') - .setParameters({ userIds: options.userIds, embedding: asVector(options.embedding) }); + .innerJoin('asset.smartSearch', 'search') + .andWhere('asset.ownerId IN (:...userIds )') + .orderBy('search.embedding <=> :embedding') + .setParameters({ userIds, embedding: asVector(embedding) }); await manager.query(this.getRuntimeConfig(pagination.size)); - return paginatedBuilder(builder, { + results = await paginatedBuilder(builder, { mode: PaginationMode.LIMIT_OFFSET, skip: pagination.page * pagination.size, take: pagination.size, diff --git a/server/src/infra/sql/search.repository.sql b/server/src/infra/sql/search.repository.sql new file mode 100644 index 0000000000000..73995316c26f3 --- /dev/null +++ b/server/src/infra/sql/search.repository.sql @@ -0,0 +1,43 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- SearchRepository.searchFaces +START TRANSACTION +SET + LOCAL vectors.enable_prefilter = on; + +SET + LOCAL vectors.search_mode = basic; + +SET + LOCAL vectors.hnsw_ef_search = 100 +WITH + "cte" AS ( + SELECT + "faces"."id" AS "id", + "faces"."assetId" AS "assetId", + "faces"."personId" AS "personId", + "faces"."imageWidth" AS "imageWidth", + "faces"."imageHeight" AS "imageHeight", + "faces"."boundingBoxX1" AS "boundingBoxX1", + "faces"."boundingBoxY1" AS "boundingBoxY1", + "faces"."boundingBoxX2" AS "boundingBoxX2", + "faces"."boundingBoxY2" AS "boundingBoxY2", + "faces"."embedding" <= > $1 AS "distance" + FROM + "asset_faces" "faces" + INNER JOIN "assets" "asset" ON "asset"."id" = "faces"."assetId" + AND ("asset"."deletedAt" IS NULL) + WHERE + "asset"."ownerId" IN ($2) + ORDER BY + "faces"."embedding" <= > $1 ASC + LIMIT + 100 + ) +SELECT + res.* +FROM + "cte" "res" +WHERE + res.distance <= $3 +COMMIT diff --git a/server/src/infra/sql/smart.info.repository.sql b/server/src/infra/sql/smart.info.repository.sql deleted file mode 100644 index 3151aede73a59..0000000000000 --- a/server/src/infra/sql/smart.info.repository.sql +++ /dev/null @@ -1,129 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SmartInfoRepository.searchCLIP -START TRANSACTION -SET - LOCAL vectors.enable_prefilter = on; - -SET - LOCAL vectors.search_mode = vbase; - -SET - LOCAL vectors.hnsw_ef_search = 100; -SELECT - "a"."id" AS "a_id", - "a"."deviceAssetId" AS "a_deviceAssetId", - "a"."ownerId" AS "a_ownerId", - "a"."libraryId" AS "a_libraryId", - "a"."deviceId" AS "a_deviceId", - "a"."type" AS "a_type", - "a"."originalPath" AS "a_originalPath", - "a"."resizePath" AS "a_resizePath", - "a"."webpPath" AS "a_webpPath", - "a"."thumbhash" AS "a_thumbhash", - "a"."encodedVideoPath" AS "a_encodedVideoPath", - "a"."createdAt" AS "a_createdAt", - "a"."updatedAt" AS "a_updatedAt", - "a"."deletedAt" AS "a_deletedAt", - "a"."fileCreatedAt" AS "a_fileCreatedAt", - "a"."localDateTime" AS "a_localDateTime", - "a"."fileModifiedAt" AS "a_fileModifiedAt", - "a"."isFavorite" AS "a_isFavorite", - "a"."isArchived" AS "a_isArchived", - "a"."isExternal" AS "a_isExternal", - "a"."isReadOnly" AS "a_isReadOnly", - "a"."isOffline" AS "a_isOffline", - "a"."checksum" AS "a_checksum", - "a"."duration" AS "a_duration", - "a"."isVisible" AS "a_isVisible", - "a"."livePhotoVideoId" AS "a_livePhotoVideoId", - "a"."originalFileName" AS "a_originalFileName", - "a"."sidecarPath" AS "a_sidecarPath", - "a"."stackId" AS "a_stackId", - "e"."assetId" AS "e_assetId", - "e"."description" AS "e_description", - "e"."exifImageWidth" AS "e_exifImageWidth", - "e"."exifImageHeight" AS "e_exifImageHeight", - "e"."fileSizeInByte" AS "e_fileSizeInByte", - "e"."orientation" AS "e_orientation", - "e"."dateTimeOriginal" AS "e_dateTimeOriginal", - "e"."modifyDate" AS "e_modifyDate", - "e"."timeZone" AS "e_timeZone", - "e"."latitude" AS "e_latitude", - "e"."longitude" AS "e_longitude", - "e"."projectionType" AS "e_projectionType", - "e"."city" AS "e_city", - "e"."livePhotoCID" AS "e_livePhotoCID", - "e"."autoStackId" AS "e_autoStackId", - "e"."state" AS "e_state", - "e"."country" AS "e_country", - "e"."make" AS "e_make", - "e"."model" AS "e_model", - "e"."lensModel" AS "e_lensModel", - "e"."fNumber" AS "e_fNumber", - "e"."focalLength" AS "e_focalLength", - "e"."iso" AS "e_iso", - "e"."exposureTime" AS "e_exposureTime", - "e"."profileDescription" AS "e_profileDescription", - "e"."colorspace" AS "e_colorspace", - "e"."bitsPerSample" AS "e_bitsPerSample", - "e"."fps" AS "e_fps" -FROM - "assets" "a" - INNER JOIN "smart_search" "s" ON "s"."assetId" = "a"."id" - LEFT JOIN "exif" "e" ON "e"."assetId" = "a"."id" -WHERE - ( - "a"."ownerId" IN ($1) - AND "a"."isArchived" = false - AND "a"."isVisible" = true - AND "a"."fileCreatedAt" < NOW() - ) - AND ("a"."deletedAt" IS NULL) -ORDER BY - "s"."embedding" <= > $2 ASC -LIMIT - 100 -COMMIT - --- SmartInfoRepository.searchFaces -START TRANSACTION -SET - LOCAL vectors.enable_prefilter = on; - -SET - LOCAL vectors.search_mode = vbase; - -SET - LOCAL vectors.hnsw_ef_search = 100; -WITH - "cte" AS ( - SELECT - "faces"."id" AS "id", - "faces"."assetId" AS "assetId", - "faces"."personId" AS "personId", - "faces"."imageWidth" AS "imageWidth", - "faces"."imageHeight" AS "imageHeight", - "faces"."boundingBoxX1" AS "boundingBoxX1", - "faces"."boundingBoxY1" AS "boundingBoxY1", - "faces"."boundingBoxX2" AS "boundingBoxX2", - "faces"."boundingBoxY2" AS "boundingBoxY2", - "faces"."embedding" <= > $1 AS "distance" - FROM - "asset_faces" "faces" - INNER JOIN "assets" "asset" ON "asset"."id" = "faces"."assetId" - AND ("asset"."deletedAt" IS NULL) - WHERE - "asset"."ownerId" IN ($2) - ORDER BY - "faces"."embedding" <= > $1 ASC - LIMIT - 100 - ) -SELECT - res.* -FROM - "cte" "res" -WHERE - res.distance <= $3 -COMMIT