diff --git a/docker/docker-compose.test.yml b/docker/docker-compose.test.yml index daa6524f8..97ea6d4eb 100644 --- a/docker/docker-compose.test.yml +++ b/docker/docker-compose.test.yml @@ -23,8 +23,7 @@ services: - database database: - image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07 - command: -c fsync=off + image: tensorchord/pgvecto-rs:pg14-v0.1.10 environment: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index bfab4bb4f..7433bac79 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -210,7 +210,7 @@ export class SystemConfigCore { [FeatureFlag.MAP]: config.map.enabled, [FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled, [FeatureFlag.SIDECAR]: true, - [FeatureFlag.SEARCH]: process.env.TYPESENSE_ENABLED !== 'false', + [FeatureFlag.SEARCH]: true, [FeatureFlag.TRASH]: config.trash.enabled, // TODO: use these instead of `POST oauth/config` diff --git a/server/src/immich/main.ts b/server/src/immich/main.ts index 647957aa4..092252ef0 100644 --- a/server/src/immich/main.ts +++ b/server/src/immich/main.ts @@ -1,5 +1,5 @@ import { envName, getLogLevels, isDev, serverVersion } from '@app/domain'; -import { RedisIoAdapter, runVectorMigrations } from '@app/infra'; +import { RedisIoAdapter, dataSource } from '@app/infra'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; @@ -29,7 +29,7 @@ export async function bootstrap() { app.useStaticAssets('www'); app.use(indexFallback(excludePaths)); - await runVectorMigrations(); + await dataSource.query(`SET vectors.enable_prefilter = on`); const server = await app.listen(port); server.requestTimeout = 30 * 60 * 1000; diff --git a/server/src/infra/database.config.ts b/server/src/infra/database.config.ts index 20289723c..089fd6878 100644 --- a/server/src/infra/database.config.ts +++ b/server/src/infra/database.config.ts @@ -23,30 +23,5 @@ export const databaseConfig: PostgresConnectionOptions = { ...urlOrParts, }; -export const databaseConfigVector: PostgresConnectionOptions = { - ...databaseConfig, - migrationsRun: false, - migrations: [__dirname + '/migrations/vector/*.{js,ts}'], -}; - // this export is used by TypeORM commands in package.json#scripts -export let dataSource = new DataSource(databaseConfig); - -export async function runVectorMigrations(): Promise { - if (!dataSource.isInitialized) { - dataSource = await dataSource.initialize(); - } - - const hasVectorExtension = (await dataSource.query( - `SELECT * FROM pg_available_extensions WHERE name = 'vectors'`, - )).length > 0; - - if (hasVectorExtension) { - const dataSourceVector = await new DataSource(databaseConfigVector).initialize(); - await dataSourceVector.runMigrations(); - - await dataSourceVector.query(`SET vectors.enable_prefilter = on`); - - await dataSourceVector.destroy(); - } -} +export const dataSource = new DataSource(databaseConfig); diff --git a/server/src/infra/migrations/vector/1699746198141-UsePgVectors.ts b/server/src/infra/migrations/1699746198141-UsePgVectors.ts similarity index 100% rename from server/src/infra/migrations/vector/1699746198141-UsePgVectors.ts rename to server/src/infra/migrations/1699746198141-UsePgVectors.ts diff --git a/server/src/infra/migrations/vector/1699746301742-AddCLIPEmbeddingIndex.ts b/server/src/infra/migrations/1699746301742-AddCLIPEmbeddingIndex.ts similarity index 100% rename from server/src/infra/migrations/vector/1699746301742-AddCLIPEmbeddingIndex.ts rename to server/src/infra/migrations/1699746301742-AddCLIPEmbeddingIndex.ts diff --git a/server/src/infra/migrations/vector/1699746444644-AddFaceEmbeddingIndex.ts b/server/src/infra/migrations/1699746444644-AddFaceEmbeddingIndex.ts similarity index 100% rename from server/src/infra/migrations/vector/1699746444644-AddFaceEmbeddingIndex.ts rename to server/src/infra/migrations/1699746444644-AddFaceEmbeddingIndex.ts diff --git a/server/src/infra/repositories/person.repository.ts b/server/src/infra/repositories/person.repository.ts index 892cf07f3..5eda03cf0 100644 --- a/server/src/infra/repositories/person.repository.ts +++ b/server/src/infra/repositories/person.repository.ts @@ -227,8 +227,10 @@ export class PersonRepository implements IPersonRepository { if (!entity.personId) { throw new Error('Person ID is required to create a face'); } - const { embedding, ...face } = entity; - await this.assetFaceRepository.insert({ ...face, embedding: () => asVector(embedding, true) }); + if (!entity.embedding) { + throw new Error('Embedding is required to create a face'); + } + await this.assetFaceRepository.insert({ ...entity, embedding: () => asVector(entity.embedding, true) }); return this.assetFaceRepository.findOneByOrFail({ assetId: entity.assetId, personId: entity.personId }); } diff --git a/server/test/e2e/asset.e2e-spec.ts b/server/test/e2e/asset.e2e-spec.ts index 95cc92b6a..e749231b2 100644 --- a/server/test/e2e/asset.e2e-spec.ts +++ b/server/test/e2e/asset.e2e-spec.ts @@ -764,7 +764,7 @@ describe(`${AssetController.name} (e2e)`, () => { const personRepository = app.get(IPersonRepository); const person = await personRepository.create({ ownerId: asset1.ownerId, name: 'Test Person' }); - await personRepository.createFace({ assetId: asset1.id, personId: person.id }); + await personRepository.createFace({ assetId: asset1.id, personId: person.id, embedding: Array.from({length: 512}, Math.random) }); const { status, body } = await request(server) .put(`/asset/${asset1.id}`) @@ -1339,7 +1339,7 @@ describe(`${AssetController.name} (e2e)`, () => { beforeEach(async () => { const personRepository = app.get(IPersonRepository); const person = await personRepository.create({ ownerId: asset1.ownerId, name: 'Test Person' }); - await personRepository.createFace({ assetId: asset1.id, personId: person.id }); + await personRepository.createFace({ assetId: asset1.id, personId: person.id, embedding: Array.from({length: 512}, Math.random) }); }); it('should not return asset with facesRecognizedAt unset', async () => { diff --git a/server/test/e2e/person.e2e-spec.ts b/server/test/e2e/person.e2e-spec.ts index e3796619e..30b1de9ed 100644 --- a/server/test/e2e/person.e2e-spec.ts +++ b/server/test/e2e/person.e2e-spec.ts @@ -37,7 +37,7 @@ describe(`${PersonController.name}`, () => { name: 'visible_person', thumbnailPath: '/thumbnail/face_asset', }); - await personRepository.createFace({ assetId: faceAsset.id, personId: visiblePerson.id }); + await personRepository.createFace({ assetId: faceAsset.id, personId: visiblePerson.id, embedding: Array.from({length: 512}, Math.random) }); hiddenPerson = await personRepository.create({ ownerId: loginResponse.userId, @@ -45,7 +45,7 @@ describe(`${PersonController.name}`, () => { isHidden: true, thumbnailPath: '/thumbnail/face_asset', }); - await personRepository.createFace({ assetId: faceAsset.id, personId: hiddenPerson.id }); + await personRepository.createFace({ assetId: faceAsset.id, personId: hiddenPerson.id, embedding: Array.from({length: 512}, Math.random) }); }); describe('GET /person', () => { diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index cc1c8defa..08ec0e2cf 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -81,7 +81,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { oauth: false, oauthAutoLaunch: false, passwordLogin: true, - search: false, + search: true, sidecar: true, tagImage: false, trash: true, diff --git a/server/test/test-utils.ts b/server/test/test-utils.ts index 0ef80e858..ae14ac4ae 100644 --- a/server/test/test-utils.ts +++ b/server/test/test-utils.ts @@ -20,6 +20,7 @@ export const db = { await dataSource.initialize(); } + await dataSource.query(`SET vectors.enable_prefilter = on`); await dataSource.transaction(async (em) => { const entities = options?.entities || []; const tableNames =