mirror of
https://github.com/immich-app/immich
synced 2026-08-29 13:15:45 +00:00
reuse pagination options
This commit is contained in:
parent
34c25b43fc
commit
8981d5ac29
5 changed files with 40 additions and 45 deletions
|
|
@ -20,7 +20,7 @@ import {
|
|||
withExifInner,
|
||||
withSearchOrder,
|
||||
} from 'src/utils/database';
|
||||
import { paginationHelper } from 'src/utils/pagination';
|
||||
import { paginationHelper, PaginationOptions } from 'src/utils/pagination';
|
||||
import z from 'zod';
|
||||
|
||||
export interface SearchAssetIdOptions {
|
||||
|
|
@ -149,11 +149,6 @@ export interface AssetSearchBuilderV3Options {
|
|||
order?: SearchOrder;
|
||||
}
|
||||
|
||||
export interface AssetSearchPaginationV3Options {
|
||||
size: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export type SmartSearchOptions = SearchDateOptions &
|
||||
SearchEmbeddingOptions &
|
||||
SearchExifOptions &
|
||||
|
|
@ -532,16 +527,16 @@ export class SearchRepository {
|
|||
// TODO(v4): drop the V3 suffix once the legacy methods are removed
|
||||
@GenerateSql(...searchMetadataV3Examples)
|
||||
async searchMetadataV3(
|
||||
pagination: AssetSearchPaginationV3Options,
|
||||
pagination: PaginationOptions,
|
||||
options: AssetSearchBuilderV3Options,
|
||||
scope: AssetSearchScope,
|
||||
) {
|
||||
const items = await withSearchOrder(searchAssetBuilder(this.db, options, scope), options.order)
|
||||
.select(columns.searchAsset)
|
||||
.limit(pagination.size + 1)
|
||||
.offset(pagination.offset ?? 0)
|
||||
.limit(pagination.take + 1)
|
||||
.offset(pagination.skip ?? 0)
|
||||
.execute();
|
||||
return paginationHelper(items, pagination.size);
|
||||
return paginationHelper(items, pagination.take);
|
||||
}
|
||||
|
||||
// TODO(v4): drop the V3 suffix once the legacy methods are removed
|
||||
|
|
@ -561,7 +556,7 @@ export class SearchRepository {
|
|||
// TODO(v4): drop the V3 suffix once the legacy methods are removed
|
||||
@GenerateSql(...searchSmartV3Examples)
|
||||
searchSmartV3(
|
||||
pagination: AssetSearchPaginationV3Options,
|
||||
pagination: PaginationOptions,
|
||||
options: Omit<AssetSearchBuilderV3Options, 'order'> & { embedding: string },
|
||||
scope: AssetSearchScope,
|
||||
) {
|
||||
|
|
@ -572,10 +567,10 @@ export class SearchRepository {
|
|||
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
|
||||
.orderBy(sql`smart_search.embedding <=> ${options.embedding}`)
|
||||
.orderBy('asset.id', 'asc')
|
||||
.limit(pagination.size + 1)
|
||||
.offset(pagination.offset ?? 0)
|
||||
.limit(pagination.take + 1)
|
||||
.offset(pagination.skip ?? 0)
|
||||
.execute();
|
||||
return paginationHelper(items, pagination.size);
|
||||
return paginationHelper(items, pagination.take);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ describe(SearchService.name, () => {
|
|||
mocks.machineLearning.encodeText.mockResolvedValue('[1, 2, 3]');
|
||||
await sut.searchSmart(auth, { size: 100, filter: {}, query: 'test' });
|
||||
expect(mocks.search.searchSmartV3).toHaveBeenCalledWith(
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
expect.objectContaining({ embedding: '[1, 2, 3]' }),
|
||||
expect.objectContaining({ lockedOwnerId: expect.any(String) }),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ export class SearchService extends BaseService {
|
|||
const { offset } = decodeSearchCursor(dto.cursor);
|
||||
const size = dto.size;
|
||||
const { hasNextPage, items } = await this.searchRepository.searchMetadataV3(
|
||||
{ size, offset },
|
||||
{ take: size, skip: offset },
|
||||
{
|
||||
filter,
|
||||
withExif: dto.withExif,
|
||||
|
|
@ -287,7 +287,7 @@ export class SearchService extends BaseService {
|
|||
|
||||
// no cursor until a rank-aware pagination strategy for smart search is decided
|
||||
const { items } = await this.searchRepository.searchSmartV3(
|
||||
{ size: dto.size },
|
||||
{ take: dto.size },
|
||||
{ filter, withExif: dto.withExif, embedding },
|
||||
scope,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -817,12 +817,12 @@ export function withSearchOrder(qb: ReturnType<typeof searchAssetBuilder>, order
|
|||
const scopeExample: AssetSearchScope = { userIds: [DummyValue.UUID], lockedOwnerId: DummyValue.UUID };
|
||||
|
||||
export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
||||
{ name: 'baseline', params: [{ size: 100 }, {}, scopeExample] },
|
||||
{ name: 'empty', params: [{ size: 100 }, {}, { lockedOwnerId: DummyValue.UUID }] },
|
||||
{ name: 'baseline', params: [{ take: 100 }, {}, scopeExample] },
|
||||
{ name: 'empty', params: [{ take: 100 }, {}, { lockedOwnerId: DummyValue.UUID }] },
|
||||
{
|
||||
name: 'or-exif-only',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { or: [{ city: { eq: DummyValue.STRING } }] },
|
||||
},
|
||||
|
|
@ -831,12 +831,12 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
},
|
||||
{
|
||||
name: 'string-eq-null',
|
||||
params: [{ size: 100 }, { filter: { city: { eq: null } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { city: { eq: null } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'string-pattern-like',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { description: { like: DummyValue.STRING } },
|
||||
},
|
||||
|
|
@ -846,7 +846,7 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
{
|
||||
name: 'string-pattern-notLike',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { description: { notLike: DummyValue.STRING } },
|
||||
},
|
||||
|
|
@ -856,7 +856,7 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
{
|
||||
name: 'string-pattern-startsWith',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { originalFileName: { startsWith: DummyValue.STRING } },
|
||||
},
|
||||
|
|
@ -865,16 +865,16 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
},
|
||||
{
|
||||
name: 'string-similarity-ocr',
|
||||
params: [{ size: 100 }, { filter: { ocr: { matches: DummyValue.STRING } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { ocr: { matches: DummyValue.STRING } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-any',
|
||||
params: [{ size: 100 }, { filter: { albumIds: { any: [DummyValue.UUID] } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { albumIds: { any: [DummyValue.UUID] } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-all',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { personIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } },
|
||||
},
|
||||
|
|
@ -883,16 +883,16 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
},
|
||||
{
|
||||
name: 'ids-all-single',
|
||||
params: [{ size: 100 }, { filter: { albumIds: { all: [DummyValue.UUID] } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { albumIds: { all: [DummyValue.UUID] } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-none',
|
||||
params: [{ size: 100 }, { filter: { tagIds: { none: [DummyValue.UUID] } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { tagIds: { none: [DummyValue.UUID] } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-tags-all',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { tagIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } },
|
||||
},
|
||||
|
|
@ -901,16 +901,16 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
},
|
||||
{
|
||||
name: 'has-albums-false',
|
||||
params: [{ size: 100 }, { filter: { hasAlbums: { eq: false } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { hasAlbums: { eq: false } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'is-encoded',
|
||||
params: [{ size: 100 }, { filter: { isEncoded: { eq: true } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { isEncoded: { eq: true } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'number-range',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { fileSizeInBytes: { gte: 100, lte: 1000 } },
|
||||
},
|
||||
|
|
@ -919,12 +919,12 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
},
|
||||
{
|
||||
name: 'date-eq',
|
||||
params: [{ size: 100 }, { filter: { takenAt: { eq: DummyValue.DATE } } }, scopeExample],
|
||||
params: [{ take: 100 }, { filter: { takenAt: { eq: DummyValue.DATE } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'date-range',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: { takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE } },
|
||||
},
|
||||
|
|
@ -934,7 +934,7 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
{
|
||||
name: 'order-fileSize-noExif',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
order: { field: SearchOrderField.FileSizeInBytes, direction: AssetOrder.Desc },
|
||||
withExif: false,
|
||||
|
|
@ -945,7 +945,7 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
{
|
||||
name: 'order-rating-withExif',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
order: { field: SearchOrderField.Rating, direction: AssetOrder.Asc },
|
||||
withExif: true,
|
||||
|
|
@ -956,7 +956,7 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
{
|
||||
name: 'or-branches',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: {
|
||||
or: [{ isFavorite: { eq: true } }, { personIds: { any: [DummyValue.UUID] } }],
|
||||
|
|
@ -968,7 +968,7 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
{
|
||||
name: 'or-with-top-level',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
filter: {
|
||||
takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE },
|
||||
|
|
@ -980,7 +980,7 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
|||
},
|
||||
{
|
||||
name: 'cursor-offset',
|
||||
params: [{ size: 100, offset: 100 }, { filter: { isFavorite: { eq: true } } }, scopeExample],
|
||||
params: [{ take: 100, skip: 100 }, { filter: { isFavorite: { eq: true } } }, scopeExample],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -995,12 +995,12 @@ export const searchRandomV3Examples: GenerateSqlQueries[] = [
|
|||
export const searchSmartV3Examples: GenerateSqlQueries[] = [
|
||||
{
|
||||
name: 'baseline',
|
||||
params: [{ size: 100 }, { embedding: DummyValue.VECTOR }, scopeExample],
|
||||
params: [{ take: 100 }, { embedding: DummyValue.VECTOR }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'with-filter',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{ take: 100 },
|
||||
{
|
||||
embedding: DummyValue.VECTOR,
|
||||
filter: { takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE } },
|
||||
|
|
@ -1010,7 +1010,7 @@ export const searchSmartV3Examples: GenerateSqlQueries[] = [
|
|||
},
|
||||
{
|
||||
name: 'cursor-offset',
|
||||
params: [{ size: 100, offset: 100 }, { embedding: DummyValue.VECTOR }, scopeExample],
|
||||
params: [{ take: 100, skip: 100 }, { embedding: DummyValue.VECTOR }, scopeExample],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -406,12 +406,12 @@ describe(SearchService.name, () => {
|
|||
|
||||
const options = { filter: {}, embedding: unitVector(0) };
|
||||
const scope = { userIds: [user.id], lockedOwnerId: user.id };
|
||||
const firstPage = await searchRepository.searchSmartV3({ size: 2 }, options, scope);
|
||||
const firstPage = await searchRepository.searchSmartV3({ take: 2 }, options, scope);
|
||||
expect(firstPage.items.length).toBe(2);
|
||||
expect(firstPage.items[0].id).toBe(assetIds[0]);
|
||||
expect(firstPage.hasNextPage).toBe(true);
|
||||
|
||||
const secondPage = await searchRepository.searchSmartV3({ size: 2, offset: 2 }, options, scope);
|
||||
const secondPage = await searchRepository.searchSmartV3({ take: 2, skip: 2 }, options, scope);
|
||||
expect(secondPage.items.length).toBe(1);
|
||||
expect(secondPage.hasNextPage).toBe(false);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue