Repository: twentyhq/twenty (2025-08-12T13:53:20Z)

Issue:
Destroy many custom objects fails
When trying to destroy many records of a custom object, it fails with a backend error : 

`missing FROM-clause entry for table "_surveyResult`

This doesn't fail for standard objects.


Discussion:

Patch:
diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts
index 097ba73dd17a..07243cb6dfce 100644
--- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts
+++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts
@@ -12,7 +12,6 @@ import { type DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-r
 
 import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
 import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
-import { computeTableName } from 'src/engine/utils/compute-table-name.util';
 
 @Injectable()
 export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseResolverService<
@@ -31,14 +30,9 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
       objectMetadataItemWithFieldMaps.nameSingular,
     );
 
-    const tableName = computeTableName(
-      objectMetadataItemWithFieldMaps.nameSingular,
-      objectMetadataItemWithFieldMaps.isCustom,
-    );
-
     executionArgs.graphqlQueryParser.applyFilterToBuilder(
       queryBuilder,
-      tableName,
+      objectMetadataItemWithFieldMaps.nameSingular,
       executionArgs.args.filter,
     );
 
diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts
index 6e72b03167c8..f603d099c777 100644
--- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts
@@ -7,6 +7,7 @@ import {
   type ObjectLiteral,
 } from 'typeorm';
 import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
+import { type WhereClause } from 'typeorm/query-builder/WhereClause';
 
 import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
 import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
