Skip to content

Commit 6f76395

Browse files
committed
fix: missing cross-base link fields when update dbTableName
1 parent 12ec0e8 commit 6f76395

File tree

5 files changed

+136
-49
lines changed

5 files changed

+136
-49
lines changed

apps/nestjs-backend/src/db-provider/db.provider.interface.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DriverClient, IFilter, ISortItem } from '@teable/core';
1+
import type { DriverClient, IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
22
import type { Prisma } from '@teable/db-main-prisma';
33
import type { IAggregationField, ISearchIndexByQueryRo } from '@teable/openapi';
44
import type { Knex } from 'knex';
@@ -162,4 +162,8 @@ export interface IDbProvider {
162162
qb: Knex.QueryBuilder,
163163
props: ICalendarDailyCollectionQueryProps
164164
): Knex.QueryBuilder;
165+
166+
lookupOptionsQuery(optionsKey: keyof ILookupOptionsVo, value: string): string;
167+
168+
optionsQuery(optionsKey: string, value: string): string;
165169
}

apps/nestjs-backend/src/db-provider/postgres.provider.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable sonarjs/no-duplicate-string */
22
import { Logger } from '@nestjs/common';
3-
import type { IFilter, ISortItem } from '@teable/core';
3+
import type { IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
44
import { DriverClient } from '@teable/core';
55
import type { PrismaClient } from '@teable/db-main-prisma';
66
import type { IAggregationField, ISearchIndexByQueryRo } from '@teable/openapi';
@@ -418,4 +418,26 @@ export class PostgresProvider implements IDbProvider {
418418
.groupBy('dates.date')
419419
.orderBy('dates.date', 'asc');
420420
}
421+
422+
// select id and lookup_options for "field" table options is a json saved in string format, match optionsKey and value
423+
// please use json method in postgres
424+
lookupOptionsQuery(optionsKey: keyof ILookupOptionsVo, value: string): string {
425+
return this.knex('field')
426+
.select({
427+
id: 'id',
428+
lookupOptions: 'lookup_options',
429+
})
430+
.whereRaw(`lookup_options::json->>'${optionsKey}' = ?`, [value])
431+
.toQuery();
432+
}
433+
434+
optionsQuery(optionsKey: string, value: string): string {
435+
return this.knex('field')
436+
.select({
437+
id: 'id',
438+
options: 'options',
439+
})
440+
.whereRaw(`options::json->>'${optionsKey}' = ?`, [value])
441+
.toQuery();
442+
}
421443
}

apps/nestjs-backend/src/db-provider/sqlite.provider.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable sonarjs/no-duplicate-string */
22
import { Logger } from '@nestjs/common';
3-
import type { IFilter, ISortItem } from '@teable/core';
3+
import type { IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
44
import { DriverClient } from '@teable/core';
55
import type { PrismaClient } from '@teable/db-main-prisma';
66
import type { IAggregationField, ISearchIndexByQueryRo } from '@teable/openapi';
@@ -375,4 +375,26 @@ export class SqliteProvider implements IDbProvider {
375375
.groupBy('d.date')
376376
.orderBy('d.date', 'asc');
377377
}
378+
379+
// select id and lookup_options for "field" table options is a json saved in string format, match optionsKey and value
380+
// please use json method in sqlite
381+
lookupOptionsQuery(optionsKey: keyof ILookupOptionsVo, value: string): string {
382+
return this.knex('field')
383+
.select({
384+
id: 'id',
385+
lookupOptions: 'lookup_options',
386+
})
387+
.whereRaw(`json_extract(lookup_options, '$.?') = ?`, [optionsKey, value])
388+
.toQuery();
389+
}
390+
391+
optionsQuery(optionsKey: string, value: string): string {
392+
return this.knex('field')
393+
.select({
394+
id: 'id',
395+
options: 'options',
396+
})
397+
.whereRaw(`json_extract(options, '$.?') = ?`, [optionsKey, value])
398+
.toQuery();
399+
}
378400
}

apps/nestjs-backend/src/features/table/open-api/table-open-api.service.ts

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -469,55 +469,39 @@ export class TableOpenApiService {
469469
throw new NotFoundException(`table ${tableId} not found`);
470470
});
471471

472-
const linkFieldsRaw = await this.prismaService.field.findMany({
473-
where: { table: { baseId }, type: FieldType.Link },
474-
select: { id: true, options: true },
475-
});
476-
477-
const relationalFieldsRaw = await this.prismaService.field.findMany({
478-
where: { table: { baseId }, lookupOptions: { not: null } },
479-
select: { id: true, lookupOptions: true },
480-
});
472+
const linkFieldsQuery = this.dbProvider.optionsQuery('fkHostTableName', oldDbTableName);
473+
const lookupFieldsQuery = this.dbProvider.lookupOptionsQuery('fkHostTableName', oldDbTableName);
481474

