mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
simplify query, handle nested empty tag
This commit is contained in:
parent
54b61d991a
commit
3b5fbe7a89
2 changed files with 37 additions and 25 deletions
|
|
@ -165,18 +165,14 @@ export class TagRepository {
|
||||||
async deleteEmptyTags() {
|
async deleteEmptyTags() {
|
||||||
const result = await this.db
|
const result = await this.db
|
||||||
.deleteFrom('tag')
|
.deleteFrom('tag')
|
||||||
.where('id', 'not in', (eb) => eb.selectFrom('tag_asset').select('tagsId'))
|
.where(({ not, exists, selectFrom }) =>
|
||||||
.where('id', 'not in', (eb) =>
|
not(
|
||||||
eb
|
exists(
|
||||||
.selectFrom('tag as child')
|
selectFrom('tag_closure')
|
||||||
.select('child.parentId')
|
.whereRef('tag.id', '=', 'tag_closure.id_ancestor')
|
||||||
.where('child.parentId', 'is not', null)
|
.innerJoin('tag_asset', 'tag_closure.id_descendant', 'tag_asset.tagsId'),
|
||||||
.where((eb2) =>
|
|
||||||
eb2.or([
|
|
||||||
eb2('child.id', 'in', (eb3) => eb3.selectFrom('tag_asset').select('tagsId')),
|
|
||||||
eb2('child.id', 'not in', (eb3) => eb3.selectFrom('tag_asset').select('tagsId')),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { TagRepository } from 'src/repositories/tag.repository';
|
import { TagRepository } from 'src/repositories/tag.repository';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { TagService } from 'src/services/tag.service';
|
import { TagService } from 'src/services/tag.service';
|
||||||
|
import { upsertTags } from 'src/utils/tag';
|
||||||
import { newMediumService } from 'test/medium.factory';
|
import { newMediumService } from 'test/medium.factory';
|
||||||
import { getKyselyDB } from 'test/utils';
|
import { getKyselyDB } from 'test/utils';
|
||||||
|
|
||||||
|
|
@ -27,8 +28,8 @@ describe(TagService.name, () => {
|
||||||
it('single tag exists, not connected to any assets, and is deleted', async () => {
|
it('single tag exists, not connected to any assets, and is deleted', async () => {
|
||||||
const { sut, ctx } = setup();
|
const { sut, ctx } = setup();
|
||||||
const { user } = await ctx.newUser();
|
const { user } = await ctx.newUser();
|
||||||
const { tag } = await ctx.newTag({ userId: user.id, value: 'tag-1' });
|
|
||||||
const tagRepo = ctx.get(TagRepository);
|
const tagRepo = ctx.get(TagRepository);
|
||||||
|
const [tag] = await upsertTags(tagRepo, { userId: user.id, tags: ['tag-1'] });
|
||||||
|
|
||||||
await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id }));
|
await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id }));
|
||||||
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success);
|
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success);
|
||||||
|
|
@ -39,8 +40,8 @@ describe(TagService.name, () => {
|
||||||
const { sut, ctx } = setup();
|
const { sut, ctx } = setup();
|
||||||
const { user } = await ctx.newUser();
|
const { user } = await ctx.newUser();
|
||||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
const { tag } = await ctx.newTag({ userId: user.id, value: 'tag-1' });
|
|
||||||
const tagRepo = ctx.get(TagRepository);
|
const tagRepo = ctx.get(TagRepository);
|
||||||
|
const [tag] = await upsertTags(tagRepo, { userId: user.id, tags: ['tag-1'] });
|
||||||
|
|
||||||
await ctx.newTagAsset({ tagIds: [tag.id], assetIds: [asset.id] });
|
await ctx.newTagAsset({ tagIds: [tag.id], assetIds: [asset.id] });
|
||||||
|
|
||||||
|
|
@ -53,48 +54,63 @@ describe(TagService.name, () => {
|
||||||
const { sut, ctx } = setup();
|
const { sut, ctx } = setup();
|
||||||
const { user } = await ctx.newUser();
|
const { user } = await ctx.newUser();
|
||||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
const { tag: parentTag } = await ctx.newTag({ userId: user.id, value: 'parent' });
|
|
||||||
const { tag: childrenTag } = await ctx.newTag({ userId: user.id, value: 'child', parentId: parentTag.id });
|
|
||||||
const tagRepo = ctx.get(TagRepository);
|
const tagRepo = ctx.get(TagRepository);
|
||||||
|
const [parentTag, childTag] = await upsertTags(tagRepo, { userId: user.id, tags: ['parent', 'parent/child'] });
|
||||||
|
|
||||||
await ctx.newTagAsset({ tagIds: [parentTag.id], assetIds: [asset.id] });
|
await ctx.newTagAsset({ tagIds: [parentTag.id], assetIds: [asset.id] });
|
||||||
|
|
||||||
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
||||||
expect.objectContaining({ id: parentTag.id }),
|
expect.objectContaining({ id: parentTag.id }),
|
||||||
);
|
);
|
||||||
await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual(
|
await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual(
|
||||||
expect.objectContaining({ id: childrenTag.id }),
|
expect.objectContaining({ id: childTag.id }),
|
||||||
);
|
);
|
||||||
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success);
|
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success);
|
||||||
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
||||||
expect.objectContaining({ id: parentTag.id }),
|
expect.objectContaining({ id: parentTag.id }),
|
||||||
);
|
);
|
||||||
await expect(tagRepo.getByValue(user.id, 'child')).resolves.toBeUndefined();
|
await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hierarchical tag exists, and only the child is connected to an asset, and nothing is deleted', async () => {
|
it('hierarchical tag exists, and only the child is connected to an asset, and nothing is deleted', async () => {
|
||||||
const { sut, ctx } = setup();
|
const { sut, ctx } = setup();
|
||||||
const { user } = await ctx.newUser();
|
const { user } = await ctx.newUser();
|
||||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
const { tag: parentTag } = await ctx.newTag({ userId: user.id, value: 'parent' });
|
|
||||||
const { tag: childrenTag } = await ctx.newTag({ userId: user.id, value: 'child', parentId: parentTag.id });
|
|
||||||
const tagRepo = ctx.get(TagRepository);
|
const tagRepo = ctx.get(TagRepository);
|
||||||
|
const [parentTag, childTag] = await upsertTags(tagRepo, { userId: user.id, tags: ['parent', 'parent/child'] });
|
||||||
|
|
||||||
await ctx.newTagAsset({ tagIds: [childrenTag.id], assetIds: [asset.id] });
|
await ctx.newTagAsset({ tagIds: [childTag.id], assetIds: [asset.id] });
|
||||||
|
|
||||||
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
||||||
expect.objectContaining({ id: parentTag.id }),
|
expect.objectContaining({ id: parentTag.id }),
|
||||||
);
|
);
|
||||||
await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual(
|
await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual(
|
||||||
expect.objectContaining({ id: childrenTag.id }),
|
expect.objectContaining({ id: childTag.id }),
|
||||||
);
|
);
|
||||||
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success);
|
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success);
|
||||||
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
||||||
expect.objectContaining({ id: parentTag.id }),
|
expect.objectContaining({ id: parentTag.id }),
|
||||||
);
|
);
|
||||||
await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual(
|
await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual(
|
||||||
expect.objectContaining({ id: childrenTag.id }),
|
expect.objectContaining({ id: childTag.id }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('hierarchical tag exists, and neither parent nor child is connected to an asset, and both are deleted', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const tagRepo = ctx.get(TagRepository);
|
||||||
|
const [parentTag, childTag] = await upsertTags(tagRepo, { userId: user.id, tags: ['parent', 'parent/child'] });
|
||||||
|
|
||||||
|
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(
|
||||||
|
expect.objectContaining({ id: parentTag.id }),
|
||||||
|
);
|
||||||
|
await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual(
|
||||||
|
expect.objectContaining({ id: childTag.id }),
|
||||||
|
);
|
||||||
|
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success);
|
||||||
|
await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toBeUndefined();
|
||||||
|
await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue