Skip to content

Commit

Permalink
fix: database execution error message (#1236)
Browse files Browse the repository at this point in the history
* fix: database execution error message

* fix: db excute error for add records

* fix: record history e2e testing
  • Loading branch information
Sky-FE authored Jan 10, 2025
1 parent 3a58f8f commit f556e64
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 54 deletions.
2 changes: 2 additions & 0 deletions apps/nestjs-backend/src/event-emitter/events/event.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,6 @@ export enum Events {
WORKFLOW_DEACTIVATE = 'workflow.deactivate',

CROP_IMAGE = 'crop.image',

RECORD_HISTORY_CREATE = 'record.history.create',
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Knex } from 'knex';
import { isString } from 'lodash';
import { InjectModel } from 'nest-knexjs';
import { BaseConfig, IBaseConfig } from '../../configs/base.config';
import { EventEmitterService } from '../event-emitter.service';
import { Events, RecordUpdateEvent } from '../events';

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

await this.prismaService.$executeRawUnsafe(query);
}

this.eventEmitterService.emit(Events.RECORD_HISTORY_CREATE, {
recordIds: records.map((record) => record.id),
});
}

private minimizeFieldOptions(
Expand Down
4 changes: 2 additions & 2 deletions apps/nestjs-backend/src/features/calculation/batch.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Injectable, Logger } from '@nestjs/common';
import type { IOtOperation } from '@teable/core';
import { IdPrefix, RecordOpBuilder } from '@teable/core';
import { PrismaService } from '@teable/db-main-prisma';
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
import { Knex } from 'knex';
import { groupBy, isEmpty, keyBy } from 'lodash';
import { customAlphabet } from 'nanoid';
Expand Down Expand Up @@ -246,7 +246,7 @@ export class BatchService {
await prisma.$executeRawUnsafe(insertTempTableSql);

// 3.update data
await prisma.$executeRawUnsafe(updateRecordSql);
await wrapWithValidationErrorHandler(() => prisma.$executeRawUnsafe(updateRecordSql));

// 4.delete temporary table
const dropTempTableSql = this.knex.schema.dropTable(tempTableName).toQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
PRIMARY_SUPPORTED_TYPES,
RecordOpBuilder,
} from '@teable/core';
import { PrismaService } from '@teable/db-main-prisma';
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
import { Knex } from 'knex';
import { difference, intersection, isEmpty, isEqual, keyBy, set } from 'lodash';
import { InjectModel } from 'nest-knexjs';
Expand Down Expand Up @@ -1134,7 +1134,9 @@ export class FieldConvertingService {
if (notNull) table.dropNullable(dbFieldName);
})
.toQuery();
await this.prismaService.txClient().$executeRawUnsafe(fieldValidationQuery);
await wrapWithValidationErrorHandler(() =>
this.prismaService.txClient().$executeRawUnsafe(fieldValidationQuery)
);
}

async closeConstraint(tableId: string, newField: IFieldInstance, oldField: IFieldInstance) {
Expand Down
11 changes: 7 additions & 4 deletions apps/nestjs-backend/src/features/field/field.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
checkFieldValidationEnabled,
} from '@teable/core';
import type { Field as RawField, Prisma } from '@teable/db-main-prisma';
import { PrismaService } from '@teable/db-main-prisma';
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
import { instanceToPlain } from 'class-transformer';
import { Knex } from 'knex';
import { keyBy, sortBy } from 'lodash';
Expand Down Expand Up @@ -534,16 +534,19 @@ export class FieldService implements IReadonlyAdapterService {
}

if (key === 'dbFieldType') {
await this.alterTableModifyFieldType(fieldId, newValue as DbFieldType);
await wrapWithValidationErrorHandler(() =>
this.alterTableModifyFieldType(fieldId, newValue as DbFieldType)
);
}

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

if (key === 'unique' || key === 'notNull') {
console.log('alterTableModifyFieldValidation', fieldId, { [key]: newValue });
await this.alterTableModifyFieldValidation(fieldId, key, newValue as boolean | undefined);
await wrapWithValidationErrorHandler(() =>
this.alterTableModifyFieldValidation(fieldId, key, newValue as boolean | undefined)
);
}