482475
await this.prismaService.$tx(async (prisma) => {
483-
await Promise.all(
484-
linkFieldsRaw
485-
.map((field) => ({
486-
...field,
487-
options: JSON.parse(field.options as string) as ILinkFieldOptions,
488-
}))
489-
.filter((field) => {
490-
return field.options.fkHostTableName === oldDbTableName;
491-
})
492-
.map((field) => {
493-
return prisma.field.update({
494-
where: { id: field.id },
495-
data: { options: JSON.stringify({ ...field.options, fkHostTableName: dbTableName }) },
496-
});
497-
})
498-
);
476+
const linkFieldsRaw =
477+
await this.prismaService.$queryRawUnsafe<{ id: string; options: string }[]>(
478+
linkFieldsQuery
479+
);
480+
const lookupFieldsRaw =
481+
await this.prismaService.$queryRawUnsafe<{ id: string; lookupOptions: string }[]>(
482+
lookupFieldsQuery
483+
);
499484

500-
await Promise.all(
501-
relationalFieldsRaw
502-
.map((field) => ({
503-
...field,
504-
lookupOptions: JSON.parse(field.lookupOptions as string) as ILookupOptionsVo,
505-
}))
506-
.filter((field) => {
507-
return field.lookupOptions.fkHostTableName === oldDbTableName;
508-
})
509-
.map((field) => {
510-
return prisma.field.update({
511-
where: { id: field.id },
512-
data: {
513-
lookupOptions: JSON.stringify({
514-
...field.lookupOptions,
515-
fkHostTableName: dbTableName,
516-
}),
517-
},
518-
});
519-
})
520-
);
485+
for (const field of linkFieldsRaw) {
486+
const options = JSON.parse(field.options as string) as ILinkFieldOptions;
487+
await prisma.field.update({
488+
where: { id: field.id },
489+
data: { options: JSON.stringify({ ...options, fkHostTableName: dbTableName }) },
490+
});
491+
}
492+
493+
for (const field of lookupFieldsRaw) {
494+
const lookupOptions = JSON.parse(field.lookupOptions as string) as ILookupOptionsVo;
495+
await prisma.field.update({
496+
where: { id: field.id },
497+
data: {
498+
lookupOptions: JSON.stringify({
499+
...lookupOptions,
500+
fkHostTableName: dbTableName,
501+
}),
502+
},
503+
});
504+
}
521505

522506
await this.tableService.updateTable(baseId, tableId, { dbTableName });
523507
const renameSql = this.dbProvider.renameTableName(oldDbTableName, dbTableName);

apps/nestjs-backend/test/link-api.e2e-spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2997,6 +2997,61 @@ describe('OpenAPI link (e2e)', () => {
29972997
);
29982998
expect((symUpdatedLinkField.options as ILinkFieldOptions).baseId).toEqual(baseId);
29992999
});
3000+
3001+
it('should correct update db table name when link field is cross base', async () => {
3002+
const linkFieldRo: IFieldRo = {
3003+
name: 'link field',
3004+
type: FieldType.Link,
3005+
options: {
3006+
baseId: baseId2,
3007+
relationship: Relationship.ManyOne,
3008+
foreignTableId: table2.id,
3009+
},
3010+
};
3011+
3012+
const linkField = await createField(table1.id, linkFieldRo);
3013+
3014+
const symLinkField = await getField(
3015+
table2.id,
3016+
(linkField.options as ILinkFieldOptions).symmetricFieldId as string
3017+
);
3018+
3019+
expect((linkField.options as ILinkFieldOptions).fkHostTableName).toEqual(table1.dbTableName);
3020+
expect((symLinkField.options as ILinkFieldOptions).fkHostTableName).toEqual(
3021+
table1.dbTableName
3022+
);
3023+
3024+
const lookupFieldRo: IFieldRo = {
3025+
type: FieldType.SingleLineText,
3026+
isLookup: true,
3027+
lookupOptions: {
3028+
foreignTableId: table1.id,
3029+
lookupFieldId: table1.fields[0].id,
3030+
linkFieldId: symLinkField.id,
3031+
},
3032+
};
3033+
3034+
const lookupField = await createField(table2.id, lookupFieldRo);
3035+
3036+
await updateDbTableName(baseId, table1.id, { dbTableName: 'newAwesomeName' });
3037+
const newTable1 = await getTable(baseId, table1.id);
3038+
const updatedLink1 = await getField(table1.id, linkField.id);
3039+
const updatedLink2 = await getField(table2.id, symLinkField.id);
3040+
const updatedLookupField = await getField(table2.id, lookupField.id);
3041+
3042+
expect(newTable1.dbTableName.split(/[._]/)).toEqual(['bseTestBaseId', 'newAwesomeName']);
3043+
expect((updatedLink1.options as ILinkFieldOptions).fkHostTableName.split(/[._]/)).toEqual([
3044+
'bseTestBaseId',
3045+
'newAwesomeName',
3046+
]);
3047+
expect((updatedLink2.options as ILinkFieldOptions).fkHostTableName.split(/[._]/)).toEqual([
3048+
'bseTestBaseId',
3049+
'newAwesomeName',
3050+
]);
3051+
expect(
3052+
(updatedLookupField.lookupOptions as ILookupOptionsVo).fkHostTableName.split(/[._]/)
3053+
).toEqual(['bseTestBaseId', 'newAwesomeName']);
3054+
});
30003055
});
30013056

30023057
describe('lookup a link field cross 2 table', () => {

0 commit comments

Comments
 (0)