mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
Merge 77ed175b34 into af33a78d18
This commit is contained in:
commit
785fe6dd21
7 changed files with 197 additions and 56 deletions
|
|
@ -28,21 +28,15 @@ select
|
|||
"person".*
|
||||
from
|
||||
"person"
|
||||
inner join "asset_face" on "asset_face"."personId" = "person"."id"
|
||||
inner join "asset" on "asset_face"."assetId" = "asset"."id"
|
||||
and "asset"."visibility" = 'timeline'
|
||||
and "asset"."deletedAt" is null
|
||||
where
|
||||
"person"."ownerId" = $1
|
||||
and "asset_face"."deletedAt" is null
|
||||
and "asset_face"."isVisible" is true
|
||||
and "person"."isHidden" = $2
|
||||
group by
|
||||
"person"."id"
|
||||
having
|
||||
(
|
||||
"person"."name" != $3
|
||||
or count("asset_face"."assetId") >= COALESCE(
|
||||
or "person"."assetCount" >= COALESCE(
|
||||
(
|
||||
SELECT
|
||||
value -> 'people' ->> 'minimumFaces'
|
||||
|
|
@ -59,7 +53,7 @@ order by
|
|||
"person"."isHidden" asc,
|
||||
"person"."isFavorite" desc,
|
||||
NULLIF(person.name, '') is null asc,
|
||||
count("asset_face"."assetId") desc,
|
||||
"person"."assetCount" desc,
|
||||
NULLIF(person.name, '') asc nulls last,
|
||||
"person"."createdAt"
|
||||
limit
|
||||
|
|
@ -262,24 +256,7 @@ select
|
|||
from
|
||||
"person"
|
||||
where
|
||||
exists (
|
||||
select
|
||||
from
|
||||
"asset_face"
|
||||
where
|
||||
"asset_face"."personId" = "person"."id"
|
||||
and "asset_face"."deletedAt" is null
|
||||
and "asset_face"."isVisible" = $2
|
||||
and exists (
|
||||
select
|
||||
from
|
||||
"asset"
|
||||
where
|
||||
"asset"."id" = "asset_face"."assetId"
|
||||
and "asset"."visibility" = 'timeline'
|
||||
and "asset"."deletedAt" is null
|
||||
)
|
||||
)
|
||||
"person"."assetCount" > $2
|
||||
and "person"."ownerId" = $3
|
||||
|
||||
-- PersonRepository.refreshFaces
|
||||
|
|
|
|||
|
|
@ -152,23 +152,14 @@ export class PersonRepository {
|
|||
const items = await this.db
|
||||
.selectFrom('person')
|
||||
.selectAll('person')
|
||||
.innerJoin('asset_face', 'asset_face.personId', 'person.id')
|
||||
.innerJoin('asset', (join) =>
|
||||
join
|
||||
.onRef('asset_face.assetId', '=', 'asset.id')
|
||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.on('asset.deletedAt', 'is', null),
|
||||
)
|
||||
.where('person.ownerId', '=', userId)
|
||||
.where('asset_face.deletedAt', 'is', null)
|
||||
.where('asset_face.isVisible', 'is', true)
|
||||
.orderBy('person.isHidden', 'asc')
|
||||
.orderBy('person.isFavorite', 'desc')
|
||||
.having((eb) =>
|
||||
eb.or([
|
||||
eb('person.name', '!=', ''),
|
||||
eb(
|
||||
(innerEb) => innerEb.fn.count('asset_face.assetId'),
|
||||
'person.assetCount',
|
||||
'>=',
|
||||
sql<number>`COALESCE(
|
||||
(SELECT value -> 'people' ->> 'minimumFaces'
|
||||
|
|
@ -201,7 +192,7 @@ export class PersonRepository {
|
|||
.$if(!options?.closestFaceAssetId, (qb) =>
|
||||
qb
|
||||
.orderBy(sql`NULLIF(person.name, '') is null`, 'asc')
|
||||
.orderBy((eb) => eb.fn.count('asset_face.assetId'), 'desc')
|
||||
.orderBy('person.assetCount', 'desc')
|
||||
.orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast())
|
||||
.orderBy('person.createdAt'),
|
||||
)
|
||||
|
|
@ -369,24 +360,7 @@ export class PersonRepository {
|
|||
const zero = sql.lit(0);
|
||||
return this.db
|
||||
.selectFrom('person')
|
||||
.where((eb) =>
|
||||
eb.exists((eb) =>
|
||||
eb
|
||||
.selectFrom('asset_face')
|
||||
.whereRef('asset_face.personId', '=', 'person.id')
|
||||
.where('asset_face.deletedAt', 'is', null)
|
||||
.where('asset_face.isVisible', '=', true)
|
||||
.where((eb) =>
|
||||
eb.exists((eb) =>
|
||||
eb
|
||||
.selectFrom('asset')
|
||||
.whereRef('asset.id', '=', 'asset_face.assetId')
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.where('asset.deletedAt', 'is', null),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.where('person.assetCount', '>', 0)
|
||||
.where('person.ownerId', '=', userId)
|
||||
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>(), zero).as('total'))
|
||||
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>().filterWhere('isHidden', '=', true), zero).as('hidden'))
|
||||
|
|
|
|||
|
|
@ -219,6 +219,66 @@ export const person_delete_audit = registerFunction({
|
|||
END`,
|
||||
});
|
||||
|
||||
export const person_increment_asset_count = registerFunction({
|
||||
name: 'person_increment_asset_count',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
IF NEW."deletedAt" IS NULL AND NEW."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END`,
|
||||
});
|
||||
|
||||
export const person_update_asset_count = registerFunction({
|
||||
name: 'person_update_asset_count',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
IF OLD."personId" != NEW."personId" AND OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
|
||||
END IF;
|
||||
|
||||
IF OLD."personId" != NEW."personId" AND NEW."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
|
||||
IF
|
||||
OLD."personId" = NEW."personId" AND
|
||||
(OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL) OR
|
||||
(OLD."isVisible" IS true AND NEW."isVisible" IS false)
|
||||
THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
|
||||
IF
|
||||
OLD."personId" = NEW."personId" AND
|
||||
(OLD."deletedAt" IS NOT NULL AND NEW."deletedAt" IS NULL) OR
|
||||
(OLD."isVisible" IS false AND NEW."isVisible" IS true)
|
||||
THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END`,
|
||||
});
|
||||
|
||||
export const person_decrement_asset_count = registerFunction({
|
||||
name: 'person_decrement_asset_count',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
IF OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END`,
|
||||
});
|
||||
|
||||
export const user_metadata_audit = registerFunction({
|
||||
name: 'user_metadata_audit',
|
||||
returnType: 'TRIGGER',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`ALTER TABLE "person" ADD "assetCount" integer NOT NULL DEFAULT 0;`.execute(db);
|
||||
await sql`UPDATE "person" SET "assetCount" = (
|
||||
SELECT COUNT("assetId")
|
||||
FROM "asset_face"
|
||||
WHERE "personId" = "person".id AND "deletedAt" IS NULL AND "isVisible" IS true
|
||||
)`.execute(db);
|
||||
await sql`CREATE INDEX "idx_person_assetCount" ON "person" ("assetCount");`.execute(db);
|
||||
await sql`CREATE OR REPLACE FUNCTION person_increment_asset_count()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW."deletedAt" IS NULL AND NEW."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE OR REPLACE FUNCTION person_update_asset_count()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
IF OLD."personId" != NEW."personId" AND OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
|
||||
END IF;
|
||||
|
||||
IF OLD."personId" != NEW."personId" AND NEW."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
|
||||
IF
|
||||
OLD."personId" = NEW."personId" AND
|
||||
(OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL) OR
|
||||
(OLD."isVisible" IS true AND NEW."isVisible" IS false)
|
||||
THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
|
||||
IF
|
||||
OLD."personId" = NEW."personId" AND
|
||||
(OLD."deletedAt" IS NOT NULL AND NEW."deletedAt" IS NULL) OR
|
||||
(OLD."isVisible" IS false AND NEW."isVisible" IS true)
|
||||
THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE OR REPLACE FUNCTION person_decrement_asset_count()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
IF OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
|
||||
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "asset_face_delete_decrement_person_asset_count_trigger"
|
||||
AFTER DELETE ON "asset_face"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION person_decrement_asset_count();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "asset_face_update_increment_person_asset_count_trigger"
|
||||
AFTER UPDATE ON "asset_face"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION person_update_asset_count();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "asset_face_insert_increment_person_asset_count_trigger"
|
||||
AFTER INSERT ON "asset_face"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION person_increment_asset_count();`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_person_increment_asset_count', '{"type":"function","name":"person_increment_asset_count","sql":"CREATE OR REPLACE FUNCTION person_increment_asset_count()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n IF NEW.\\"deletedAt\\" IS NULL AND NEW.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" + 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_person_update_asset_count', '{"type":"function","name":"person_update_asset_count","sql":"CREATE OR REPLACE FUNCTION person_update_asset_count()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n IF OLD.\\"personId\\" != NEW.\\"personId\\" AND OLD.\\"deletedAt\\" IS NULL AND OLD.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" - 1 WHERE id = OLD.\\"personId\\";\\n END IF;\\n\\n IF OLD.\\"personId\\" != NEW.\\"personId\\" AND NEW.\\"deletedAt\\" IS NULL AND OLD.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" + 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n\\n IF\\n OLD.\\"personId\\" = NEW.\\"personId\\" AND\\n (OLD.\\"deletedAt\\" IS NULL AND NEW.\\"deletedAt\\" IS NOT NULL) OR\\n (OLD.\\"isVisible\\" IS true AND NEW.\\"isVisible\\" IS false)\\n THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" - 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n\\n IF\\n OLD.\\"personId\\" = NEW.\\"personId\\" AND\\n (OLD.\\"deletedAt\\" IS NOT NULL AND NEW.\\"deletedAt\\" IS NULL) OR\\n (OLD.\\"isVisible\\" IS false AND NEW.\\"isVisible\\" IS true)\\n THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" + 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_person_decrement_asset_count', '{"type":"function","name":"person_decrement_asset_count","sql":"CREATE OR REPLACE FUNCTION person_decrement_asset_count()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n IF OLD.\\"deletedAt\\" IS NULL AND OLD.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" - 1 WHERE id = OLD.\\"personId\\";\\n END IF;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_delete_decrement_person_asset_count_trigger', '{"type":"trigger","name":"asset_face_delete_decrement_person_asset_count_trigger","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_delete_decrement_person_asset_count_trigger\\"\\n AFTER DELETE ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION person_decrement_asset_count();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_update_increment_person_asset_count_trigger', '{"type":"trigger","name":"asset_face_update_increment_person_asset_count_trigger","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_update_increment_person_asset_count_trigger\\"\\n AFTER UPDATE ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION person_update_asset_count();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_insert_increment_person_asset_count_trigger', '{"type":"trigger","name":"asset_face_insert_increment_person_asset_count_trigger","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_insert_increment_person_asset_count_trigger\\"\\n AFTER INSERT ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION person_increment_asset_count();"}'::jsonb);`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TRIGGER "asset_face_insert_increment_person_asset_count_trigger" ON "asset_face";`.execute(db);
|
||||
await sql`DROP FUNCTION person_increment_asset_count;`.execute(db);
|
||||
await sql`DROP TRIGGER "asset_face_update_increment_person_asset_count_trigger" ON "asset_face";`.execute(db);
|
||||
await sql`DROP FUNCTION person_update_asset_count;`.execute(db);
|
||||
await sql`DROP TRIGGER "asset_face_delete_decrement_person_asset_count_trigger" ON "asset_face";`.execute(db);
|
||||
await sql`DROP FUNCTION person_decrement_asset_count;`.execute(db);
|
||||
await sql`DROP INDEX "idx_person_assetCount";`.execute(db);
|
||||
await sql`ALTER TABLE "person" DROP COLUMN "assetCount";`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_person_increment_asset_count';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_person_update_asset_count';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_person_decrement_asset_count';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_delete_decrement_person_asset_count_trigger';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_update_increment_person_asset_count_trigger';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_insert_increment_person_asset_count_trigger';`.execute(db);
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import {
|
||||
AfterDeleteTrigger,
|
||||
AfterInsertTrigger,
|
||||
AfterUpdateTrigger,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
ForeignKeyColumn,
|
||||
|
|
@ -13,7 +15,12 @@ import {
|
|||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { SourceType } from 'src/enum';
|
||||
import { asset_face_source_type } from 'src/schema/enums';
|
||||
import { asset_face_audit } from 'src/schema/functions';
|
||||
import {
|
||||
asset_face_audit,
|
||||
person_decrement_asset_count,
|
||||
person_increment_asset_count,
|
||||
person_update_asset_count,
|
||||
} from 'src/schema/functions';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { PersonTable } from 'src/schema/tables/person.table';
|
||||
|
||||
|
|
@ -25,6 +32,21 @@ import { PersonTable } from 'src/schema/tables/person.table';
|
|||
referencingOldTableAs: 'old',
|
||||
when: 'pg_trigger_depth() = 0',
|
||||
})
|
||||
@AfterInsertTrigger({
|
||||
name: 'asset_face_insert_increment_person_asset_count_trigger',
|
||||
scope: 'row',
|
||||
function: person_increment_asset_count,
|
||||
})
|
||||
@AfterUpdateTrigger({
|
||||
name: 'asset_face_update_increment_person_asset_count_trigger',
|
||||
scope: 'row',
|
||||
function: person_update_asset_count,
|
||||
})
|
||||
@AfterDeleteTrigger({
|
||||
name: 'asset_face_delete_decrement_person_asset_count_trigger',
|
||||
scope: 'row',
|
||||
function: person_decrement_asset_count,
|
||||
})
|
||||
// schemaFromDatabase does not preserve column order
|
||||
@Index({ name: 'asset_face_assetId_personId_idx', columns: ['assetId', 'personId'] })
|
||||
@Index({
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ import { UserTable } from 'src/schema/tables/user.table';
|
|||
using: 'gin',
|
||||
expression: 'f_unaccent("name") gin_trgm_ops',
|
||||
})
|
||||
@Index({
|
||||
name: 'idx_person_assetCount',
|
||||
columns: ['assetCount'],
|
||||
})
|
||||
@UpdatedAtTrigger('person_updatedAt')
|
||||
@AfterDeleteTrigger({
|
||||
scope: 'statement',
|
||||
|
|
@ -66,4 +70,7 @@ export class PersonTable {
|
|||
|
||||
@UpdateIdColumn({ index: true })
|
||||
updateId!: Generated<string>;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
assetCount!: Generated<number>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export class PersonFactory {
|
|||
|
||||
static from(dto: PersonLike = {}) {
|
||||
return new PersonFactory({
|
||||
assetCount: 0,
|
||||
birthDate: null,
|
||||
color: null,
|
||||
createdAt: newDate(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue