Skip to content

Commit f556e64

Browse files
authored
fix: database execution error message (#1236)
* fix: database execution error message * fix: db excute error for add records * fix: record history e2e testing
1 parent 3a58f8f commit f556e64

File tree

8 files changed

+51
-54
lines changed

8 files changed

+51
-54
lines changed

apps/nestjs-backend/src/event-emitter/events/event.enum.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,6 @@ export enum Events {
6363
WORKFLOW_DEACTIVATE = 'workflow.deactivate',
6464

6565
CROP_IMAGE = 'crop.image',
66+
67+
RECORD_HISTORY_CREATE = 'record.history.create',
6668
}

apps/nestjs-backend/src/event-emitter/listeners/record-history.listener.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Knex } from 'knex';
99
import { isString } from 'lodash';
1010
import { InjectModel } from 'nest-knexjs';
1111
import { BaseConfig, IBaseConfig } from '../../configs/base.config';
12+
import { EventEmitterService } from '../event-emitter.service';
1213
import { Events, RecordUpdateEvent } from '../events';
1314

1415
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -18,6 +19,7 @@ const SELECT_FIELD_TYPE_SET = new Set([FieldType.SingleSelect, FieldType.Multipl
1819
export class RecordHistoryListener {
1920
constructor(
2021
private readonly prismaService: PrismaService,
22+
private readonly eventEmitterService: EventEmitterService,
2123
@BaseConfig() private readonly baseConfig: IBaseConfig,
2224
@InjectModel('CUSTOM_KNEX') private readonly knex: Knex
2325
) {}
@@ -130,6 +132,10 @@ export class RecordHistoryListener {
130132

131133
await this.prismaService.$executeRawUnsafe(query);
132134
}
135+
136+
this.eventEmitterService.emit(Events.RECORD_HISTORY_CREATE, {
137+
recordIds: records.map((record) => record.id),
138+
});
133139
}
134140

135141
private minimizeFieldOptions(

apps/nestjs-backend/src/features/calculation/batch.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Injectable, Logger } from '@nestjs/common';
33
import type { IOtOperation } from '@teable/core';
44
import { IdPrefix, RecordOpBuilder } from '@teable/core';
5-
import { PrismaService } from '@teable/db-main-prisma';
5+
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
66
import { Knex } from 'knex';
77
import { groupBy, isEmpty, keyBy } from 'lodash';
88
import { customAlphabet } from 'nanoid';
@@ -246,7 +246,7 @@ export class BatchService {
246246
await prisma.$executeRawUnsafe(insertTempTableSql);
247247

248248
// 3.update data
249-
await prisma.$executeRawUnsafe(updateRecordSql);
249+
await wrapWithValidationErrorHandler(() => prisma.$executeRawUnsafe(updateRecordSql));
250250

251251
// 4.delete temporary table
252252
const dropTempTableSql = this.knex.schema.dropTable(tempTableName).toQuery();

apps/nestjs-backend/src/features/field/field-calculate/field-converting.service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
PRIMARY_SUPPORTED_TYPES,
2323
RecordOpBuilder,
2424
} from '@teable/core';
25-
import { PrismaService } from '@teable/db-main-prisma';
25+
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
2626
import { Knex } from 'knex';
2727
import { difference, intersection, isEmpty, isEqual, keyBy, set } from 'lodash';
2828
import { InjectModel } from 'nest-knexjs';
@@ -1134,7 +1134,9 @@ export class FieldConvertingService {
11341134
if (notNull) table.dropNullable(dbFieldName);
11351135
})
11361136
.toQuery();
1137-
await this.prismaService.txClient().$executeRawUnsafe(fieldValidationQuery);
1137+
await wrapWithValidationErrorHandler(() =>
1138+
this.prismaService.txClient().$executeRawUnsafe(fieldValidationQuery)
1139+
);
11381140
}
11391141

11401142
async closeConstraint(tableId: string, newField: IFieldInstance, oldField: IFieldInstance) {

apps/nestjs-backend/src/features/field/field.service.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
checkFieldValidationEnabled,
2020
} from '@teable/core';
2121
import type { Field as RawField, Prisma } from '@teable/db-main-prisma';
22-
import { PrismaService } from '@teable/db-main-prisma';
22+
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
2323
import { instanceToPlain } from 'class-transformer';
2424
import { Knex } from 'knex';
2525
import { keyBy, sortBy } from 'lodash';
@@ -534,16 +534,19 @@ export class FieldService implements IReadonlyAdapterService {
534534
}
535535

536536
if (key === 'dbFieldType') {
537-
await this.alterTableModifyFieldType(fieldId, newValue as DbFieldType);
537+
await wrapWithValidationErrorHandler(() =>
538+
this.alterTableModifyFieldType(fieldId, newValue as DbFieldType)
539+
);
538540
}
539541

540542
if (key === 'dbFieldName') {
541543
await this.alterTableModifyFieldName(fieldId, newValue as string);
542544
}
543545