return { [key]: newValue ?? null };
Expand Down
6 changes: 4 additions & 2 deletions apps/nestjs-backend/src/features/record/record.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
Relationship,
} from '@teable/core';
import type { Prisma } from '@teable/db-main-prisma';
import { PrismaService } from '@teable/db-main-prisma';
import { PrismaService, wrapWithValidationErrorHandler } from '@teable/db-main-prisma';
import type {
ICreateRecordsRo,
IGetRecordQuery,
Expand Down Expand Up @@ -1048,7 +1048,9 @@ export class RecordService {

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

await this.prismaService.txClient().$executeRawUnsafe(sql);
await wrapWithValidationErrorHandler(() =>
this.prismaService.txClient().$executeRawUnsafe(sql)
);

return snapshots;
}
Expand Down
4 changes: 2 additions & 2 deletions apps/nestjs-backend/test/record-history.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ describe('Record history (e2e)', () => {
const baseConfigService = app.get(baseConfig.KEY) as IBaseConfig;
baseConfigService.recordHistoryDisabled = false;

awaitWithEvent = createAwaitWithEvent(eventEmitterService, Events.TABLE_RECORD_UPDATE);
awaitWithEvent = createAwaitWithEvent(eventEmitterService, Events.RECORD_HISTORY_CREATE);
});

afterAll(async () => {
eventEmitterService.eventEmitter.removeAllListeners(Events.TABLE_RECORD_UPDATE);
eventEmitterService.eventEmitter.removeAllListeners(Events.RECORD_HISTORY_CREATE);
await app.close();
});

Expand Down
66 changes: 24 additions & 42 deletions packages/db-main-prisma/src/prisma.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,30 @@ interface ITx {
rawOpMaps?: unknown;
}

function proxyClient(tx: Prisma.TransactionClient) {
return new Proxy(tx, {
get(target, p) {
if (p === '$queryRawUnsafe' || p === '$executeRawUnsafe') {
return async function (query: string, ...args: unknown[]) {
try {
return await target[p](query, ...args);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
const code = e.meta?.code ?? e.code;
if (
code === PostgresErrorCode.UNIQUE_VIOLATION ||
code === SqliteErrorCode.UNIQUE_VIOLATION
) {
throw new HttpException(
'Duplicate detected! Please ensure that all fields with unique value validation are indeed unique.',
HttpStatus.BAD_REQUEST
);
}
if (
code === PostgresErrorCode.NOT_NULL_VIOLATION ||
code === SqliteErrorCode.NOT_NULL_VIOLATION
) {
throw new HttpException(
'One or more required fields were not provided! Please ensure all mandatory fields are filled.',
HttpStatus.BAD_REQUEST
);
}
throw new HttpException(
`An error occurred in ${p}: ${e.message}`,
HttpStatus.INTERNAL_SERVER_ERROR
);
}
};
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return target[p];
},
});
}
export const wrapWithValidationErrorHandler = async (fn: () => Promise<unknown>) => {
try {
await fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
const code = e.meta?.code ?? e.code;
if (code === PostgresErrorCode.UNIQUE_VIOLATION || code === SqliteErrorCode.UNIQUE_VIOLATION) {
throw new HttpException(
'Duplicate detected! Please ensure that all fields with unique value validation are indeed unique.',
HttpStatus.BAD_REQUEST
);
}
if (
code === PostgresErrorCode.NOT_NULL_VIOLATION ||
code === SqliteErrorCode.NOT_NULL_VIOLATION
) {
throw new HttpException(
'One or more required fields were not provided! Please ensure all mandatory fields are filled.',
HttpStatus.BAD_REQUEST
);
}
throw new HttpException(`An error occurred: ${e.message}`, HttpStatus.INTERNAL_SERVER_ERROR);
}
};

@Injectable()
export class PrismaService
Expand Down Expand Up @@ -118,7 +101,6 @@ export class PrismaService

await this.cls.runWith(this.cls.get(), async () => {
result = await super.$transaction<R>(async (prisma) => {
prisma = proxyClient(prisma);
this.cls.set('tx.client', prisma);
this.cls.set('tx.id', nanoid());
this.cls.set('tx.timeStr', new Date().toISOString());
Expand Down

0 comments on commit f556e64

Please sign in to comment.