Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 0ce8c6220f Add missing index on permissionFlag.roleId for permission check queries
https://sonarly.com/issue/20984?type=bug

The `GET /rest/metadata/objects` endpoint takes ~9.6 seconds due to a `roleRepository.findOne()` with LEFT JOIN to `permissionFlag` table taking 8.6 seconds, caused by a missing index on `permissionFlag.roleId` combined with database contention.

Fix: Added a TypeORM core migration that creates an index on `core.permissionFlag("roleId")`.

The `permissionFlag` table was created without an index on the `roleId` column. While it has a unique constraint `IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE` on `("flag", "roleId")`, the leading column is `flag`, making it unusable for JOIN lookups by `roleId` alone.

Every authenticated REST metadata API request triggers `SettingsPermissionGuard`, which calls `PermissionsService.userHasWorkspaceSettingPermission()`. This method does `roleRepository.findOne({ where: { id, workspaceId }, relations: ['permissionFlags'] })`, which TypeORM translates into a `SELECT DISTINCT` with `LEFT JOIN core.permissionFlag ON permissionFlag.roleId = role.id`. Without an index on `roleId`, PostgreSQL must sequentially scan the `permissionFlag` table for the JOIN — which under database contention caused the query to take 8.6 seconds.

The fix:
```sql
CREATE INDEX "IDX_PERMISSION_FLAG_ROLE_ID" ON "core"."permissionFlag" ("roleId")
```

This follows the team's existing convention — compare with `objectPermission` which already has `IDX_OBJECT_PERMISSION_WORKSPACE_ID_ROLE_ID` for the same purpose. The migration uses the established naming pattern (`IDX_<TABLE>_<COLUMN>`) and includes proper `up`/`down` methods.

For large production deployments, consider running `CREATE INDEX CONCURRENTLY` manually before the migration to avoid table locking.
2026-04-02 00:44:00 +00:00
@@ -0,0 +1,19 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class AddPermissionFlagRoleIdIndex1775090400000
implements MigrationInterface
{
name = 'AddPermissionFlagRoleIdIndex1775090400000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE INDEX "IDX_PERMISSION_FLAG_ROLE_ID" ON "core"."permissionFlag" ("roleId")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "core"."IDX_PERMISSION_FLAG_ROLE_ID"`,
);
}
}