@@ -23,8 +24,10 @@ import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repositor
 import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
 import { type WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
 import { type WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
+import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition';
 import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
 import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
+import { computeTableName } from 'src/engine/utils/compute-table-name.util';
 
 export class WorkspaceDeleteQueryBuilder<
   T extends ObjectLiteral,
@@ -89,13 +92,25 @@ export class WorkspaceDeleteQueryBuilder<
         this.featureFlagMap,
       );
 
+      const tableName = computeTableName(
+        objectMetadata.nameSingular,
+        objectMetadata.isCustom,
+      );
+
       eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
+
       eventSelectQueryBuilder.expressionMap.aliases =
         this.expressionMap.aliases;
       eventSelectQueryBuilder.setParameters(this.getParameters());
 
       const before = await eventSelectQueryBuilder.getOne();
 
+      this.expressionMap.wheres = applyTableAliasOnWhereCondition({
+        condition: this.expressionMap.wheres,
+        tableName,
+        aliasName: objectMetadata.nameSingular,
+      }) as WhereClause[];
+
       const result = await super.execute();
 
       const formattedResult = formatResult<T[]>(
diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/apply-table-alias-on-where-condition.ts b/packages/twenty-server/src/engine/twenty-orm/utils/apply-table-alias-on-where-condition.ts
new file mode 100644
index 000000000000..29c04407d195
--- /dev/null
+++ b/packages/twenty-server/src/engine/twenty-orm/utils/apply-table-alias-on-where-condition.ts
@@ -0,0 +1,61 @@
+import { isArray, isObject, isString } from '@sniptt/guards';
+import {
+  type WhereClause,
+  type WhereClauseCondition,
+} from 'typeorm/query-builder/WhereClause';
+
+type ApplyTableAliasOnWhereConditionParams = {
+  condition: WhereClauseCondition;
+  tableName: string;
+  aliasName: string;
+};
+
+export const applyTableAliasOnWhereCondition = ({
+  condition,
+  tableName,
+  aliasName,
+}: ApplyTableAliasOnWhereConditionParams): WhereClauseCondition => {
+  if (isString(condition)) {
+    const conditionParts = condition.split('.');
+
+    if (conditionParts.length === 1) {
+      return condition;
+    }
+
+    const [tableNamePart, ...rest] = conditionParts;
+
+    return `${tableNamePart.replace(aliasName, tableName)}.${rest.join('.')}`;
+  }
+
+  if (isArray(condition)) {
+    return condition.map((where: WhereClause) => {
+      return {
+        ...where,
+        condition: applyTableAliasOnWhereCondition({
+          condition: where.condition,
+          tableName,
+          aliasName,
+        }),
+      };
+    });
+  }
+
+  if (isObject(condition)) {
+    if ('condition' in condition) {
+      return {
+        ...condition,
+        condition: applyTableAliasOnWhereCondition({
+          condition: condition.condition,
+          tableName,
+          aliasName,
+        }),
+      };
+    }
+
+    if ('operator' in condition) {
+      return condition;
+    }
+  }
+
+  return condition;
+};


Test patch:
diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/apply-table-alias-on-where-condition.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/apply-table-alias-on-where-condition.util.spec.ts
new file mode 100644
index 000000000000..4c30ffb2c06c
--- /dev/null
+++ b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/apply-table-alias-on-where-condition.util.spec.ts
@@ -0,0 +1,132 @@
+import { type WhereClauseCondition } from 'typeorm/query-builder/WhereClause';
+
+import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition';
+
+describe('applyTableAliasOnWhereCondition', () => {
+  const tableName = '_listing';
+  const aliasName = 'listing';
+
+  describe('when processing simple where conditions with string conditions', () => {
+    it('should replace alias name with table name in string condition', () => {
+      const condition: WhereClauseCondition = [
+        {
+          type: 'simple',
+          condition: `${aliasName}.id = :id`,
+        },
+      ];
+
+      const result = applyTableAliasOnWhereCondition({
+        condition,
+        tableName,
+        aliasName,
+      });
+
+      expect(result).toEqual([
+        {
+          type: 'simple',
+          condition: `${tableName}.id = :id`,
+        },
+      ]);
+    });
+
+    it('should replace alias name with table name in array condition and wrapping operator condition', () => {
+      const condition: WhereClauseCondition = [
+        {
+          type: 'simple',
+          condition: {
+            operator: 'brackets',
+            condition: [
+              {
+                type: 'simple',
+                condition: `"${aliasName}"."id" IN (:...id8oi6y)`,
+              },
+            ],
+          },
+        },
+      ];
+
+      const result = applyTableAliasOnWhereCondition({
+        condition,
+        tableName,
+        aliasName,
+      });
+
+      expect(result).toEqual([
+        {
+          type: 'simple',
+          condition: {
+            operator: 'brackets',
+            condition: [
+              {
+                type: 'simple',
+                condition: `"${tableName}"."id" IN (:...id8oi6y)`,
+              },
+            ],
+          },
+        },
+      ]);
+    });
+
+    it('should replace alias name with table name in object condition', () => {
+      const condition: WhereClauseCondition = {
+        parameters: ['id'],
+        operator: 'equal',
+      };
+
+      const result = applyTableAliasOnWhereCondition({
+        condition,
+        tableName,
+        aliasName,
+      });
+
+      expect(result).toEqual({
+        operator: 'equal',
+        parameters: ['id'],
+      });
+    });
+
+    it('should replace alias name with table name in string condition without alias name', () => {
+      const condition: WhereClauseCondition = [
+        {
+          type: 'simple',
+          condition: `id = :id`,
+        },
+      ];
+
+      const result = applyTableAliasOnWhereCondition({
+        condition,
+        tableName,
+        aliasName,
+      });
+
+      expect(result).toEqual([
+        {
+          type: 'simple',
+          condition: `id = :id`,
+        },
+      ]);
+    });
+
+    it('should replace alias name with table name in string condition without quotes', () => {
+      const condition: WhereClauseCondition = [
+        {
+          type: 'simple',
+          condition: `${aliasName}."id" = :id`,
+        },
+      ];
+
+      const result = applyTableAliasOnWhereCondition({
+        condition,
+        tableName,
+        aliasName,
+      });
+
+      expect(result).toEqual([
+        {
+          type: 'simple',
+          condition: `${tableName}."id" = :id`,
+        },
+      ]);
+    });
+  });
+});