Add TwentyORM query read timeout exception (#13603)

In this PR:
- adding a try / catch around all ORM internal methods save, insert,
upsert, findOne, ...
- leveraging this error to prevent messageChannels to get FAILED
- optimizing messaging BATCH_SIZE and THROTTLE threshold according to
local tests
- 

<img width="1510" height="851" alt="image"
src="https://github.com/user-attachments/assets/802fd933-caac-4291-9cde-34a1ddf59c06"
/>
This commit is contained in:
Charles Bochet
2025-08-04 17:16:16 +02:00
committed by GitHub
parent d958447bb6
commit 2eeddcddc6
14 changed files with 656 additions and 553 deletions
@@ -14,6 +14,7 @@ import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/works
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception';
import {
TwentyORMException,
TwentyORMExceptionCode,
@@ -62,64 +63,69 @@ export class WorkspaceDeleteQueryBuilder<
}
override async execute(): Promise<DeleteResult & { generatedMaps: T[] }> {
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
try {
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
true,
this.authContext,
this.featureFlagMap,
);
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
true,
this.authContext,
this.featureFlagMap,
);
eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
eventSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases;
eventSelectQueryBuilder.setParameters(this.getParameters());
eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
eventSelectQueryBuilder.expressionMap.aliases =
this.expressionMap.aliases;
eventSelectQueryBuilder.setParameters(this.getParameters());
const before = await eventSelectQueryBuilder.getOne();
const before = await eventSelectQueryBuilder.getOne();
const result = await super.execute();
const result = await super.execute();
const formattedResult = formatResult<T[]>(
result.raw,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedResult = formatResult<T[]>(
result.raw,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedBefore = formatResult<T[]>(
before,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedBefore = formatResult<T[]>(
before,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.DESTROYED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedBefore,
authContext: this.authContext,
});
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.DESTROYED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedBefore,
authContext: this.authContext,
});
return {
raw: result.raw,
generatedMaps: formattedResult,
affected: result.affected,
};
return {
raw: result.raw,
generatedMaps: formattedResult,
affected: result.affected,
};
} catch (error) {
throw computeTwentyORMException(error);
}
}
private getMainAliasTarget(): EntityTarget<T> {
@@ -17,6 +17,7 @@ import { QueryDeepPartialEntityWithNestedRelationFields } from 'src/engine/twent
import { RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type';
import { RelationDisconnectQueryFieldsByEntityIndex } from 'src/engine/twenty-orm/entity-manager/types/relation-nested-query-fields-by-entity-index.type';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception';
import {
TwentyORMException,
TwentyORMExceptionCode,
@@ -100,98 +101,102 @@ export class WorkspaceInsertQueryBuilder<
}
override async execute(): Promise<InsertResult> {
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
try {
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
if (isDefined(this.relationNestedConfig)) {
const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
this.shouldBypassPermissionChecks,
this.authContext,
);
const updatedValues =
await this.relationNestedQueries.processRelationNestedQueries({
entities: this.expressionMap.valuesSet as
| QueryDeepPartialEntityWithNestedRelationFields<T>
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
relationNestedConfig: this.relationNestedConfig,
queryBuilder: nestedRelationQueryBuilder,
});
if (isDefined(this.relationNestedConfig)) {
const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
this.shouldBypassPermissionChecks,
this.authContext,
);
this.expressionMap.valuesSet = updatedValues;
const updatedValues =
await this.relationNestedQueries.processRelationNestedQueries({
entities: this.expressionMap.valuesSet as
| QueryDeepPartialEntityWithNestedRelationFields<T>
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
relationNestedConfig: this.relationNestedConfig,
queryBuilder: nestedRelationQueryBuilder,
});
this.expressionMap.valuesSet = updatedValues;
}
const result = await super.execute();
const eventSelectQueryBuilder = (
this.connection.manager as WorkspaceEntityManager
).createQueryBuilder(
mainAliasTarget,
this.expressionMap.mainAlias?.metadata.name ?? '',
undefined,
{
shouldBypassPermissionChecks: true,
},
) as WorkspaceSelectQueryBuilder<T>;
eventSelectQueryBuilder.whereInIds(
result.identifiers.map((identifier) => identifier.id),
);
const afterResult = await eventSelectQueryBuilder.getMany();
const formattedResultForEvent = formatResult<T[]>(
afterResult,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.CREATED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedResultForEvent,
authContext: this.authContext,
});
// TypeORM returns all entity columns for insertions
const resultWithoutInsertionExtraColumns = result.raw.map(
(rawResult: Record<string, string>) =>
Object.keys(rawResult)
.filter((key) => this.expressionMap.returning.includes(key))
.reduce((filtered: Record<string, string>, key) => {
filtered[key] = rawResult[key];
return filtered;
}, {}),
);
const formattedResult = formatResult<T[]>(
resultWithoutInsertionExtraColumns,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return {
raw: resultWithoutInsertionExtraColumns,
generatedMaps: formattedResult,
identifiers: result.identifiers,
};
} catch (error) {
throw computeTwentyORMException(error);
}
const result = await super.execute();
const eventSelectQueryBuilder = (
this.connection.manager as WorkspaceEntityManager
).createQueryBuilder(
mainAliasTarget,
this.expressionMap.mainAlias?.metadata.name ?? '',
undefined,
{
shouldBypassPermissionChecks: true,
},
) as WorkspaceSelectQueryBuilder<T>;
eventSelectQueryBuilder.whereInIds(
result.identifiers.map((identifier) => identifier.id),
);
const afterResult = await eventSelectQueryBuilder.getMany();
const formattedResultForEvent = formatResult<T[]>(
afterResult,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.CREATED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedResultForEvent,
authContext: this.authContext,
});
// TypeORM returns all entity columns for insertions
const resultWithoutInsertionExtraColumns = result.raw.map(
(rawResult: Record<string, string>) =>
Object.keys(rawResult)
.filter((key) => this.expressionMap.returning.includes(key))
.reduce((filtered: Record<string, string>, key) => {
filtered[key] = rawResult[key];
return filtered;
}, {}),
);
const formattedResult = formatResult<T[]>(
resultWithoutInsertionExtraColumns,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return {
raw: resultWithoutInsertionExtraColumns,
generatedMaps: formattedResult,
identifiers: result.identifiers,
};
}
private getMainAliasTarget(): EntityTarget<T> {
@@ -11,6 +11,7 @@ import {
PermissionsException,
PermissionsExceptionCode,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception';
import {
TwentyORMException,
TwentyORMExceptionCode,
@@ -66,111 +67,139 @@ export class WorkspaceSelectQueryBuilder<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override async execute(): Promise<any> {
this.validatePermissions();
try {
this.validatePermissions();
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const result = await super.execute();
const result = await super.execute();
const formattedResult = formatResult<T[]>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedResult = formatResult<T[]>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return {
raw: result,
generatedMaps: formattedResult,
identifiers: result.identifiers,
};
return {
raw: result,
generatedMaps: formattedResult,
identifiers: result.identifiers,
};
} catch (error) {
throw computeTwentyORMException(error);
}
}
override async getMany(): Promise<T[]> {
this.validatePermissions();
try {
this.validatePermissions();
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const result = await super.getMany();
const result = await super.getMany();
const formattedResult = formatResult<T[]>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedResult = formatResult<T[]>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return formattedResult;
return formattedResult;
} catch (error) {
throw computeTwentyORMException(error);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override getRawOne<U = any>(): Promise<U | undefined> {
this.validatePermissions();
try {
this.validatePermissions();
return super.getRawOne();
return super.getRawOne();
} catch (error) {
throw computeTwentyORMException(error);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override getRawMany<U = any>(): Promise<U[]> {
this.validatePermissions();
try {
this.validatePermissions();
return super.getRawMany();
return super.getRawMany();
} catch (error) {
throw computeTwentyORMException(error);
}
}
override async getOne(): Promise<T | null> {
this.validatePermissions();
try {
this.validatePermissions();
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const result = await super.getOne();
const result = await super.getOne();
const formattedResult = formatResult<T>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedResult = formatResult<T>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return formattedResult;
return formattedResult;
} catch (error) {
throw computeTwentyORMException(error);
}
}
override async getOneOrFail(): Promise<T> {
this.validatePermissions();
try {
this.validatePermissions();
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const result = await super.getOneOrFail();
const result = await super.getOneOrFail();
const formattedResult = formatResult<T>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedResult = formatResult<T>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return formattedResult[0];
return formattedResult[0];
} catch (error) {
throw computeTwentyORMException(error);
}
}
override getCount(): Promise<number> {
this.validatePermissions();
try {
this.validatePermissions();
return super.getCount();
return super.getCount();
} catch (error) {
throw computeTwentyORMException(error);
}
}
override getExists(): Promise<boolean> {
@@ -181,24 +210,28 @@ export class WorkspaceSelectQueryBuilder<
}
override async getManyAndCount(): Promise<[T[], number]> {
this.validatePermissions();
try {
this.validatePermissions();
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const [result, count] = await super.getManyAndCount();
const [result, count] = await super.getManyAndCount();
const formattedResult = formatResult<T[]>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedResult = formatResult<T[]>(
result,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return [formattedResult, count];
return [formattedResult, count];
} catch (error) {
throw computeTwentyORMException(error);
}
}
override insert(): WorkspaceInsertQueryBuilder<T> {
@@ -13,6 +13,7 @@ import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/works
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception';
import {
TwentyORMException,
TwentyORMExceptionCode,
@@ -62,43 +63,47 @@ export class WorkspaceSoftDeleteQueryBuilder<
}
override async execute(): Promise<UpdateResult> {
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
try {
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const after = await super.execute();
const after = await super.execute();
const formattedAfter = formatResult<T[]>(
after.raw,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const formattedAfter = formatResult<T[]>(
after.raw,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.DELETED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedAfter,
authContext: this.authContext,
});
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.DELETED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedAfter,
authContext: this.authContext,
});
return {
raw: after.raw,
generatedMaps: formattedAfter,
affected: after.affected,
};
return {
raw: after.raw,
generatedMaps: formattedAfter,
affected: after.affected,
};
} catch (error) {
throw computeTwentyORMException(error);
}
}
override select(): WorkspaceSelectQueryBuilder<T> {
@@ -17,6 +17,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
import { QueryDeepPartialEntityWithNestedRelationFields } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-nested-relation-fields.type';
import { RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type';
import { RelationDisconnectQueryFieldsByEntityIndex } from 'src/engine/twenty-orm/entity-manager/types/relation-nested-query-fields-by-entity-index.type';
import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception';
import {
TwentyORMException,
TwentyORMExceptionCode,
@@ -80,156 +81,42 @@ export class WorkspaceUpdateQueryBuilder<
}
override async execute(): Promise<UpdateResult> {
if (this.manyInputs) {
return this.executeMany();
}
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
true,
this.authContext,
this.featureFlagMap,
);
eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
eventSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases;
eventSelectQueryBuilder.setParameters(this.getParameters());
const before = await eventSelectQueryBuilder.getMany();
const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
this.shouldBypassPermissionChecks,
this.authContext,
);
if (isDefined(this.relationNestedConfig)) {
const updatedValues =
await this.relationNestedQueries.processRelationNestedQueries({
entities: this.expressionMap.valuesSet as
| QueryDeepPartialEntityWithNestedRelationFields<T>
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
relationNestedConfig: this.relationNestedConfig,
queryBuilder: nestedRelationQueryBuilder,
});
this.expressionMap.valuesSet =
updatedValues.length === 1 ? updatedValues[0] : updatedValues;
}
const formattedBefore = formatResult<T[]>(
before,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const result = await super.execute();
const after = await eventSelectQueryBuilder.getMany();
const formattedAfter = formatResult<T[]>(
after,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.UPDATED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedAfter,
beforeEntities: formattedBefore,
authContext: this.authContext,
});
const formattedResult = formatResult<T[]>(
result.raw,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return {
raw: result.raw,
generatedMaps: formattedResult,
affected: result.affected,
};
}
public async executeMany(): Promise<UpdateResult> {
for (const input of this.manyInputs) {
const fakeExpressionMapToValidatePermissions = Object.assign(
{},
this.expressionMap,
{
wheres: input.criteria,
valuesSet: input.partialEntity,
},
);
try {
if (this.manyInputs) {
return this.executeMany();
}
validateQueryIsPermittedOrThrow({
expressionMap: fakeExpressionMapToValidatePermissions,
expressionMap: this.expressionMap,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
}
const mainAliasTarget = this.getMainAliasTarget();
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
true,
this.authContext,
this.featureFlagMap,
);
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
true,
this.authContext,
this.featureFlagMap,
);
eventSelectQueryBuilder.whereInIds(
this.manyInputs.map((input) => input.criteria),
);
eventSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases;
eventSelectQueryBuilder.setParameters(this.getParameters());
eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
eventSelectQueryBuilder.expressionMap.aliases =
this.expressionMap.aliases;
eventSelectQueryBuilder.setParameters(this.getParameters());
const beforeRecords = await eventSelectQueryBuilder.getMany();
const formattedBefore = formatResult<T[]>(
beforeRecords,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const results: UpdateResult[] = [];
for (const input of this.manyInputs) {
this.expressionMap.valuesSet = input.partialEntity;
this.where({ id: input.criteria });
const before = await eventSelectQueryBuilder.getMany();
const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
@@ -242,7 +129,7 @@ export class WorkspaceUpdateQueryBuilder<
if (isDefined(this.relationNestedConfig)) {
const updatedValues =
await this.relationNestedQueries.processRelationNestedQueries({
entities: input.partialEntity as
entities: this.expressionMap.valuesSet as
| QueryDeepPartialEntityWithNestedRelationFields<T>
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
relationNestedConfig: this.relationNestedConfig,
@@ -253,39 +140,163 @@ export class WorkspaceUpdateQueryBuilder<
updatedValues.length === 1 ? updatedValues[0] : updatedValues;
}
const formattedBefore = formatResult<T[]>(
before,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const result = await super.execute();
const after = await eventSelectQueryBuilder.getMany();
results.push(result);
const formattedAfter = formatResult<T[]>(
after,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.UPDATED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedAfter,
beforeEntities: formattedBefore,
authContext: this.authContext,
});
const formattedResult = formatResult<T[]>(
result.raw,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return {
raw: result.raw,
generatedMaps: formattedResult,
affected: result.affected,
};
} catch (error) {
throw computeTwentyORMException(error);
}
}
const afterRecords = await eventSelectQueryBuilder.getMany();
public async executeMany(): Promise<UpdateResult> {
try {
for (const input of this.manyInputs) {
const fakeExpressionMapToValidatePermissions = Object.assign(
{},
this.expressionMap,
{
wheres: input.criteria,
valuesSet: input.partialEntity,
},
);
const formattedAfter = formatResult<T[]>(
afterRecords,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
validateQueryIsPermittedOrThrow({
expressionMap: fakeExpressionMapToValidatePermissions,
objectRecordsPermissions: this.objectRecordsPermissions,
objectMetadataMaps: this.internalContext.objectMetadataMaps,
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
isFieldPermissionsEnabled:
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
});
}
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.UPDATED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedAfter,
beforeEntities: formattedBefore,
authContext: this.authContext,
});
const mainAliasTarget = this.getMainAliasTarget();
const formattedResults = formatResult<T[]>(
results.map((result) => result.raw),
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
return {
raw: results.map((result) => result.raw),
generatedMaps: formattedResults,
affected: results.length,
};
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
true,
this.authContext,
this.featureFlagMap,
);
eventSelectQueryBuilder.whereInIds(
this.manyInputs.map((input) => input.criteria),
);
eventSelectQueryBuilder.expressionMap.aliases =
this.expressionMap.aliases;
eventSelectQueryBuilder.setParameters(this.getParameters());
const beforeRecords = await eventSelectQueryBuilder.getMany();
const formattedBefore = formatResult<T[]>(
beforeRecords,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
const results: UpdateResult[] = [];
for (const input of this.manyInputs) {
this.expressionMap.valuesSet = input.partialEntity;
this.where({ id: input.criteria });
const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder(
this as unknown as WorkspaceSelectQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
this.shouldBypassPermissionChecks,
this.authContext,
);
if (isDefined(this.relationNestedConfig)) {
const updatedValues =
await this.relationNestedQueries.processRelationNestedQueries({
entities: input.partialEntity as
| QueryDeepPartialEntityWithNestedRelationFields<T>
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
relationNestedConfig: this.relationNestedConfig,
queryBuilder: nestedRelationQueryBuilder,
});
this.expressionMap.valuesSet =
updatedValues.length === 1 ? updatedValues[0] : updatedValues;
}
const result = await super.execute();
results.push(result);
}
const afterRecords = await eventSelectQueryBuilder.getMany();
const formattedAfter = formatResult<T[]>(
afterRecords,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
await this.internalContext.eventEmitterService.emitMutationEvent({
action: DatabaseEventAction.UPDATED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedAfter,
beforeEntities: formattedBefore,
authContext: this.authContext,
});
const formattedResults = formatResult<T[]>(
results.map((result) => result.raw),
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return {
raw: results.map((result) => result.raw),
generatedMaps: formattedResults,
affected: results.length,
};
} catch (error) {
throw computeTwentyORMException(error);
}
}
override set(