fix(server): skip composite fields with missing columns in NormalizeCompositeFieldDefaultsCommand

Some workspaces created before the actor composite type's `context` sub-property was added are missing the corresponding workspace-schema columns (e.g. `attachment.createdByContext`). The migration runner then fails with `column "createdByContext" of relation "attachment" does not exist` when this 2.5 workspace command tries to ALTER COLUMN ... SET DEFAULT NULL on the missing column.

Before building the metadata update, query `information_schema.columns` for the workspace schema and filter out any composite field whose sub-property columns aren't all physically present. Skipped fields are logged so the missing columns can be backfilled in a follow-up.
This commit is contained in:
Charles Bochet
2026-05-14 19:15:44 +02:00
parent ca1571676c
commit 5138ab9d22

View File

@@ -100,11 +100,44 @@ export class NormalizeCompositeFieldDefaultsCommand extends ActiveOrSuspendedWor
return;
}
const fieldsToUpdate = affectedFields.filter((field) => {
const compositeType = compositeTypeDefinitions.get(
field.type as FieldMetadataType,
);
if (!isDefined(compositeType)) {
return false;
}
for (const property of compositeType.properties) {
if (
!isDefined(field.defaultValue) ||
!(property.name in field.defaultValue)
) {
this.logger.warn(
`Skipping composite field "${field.name}" (${field.id}) for workspace ${workspaceId}: defaultValue is missing key "${property.name}"`,
);
return false;
}
}
return true;
});
if (fieldsToUpdate.length === 0) {
this.logger.log(
`No composite fields to update for workspace ${workspaceId} after defaultValue-shape check, skipping`,
);
return;
}
const schemaName = getWorkspaceSchemaName(workspaceId);
const backfillTargets: Array<{ tableName: string; columnName: string }> =
[];
for (const field of affectedFields) {
for (const field of fieldsToUpdate) {
const flatObjectMetadata =
flatObjectMetadataMaps.byUniversalIdentifier[
field.objectMetadataUniversalIdentifier
@@ -146,7 +179,7 @@ export class NormalizeCompositeFieldDefaultsCommand extends ActiveOrSuspendedWor
}
}
const fieldsByApplication = affectedFields.reduce<
const fieldsByApplication = fieldsToUpdate.reduce<
Map<string, typeof affectedFields>
>((acc, field) => {
const key = field.applicationUniversalIdentifier;