544546
if (key === 'unique' || key === 'notNull') {
545-
console.log('alterTableModifyFieldValidation', fieldId, { [key]: newValue });
546-
await this.alterTableModifyFieldValidation(fieldId, key, newValue as boolean | undefined);
547+
await wrapWithValidationErrorHandler(() =>
548+
this.alterTableModifyFieldValidation(fieldId, key, newValue as boolean | undefined)
549+
);
547550
}
548551

549552
return { [key]: newValue ?? null };

apps/nestjs-backend/src/features/record/record.service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
Relationship,
3737
} from '@teable/core';
3838
import type { Prisma } from '@teable/db-main-prisma';
39-
import { PrismaService } from '@teable/db-main-prisma';
39+
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
4040
import type {
4141
ICreateRecordsRo,
4242
IGetRecordQuery,
@@ -1048,7 +1048,9 @@ export class RecordService {
10481048

10491049
const sql = this.dbProvider.batchInsertSql(dbTableName, snapshots);
10501050

1051-
await this.prismaService.txClient().$executeRawUnsafe(sql);
1051+
await wrapWithValidationErrorHandler(() =>
1052+
this.prismaService.txClient().$executeRawUnsafe(sql)
1053+
);
10521054

10531055
return snapshots;
10541056
}

apps/nestjs-backend/test/record-history.e2e-spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ describe('Record history (e2e)', () => {
3131
const baseConfigService = app.get(baseConfig.KEY) as IBaseConfig;
3232
baseConfigService.recordHistoryDisabled = false;
3333

34-
awaitWithEvent = createAwaitWithEvent(eventEmitterService, Events.TABLE_RECORD_UPDATE);
34+
awaitWithEvent = createAwaitWithEvent(eventEmitterService, Events.RECORD_HISTORY_CREATE);
3535
});
3636

3737
afterAll(async () => {
38-
eventEmitterService.eventEmitter.removeAllListeners(Events.TABLE_RECORD_UPDATE);
38+
eventEmitterService.eventEmitter.removeAllListeners(Events.RECORD_HISTORY_CREATE);
3939
await app.close();
4040
});
4141

packages/db-main-prisma/src/prisma.service.ts

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,30 @@ interface ITx {
1313
rawOpMaps?: unknown;
1414
}
1515

16-
function proxyClient(tx: Prisma.TransactionClient) {
17-
return new Proxy(tx, {
18-
get(target, p) {
19-
if (p === '$queryRawUnsafe' || p === '$executeRawUnsafe') {
20-
return async function (query: string, ...args: unknown[]) {
21-
try {
22-
return await target[p](query, ...args);
23-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
24-
} catch (e: any) {
25-
const code = e.meta?.code ?? e.code;
26-
if (
27-
code === PostgresErrorCode.UNIQUE_VIOLATION ||
28-
code === SqliteErrorCode.UNIQUE_VIOLATION
29-
) {
30-
throw new HttpException(
31-
'Duplicate detected! Please ensure that all fields with unique value validation are indeed unique.',
32-
HttpStatus.BAD_REQUEST
33-
);
34-
}
35-
if (
36-
code === PostgresErrorCode.NOT_NULL_VIOLATION ||
37-
code === SqliteErrorCode.NOT_NULL_VIOLATION
38-
) {
39-
throw new HttpException(
40-
'One or more required fields were not provided! Please ensure all mandatory fields are filled.',
41-
HttpStatus.BAD_REQUEST
42-
);
43-
}
44-
throw new HttpException(
45-
`An error occurred in ${p}: ${e.message}`,
46-
HttpStatus.INTERNAL_SERVER_ERROR
47-
);
48-
}
49-
};
50-
}
51-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
52-
// @ts-ignore
53-
return target[p];
54-
},
55-
});
56-
}
16+
export const wrapWithValidationErrorHandler = async (fn: () => Promise<unknown>) => {
17+
try {
18+
await fn();
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20+
} catch (e: any) {
21+
const code = e.meta?.code ?? e.code;
22+
if (code === PostgresErrorCode.UNIQUE_VIOLATION || code === SqliteErrorCode.UNIQUE_VIOLATION) {
23+
throw new HttpException(
24+
'Duplicate detected! Please ensure that all fields with unique value validation are indeed unique.',
25+
HttpStatus.BAD_REQUEST
26+
);
27+
}
28+
if (
29+
code === PostgresErrorCode.NOT_NULL_VIOLATION ||
30+
code === SqliteErrorCode.NOT_NULL_VIOLATION
31+
) {
32+
throw new HttpException(
33+
'One or more required fields were not provided! Please ensure all mandatory fields are filled.',
34+
HttpStatus.BAD_REQUEST
35+
);
36+
}
37+
throw new HttpException(`An error occurred: ${e.message}`, HttpStatus.INTERNAL_SERVER_ERROR);
38+
}
39+
};
5740

5841
@Injectable()
5942
export class PrismaService
@@ -118,7 +101,6 @@ export class PrismaService
118101

119102
await this.cls.runWith(this.cls.get(), async () => {
120103
result = await super.$transaction<R>(async (prisma) => {
121-
prisma = proxyClient(prisma);
122104
this.cls.set('tx.client', prisma);
123105
this.cls.set('tx.id', nanoid());
124106
this.cls.set('tx.timeStr', new Date().toISOString());

0 commit comments

Comments
 (0)