Sonarly Claude Code
86fea3aaf2
Slow POST /metadata: SELECT DISTINCT on fieldMetadata takes 1.4s, total 3.7s
https://sonarly.com/issue/3801?type=bug
The `POST /metadata` GraphQL endpoint takes 3.67s due to a `SELECT DISTINCT` query on `core.fieldMetadata` (1.37s) generated by NestJS-Query's `@CursorConnection` auto-resolver, plus ~2.3s of post-query GraphQL field resolution overhead.
Fix: ## Root Cause
`@CursorConnection('fields', () => FieldMetadataDTO)` on `ObjectMetadataDTO` causes NestJS-Query to auto-generate a resolver that executes:
```sql
SELECT DISTINCT "fields"."workspaceId", ... /* 26 columns, 4 JSONB */
FROM "core"."fieldMetadata" "fields"
WHERE "fields"."workspaceId" = $1
AND "fields"."objectMetadataId" IN ($2, ..., $39)
LIMIT 1001
```
The `DISTINCT` deduplication across 4 JSONB columns takes 1,371ms. An optimized `fieldsList` ResolveField already exists — it uses a DataLoader backed by `WorkspaceManyOrAllFlatEntityMapsCacheService` (local 100ms TTL → Redis → provider recompute).
## Fix
### 1. Remove the slow `@CursorConnection` decorator
```typescript file=packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts
// REMOVED:
// @CursorConnection('fields', () => FieldMetadataDTO)
// Also removed: import { FieldMetadataDTO } from '...field-metadata.dto'
@CursorConnection('indexMetadatas', () => IndexMetadataDTO)
export class ObjectMetadataDTO { ... }
```
The `fieldsList` ResolveField (DataLoader path) remains as the correct, cache-backed way to fetch fields.
### 2. Update the internal REST API caller
```typescript file=packages/twenty-server/src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils.ts lines=70-76
const fieldsPart = selector?.fields
? `
fieldsList {
${fieldsSelection}
}
`
: '';
```
### 3. Update the Zapier integration (3 files)
`requestDb.ts` — replace the cursor connection query with `fieldsList`:
```typescript
fieldsList {
type name label description isNullable isActive defaultValue
}
```
`data.types.ts` — update `Node` type:
```typescript
export type Node = {
nameSingular: string;
namePlural: string;
labelSingular: string;
fieldsList: NodeField[]; // was: fields: { edges: { node: NodeField }[] }
};
// NodeField gains: isActive: boolean
```
`computeInputFields.ts` — preserve active-field-only filtering at the client level:
```typescript
for (const nodeField of node.fieldsList.filter((f) => f.isActive)) {
// was: for (const field of node.fields.edges) { const nodeField = field.node;
```
## Impact
- Eliminates the 1,371ms `SELECT DISTINCT` query entirely.
- REST API response shape changes: `data.objects.edges[n].node.fields.edges[m].node` → `data.objects.edges[n].node.fieldsList[m]`. External REST consumers using the raw `/metadata/objects` endpoint with field selectors will need to update their response parsing.
- The top-level `fields(paging: ...)` GraphQL query (on `FieldMetadataDTO`) is a separate resolver and is **not** affected.
2026-03-06 08:01:07 +00:00
..
2026-03-06 01:03:50 +01:00
2026-03-06 01:03:50 +01:00
2025-12-22 15:19:11 +01:00
2026-03-06 01:03:50 +01:00
2026-03-06 01:03:50 +01:00
2026-03-06 02:24:32 +01:00
2026-03-06 01:03:50 +01:00
2026-03-06 02:23:59 +01:00
2026-03-06 08:45:08 +01:00
2026-03-06 01:03:50 +01:00
2026-03-06 01:03:50 +01:00
2026-03-06 08:01:07 +00:00
2026-03-06 01:03:50 +01:00
2026-03-06 01:03:50 +01:00
2026-02-13 15:43:32 +00:00
2026-03-06 01:03:50 +01:00
2026-03-06 08:01:07 +00:00