fix: add workspace command to backfill missing AGENT source enum values (#22593)

**What**
Adds a 2.19 workspace upgrade command that backfills missing
FieldActorSource enum values (AGENT) into the Postgres enums backing
every ACTOR composite field (createdBy, updatedBy) across all existing
workspaces.

Each ACTOR field stores its source sub-field as a Postgres enum scoped
to its own table and schema (e.g.
workspace_abc.company_createdBySource_enum). Workspaces created before
these enum values were introduced are missing them, which causes runtime
errors when those sources are used.

**How**
buildActorSourceEnumBackfillTargets collects all ACTOR fields from the
workspace metadata cache and maps each enum sub-property to its
(tableName, columnName, enumName, expectedValues) tuple.
Before iterating over all targets, the command performs a single fast
pg_catalog.pg_enum lookup on company.createdBySource as a representative
sentinel. If AGENT is already present there, the workspace is skipped
entirely (idempotency fast-path).
For each remaining target, ALTER TYPE … ADD VALUE IF NOT EXISTS is
issued per missing value via
WorkspaceSchemaEnumManagerService.addEnumValue, making the command fully
idempotent and safe to re-run.

Fixes
https://discord.com/channels/1130383047699738754/1522507190949118003

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22593?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Etienne
2026-07-08 14:05:15 +02:00
committed by GitHub
parent 6bc4d8efac
commit 4b9f393167
9 changed files with 310 additions and 5 deletions
@@ -32,6 +32,37 @@ describe('WorkspaceSchemaEnumManagerService', () => {
>;
});
describe('addEnumValue', () => {
it('emits a plain ADD VALUE statement', async () => {
await service.addEnumValue({
queryRunner: queryRunner as unknown as QueryRunner,
schemaName: 'workspace_adhj7eaegq93fzpgbfpdm8ok3',
enumName: 'company_createdBySource_enum',
value: 'AGENT',
});
expect(executedSql).toHaveLength(1);
expect(executedSql[0]).toContain('ADD VALUE ');
expect(executedSql[0]).not.toContain('IF NOT EXISTS');
expect(executedSql[0]).toContain("'AGENT'");
});
});
describe('upsertEnumValue', () => {
it('emits ADD VALUE IF NOT EXISTS', async () => {
await service.upsertEnumValue({
queryRunner: queryRunner as unknown as QueryRunner,
schemaName: 'workspace_adhj7eaegq93fzpgbfpdm8ok3',
enumName: 'company_createdBySource_enum',
value: 'AGENT',
});
expect(executedSql).toHaveLength(1);
expect(executedSql[0]).toContain('ADD VALUE IF NOT EXISTS');
expect(executedSql[0]).toContain("'AGENT'");
});
});
describe('alterEnumValues', () => {
it('should not generate an enum rename whose target collides with the source when the name exceeds the identifier length limit', async () => {
// Real-world case: long object + field names produce an enum name over
@@ -109,6 +109,22 @@ export class WorkspaceSchemaEnumManagerService {
await queryRunner.query(sql);
}
async upsertEnumValue({
queryRunner,
schemaName,
enumName,
value,
}: {
queryRunner: QueryRunner;
schemaName: string;
enumName: string;
value: string;
}): Promise<void> {
const sql = `ALTER TYPE ${escapeIdentifier(schemaName)}.${escapeIdentifier(enumName)} ADD VALUE IF NOT EXISTS ${escapeLiteral(value)}`;
await queryRunner.query(sql);
}
async renameEnumValue({
queryRunner,
schemaName,