diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql index c4f6fbdd32..196a1d1609 100644 --- a/server/src/queries/album.repository.sql +++ b/server/src/queries/album.repository.sql @@ -450,19 +450,6 @@ WHERE ORDER BY "AlbumEntity"."createdAt" DESC --- AlbumRepository.removeAsset -DELETE FROM "albums_assets_assets" -WHERE - "albums_assets_assets"."assetsId" = $1 - --- AlbumRepository.removeAssetIds -DELETE FROM "albums_assets_assets" -WHERE - ( - "albumsId" = $1 - AND "assetsId" IN ($2) - ) - -- AlbumRepository.getAssetIds SELECT "albums_assets"."assetsId" AS "assetId" @@ -471,52 +458,3 @@ FROM WHERE "albums_assets"."albumsId" = $1 AND "albums_assets"."assetsId" IN ($2) - --- AlbumRepository.addAssetIds -INSERT INTO - "albums_assets_assets" ("albumsId", "assetsId") -VALUES - ($1, $2) - --- AlbumRepository.updateThumbnails -UPDATE "albums" -SET - "albumThumbnailAssetId" = ( - SELECT - "album_assets"."assetsId" - FROM - "albums_assets_assets" "album_assets" - INNER JOIN "assets" "assets" ON "album_assets"."assetsId" = "assets"."id" - AND "assets"."deletedAt" IS NULL - WHERE - "album_assets"."albumsId" = "albums"."id" - ORDER BY - "assets"."fileCreatedAt" DESC - LIMIT - 1 - ), - "updatedAt" = CURRENT_TIMESTAMP -WHERE - "albums"."albumThumbnailAssetId" IS NULL - AND EXISTS ( - SELECT - 1 - FROM - "albums_assets_assets" "album_assets" - INNER JOIN "assets" "assets" ON "album_assets"."assetsId" = "assets"."id" - AND "assets"."deletedAt" IS NULL - WHERE - "album_assets"."albumsId" = "albums"."id" - ) - OR "albums"."albumThumbnailAssetId" IS NOT NULL - AND NOT EXISTS ( - SELECT - 1 - FROM - "albums_assets_assets" "album_assets" - INNER JOIN "assets" "assets" ON "album_assets"."assetsId" = "assets"."id" - AND "assets"."deletedAt" IS NULL - WHERE - "album_assets"."albumsId" = "albums"."id" - AND "albums"."albumThumbnailAssetId" = "album_assets"."assetsId" - ) diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index 439d66b82d..9c506d3ad8 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -9,12 +9,16 @@ with make_date(year::int, $1::int, $2::int) as "date" from generate_series( - $3, - extract( - year + ( + select + date_part( + 'year', + min((("localDateTime" at time zone 'UTC')::date)) + )::int from - current_date - ) - 1 + assets + ), + date_part('year', current_date)::int - 1 ) as "year" ) select @@ -31,20 +35,20 @@ with where "asset_job_status"."previewAt" is not null and (assets."localDateTime" at time zone 'UTC')::date = today.date - and "assets"."ownerId" = any ($4::uuid []) - and "assets"."isVisible" = $5 - and "assets"."isArchived" = $6 + and "assets"."ownerId" = any ($3::uuid []) + and "assets"."isVisible" = $4 + and "assets"."isArchived" = $5 and exists ( select from "asset_files" where "assetId" = "assets"."id" - and "asset_files"."type" = $7 + and "asset_files"."type" = $6 ) and "assets"."deletedAt" is null limit - $8 + $7 ) as "a" on true inner join "exif" on "a"."id" = "exif"."assetId" ) @@ -60,7 +64,7 @@ group by order by ("localDateTime" at time zone 'UTC')::date desc limit - $9 + $8 -- AssetRepository.getByIds select @@ -208,6 +212,17 @@ where limit $4 +-- AssetRepository.getByChecksums +select + "id", + "checksum", + "deletedAt" +from + "assets" +where + "ownerId" = $1::uuid + and "checksum" in ($2) + -- AssetRepository.getUploadAssetIdByChecksum select "id" @@ -274,6 +289,52 @@ where order by "assets"."localDateTime" desc +-- AssetRepository.getDuplicates +with + "duplicates" as ( + select + "duplicateId", + jsonb_agg("assets") as "assets" + from + "assets" + where + "ownerId" = $1::uuid + and "duplicateId" is not null + and "deletedAt" is null + and "isVisible" = $2 + group by + "duplicateId" + ), + "unique" as ( + select + "duplicateId" + from + "duplicates" + where + jsonb_array_length("assets") = $3 + ), + "removed_unique" as ( + update "assets" + set + "duplicateId" = $4 + from + "unique" + where + "assets"."duplicateId" = "unique"."duplicateId" + ) +select + * +from + "duplicates" +where + not exists ( + select + from + "unique" + where + "unique"."duplicateId" = "duplicates"."duplicateId" + ) + -- AssetRepository.getAssetIdByCity with "cities" as ( @@ -317,3 +378,23 @@ order by "id" asc limit $5 + +-- AssetRepository.getChangedDeltaSync +select + "assets".*, + ( + select + count(*) as "stackedAssetsCount" + from + "asset_stack" + where + "asset_stack"."id" = "assets"."stackId" + ) as "stackedAssetsCount" +from + "assets" +where + "ownerId" = any ($1::uuid []) + and "isVisible" = $2 + and "updatedAt" > $3 +limit + $4 diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql index 691734e224..e3945ca028 100644 --- a/server/src/queries/memory.repository.sql +++ b/server/src/queries/memory.repository.sql @@ -8,17 +8,3 @@ FROM WHERE "memories_assets"."memoriesId" = $1 AND "memories_assets"."assetsId" IN ($2) - --- MemoryRepository.addAssetIds -INSERT INTO - "memories_assets_assets" ("memoriesId", "assetsId") -VALUES - ($1, $2) - --- MemoryRepository.removeAssetIds -DELETE FROM "memories_assets_assets" -WHERE - ( - "memoriesId" = $1 - AND "assetsId" IN ($2) - ) diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql index 19322e684b..a6e93bd480 100644 --- a/server/src/queries/search.repository.sql +++ b/server/src/queries/search.repository.sql @@ -20,6 +20,47 @@ limit offset $7 +-- SearchRepository.searchRandom +( + select + "assets".* + from + "assets" + inner join "exif" on "assets"."id" = "exif"."assetId" + where + "assets"."fileCreatedAt" >= $1 + and "exif"."lensModel" = $2 + and "assets"."ownerId" = any ($3::uuid []) + and "assets"."isFavorite" = $4 + and "assets"."isArchived" = $5 + and "assets"."deletedAt" is null + and "assets"."id" < $6 + order by + "assets"."id" + limit + $7 +) +union all +( + select + "assets".* + from + "assets" + inner join "exif" on "assets"."id" = "exif"."assetId" + where + "assets"."fileCreatedAt" >= $8 + and "exif"."lensModel" = $9 + and "assets"."ownerId" = any ($10::uuid []) + and "assets"."isFavorite" = $11 + and "assets"."isArchived" = $12 + and "assets"."deletedAt" is null + and "assets"."id" > $13 + order by + "assets"."id" + limit + $14 +) + -- SearchRepository.searchSmart select "assets".* @@ -41,6 +82,34 @@ limit offset $8 +-- SearchRepository.searchDuplicates +with + "cte" as ( + select + "assets"."id" as "assetId", + "assets"."duplicateId", + smart_search.embedding <= > $1::vector as "distance" + from + "assets" + inner join "smart_search" on "assets"."id" = "smart_search"."assetId" + where + "assets"."ownerId" = any ($2::uuid []) + and "assets"."deletedAt" is null + and "assets"."isVisible" = $3 + and "assets"."type" = $4 + and "assets"."id" != $5::uuid + order by + smart_search.embedding <= > $6::vector + limit + $7 + ) +select + * +from + "cte" +where + "cte"."distance" <= $8 + -- SearchRepository.searchFaces with "cte" as ( diff --git a/server/src/queries/tag.repository.sql b/server/src/queries/tag.repository.sql index ba1aac82b3..580344c597 100644 --- a/server/src/queries/tag.repository.sql +++ b/server/src/queries/tag.repository.sql @@ -8,23 +8,3 @@ FROM WHERE "tag_asset"."tagsId" = $1 AND "tag_asset"."assetsId" IN ($2) - --- TagRepository.addAssetIds -INSERT INTO - "tag_asset" ("assetsId", "tagsId") -VALUES - ($1, $2) - --- TagRepository.removeAssetIds -DELETE FROM "tag_asset" -WHERE - ( - "tagsId" = $1 - AND "assetsId" IN ($2) - ) - --- TagRepository.upsertAssetIds -INSERT INTO - "tag_asset" ("assetsId", "tagsId") -VALUES - ($1, $2) diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index 8b7565e318..8ac352e945 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -153,7 +153,6 @@ export class AlbumRepository implements IAlbumRepository { await this.repository.delete({ ownerId: userId }); } - @GenerateSql({ params: [DummyValue.UUID] }) async removeAsset(assetId: string): Promise { // Using dataSource, because there is no direct access to albums_assets_assets. await this.dataSource @@ -164,7 +163,6 @@ export class AlbumRepository implements IAlbumRepository { .execute(); } - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) @Chunked({ paramIndex: 1 }) async removeAssetIds(albumId: string, assetIds: string[]): Promise { if (assetIds.length === 0) { @@ -207,7 +205,6 @@ export class AlbumRepository implements IAlbumRepository { return new Set(results.map(({ assetId }) => assetId)); } - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) async addAssetIds(albumId: string, assetIds: string[]): Promise { await this.addAssets(this.dataSource.manager, albumId, assetIds); } @@ -272,7 +269,6 @@ export class AlbumRepository implements IAlbumRepository { * * @returns Amount of updated album thumbnails or undefined when unknown */ - @GenerateSql() async updateThumbnails(): Promise { // Subquery for getting a new thumbnail. diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index a5c0e2268c..15ed00b570 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -92,15 +92,18 @@ export class AssetRepository implements IAssetRepository { @GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] }) getByDayOfYear(ownerIds: string[], { day, month }: MonthDay): Promise { - // TODO: CREATE INDEX idx_local_date_time ON public.assets ((("localDateTime" at time zone 'UTC')::date)); - // TODO: drop IDX_day_of_month and IDX_month return this.db .with('res', (qb) => qb .with('today', (qb) => qb .selectFrom((eb) => - eb.fn('generate_series', [eb.val(1970), sql`extract(year from current_date) - 1`]).as('year'), + eb + .fn('generate_series', [ + sql`(select date_part('year', min((("localDateTime" at time zone 'UTC')::date)))::int from assets)`, + sql`date_part('year', current_date)::int - 1`, + ]) + .as('year'), ) .select((eb) => eb.fn('make_date', [sql`year::int`, sql`${month}::int`, sql`${day}::int`]).as('date')), ) @@ -348,7 +351,7 @@ export class AssetRepository implements IAssetRepository { .executeTakeFirst() as Promise; } - @GenerateSql({ params: [DummyValue.UUID, DummyValue.BUFFER] }) + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.BUFFER]] }) getByChecksums(userId: string, checksums: Buffer[]): Promise { return this.db .selectFrom('assets') @@ -576,7 +579,6 @@ export class AssetRepository implements IAssetRepository { @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH }] }) async getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise { - // TODO: CREATE INDEX idx_local_date_time_month ON public.assets (date_trunc('MONTH', "localDateTime" at time zone 'UTC')); return hasPeople(this.db, options.personId ? [options.personId] : undefined) .selectAll('assets') .$call(withExif) @@ -601,7 +603,7 @@ export class AssetRepository implements IAssetRepository { .execute() as any as Promise; } - @GenerateSql({ params: [{ userIds: [DummyValue.UUID, DummyValue.UUID] }] }) + @GenerateSql({ params: [DummyValue.UUID] }) getDuplicates(userId: string): Promise { return ( this.db @@ -692,7 +694,7 @@ export class AssetRepository implements IAssetRepository { .execute() as any as Promise; } - @GenerateSql({ params: [{ userIds: [DummyValue.UUID], updatedAfter: DummyValue.DATE }] }) + @GenerateSql({ params: [{ userIds: [DummyValue.UUID], updatedAfter: DummyValue.DATE, limit: 100 }] }) async getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise { return this.db .selectFrom('assets') @@ -711,7 +713,6 @@ export class AssetRepository implements IAssetRepository { .execute() as any as Promise; } - @GenerateSql({ params: [{ assetId: DummyValue.UUID, type: AssetFileType.PREVIEW, path: '/path/to/file' }] }) async upsertFile(file: Pick, 'assetId' | 'path' | 'type'>): Promise { const value = { ...file, assetId: asUuid(file.assetId) }; await this.db @@ -725,7 +726,6 @@ export class AssetRepository implements IAssetRepository { .execute(); } - @GenerateSql({ params: [{ assetId: DummyValue.UUID, type: AssetFileType.PREVIEW, path: '/path/to/file' }] }) async upsertFiles(files: Pick, 'assetId' | 'path' | 'type'>[]): Promise { if (files.length === 0) { return; diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts index 3c2a1ae191..47dc705093 100644 --- a/server/src/repositories/memory.repository.ts +++ b/server/src/repositories/memory.repository.ts @@ -64,7 +64,6 @@ export class MemoryRepository implements IMemoryRepository { return new Set(results.map(({ assetId }) => assetId)); } - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) async addAssetIds(id: string, assetIds: string[]): Promise { await this.dataSource .createQueryBuilder() @@ -74,7 +73,6 @@ export class MemoryRepository implements IMemoryRepository { .execute(); } - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) @Chunked({ paramIndex: 1 }) async removeAssetIds(id: string, assetIds: string[]): Promise { await this.dataSource diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 0dd3a691b1..0c01f3409d 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -6,6 +6,7 @@ import { DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetEntity, searchAssetBuilder } from 'src/entities/asset.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; +import { AssetType } from 'src/enum'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AssetDuplicateSearch, @@ -71,12 +72,9 @@ export class SearchRepository implements ISearchRepository { searchRandom(size: number, options: AssetSearchOptions): Promise { const uuid = randomUUID(); const builder = searchAssetBuilder(this.db, options); - return builder - .where('assets.id', '>', uuid) - .orderBy('assets.id') - .limit(size) - .unionAll(() => builder.where('assets.id', '<', uuid).orderBy('assets.id').limit(size)) - .execute() as any as Promise; + const lessThan = builder.where('assets.id', '<', uuid).orderBy('assets.id').limit(size); + const greaterThan = builder.where('assets.id', '>', uuid).orderBy('assets.id').limit(size); + return sql`${lessThan} union all ${greaterThan}`.execute(this.db) as any as Promise; } @GenerateSql({ @@ -112,8 +110,10 @@ export class SearchRepository implements ISearchRepository { @GenerateSql({ params: [ { + assetId: DummyValue.UUID, embedding: Array.from({ length: 512 }, Math.random), maxDistance: 0.6, + type: AssetType.IMAGE, userIds: [DummyValue.UUID], }, ], @@ -134,7 +134,7 @@ export class SearchRepository implements ISearchRepository { .where('assets.deletedAt', 'is', null) .where('assets.isVisible', '=', true) .where('assets.type', '=', type) - .where('assets.id', '!=', assetId) + .where('assets.id', '!=', asUuid(assetId)) .orderBy(sql`smart_search.embedding <=> ${vector}`) .limit(64), ) diff --git a/server/src/repositories/tag.repository.ts b/server/src/repositories/tag.repository.ts index df5f7e6e42..994a49fc3d 100644 --- a/server/src/repositories/tag.repository.ts +++ b/server/src/repositories/tag.repository.ts @@ -108,7 +108,6 @@ export class TagRepository implements ITagRepository { return new Set(results.map(({ assetId }) => assetId)); } - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) async addAssetIds(tagId: string, assetIds: string[]): Promise { if (assetIds.length === 0) { return; @@ -122,7 +121,6 @@ export class TagRepository implements ITagRepository { .execute(); } - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) @Chunked({ paramIndex: 1 }) async removeAssetIds(tagId: string, assetIds: string[]): Promise { if (assetIds.length === 0) { @@ -140,7 +138,6 @@ export class TagRepository implements ITagRepository { .execute(); } - @GenerateSql({ params: [[{ assetId: DummyValue.UUID, tagId: DummyValue.UUID }]] }) @Chunked() async upsertAssetIds(items: AssetTagItem[]): Promise { if (items.length === 0) {