Null - Second command - Cleaning remaining empty values (#16241)
Because of [this code I forget to clean](https://github.com/twentyhq/twenty/pull/16217), - __workspace created between 1.12.0 and 1.12.2 (from friday 28 nov PM > monday 1 dec PM)__ have standard objects with empty string default value set on related TEXT type column (but default value null on field metadata) (ex: jobTitle on person) - __custom objects created between 1.12.0 and 1.12.2 (from friday 28 nov PM > monday 1 dec PM)__ have "name" TEXT field with empty string default value set + NOT NULL constraint on column Command cleans data and updates table structure
This commit is contained in:
+200
@@ -0,0 +1,200 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-13:clean-empty-string-null-in-text-fields',
|
||||
description:
|
||||
'Clean up empty string defaults in TEXT fields and convert them to NULL',
|
||||
})
|
||||
export class CleanEmptyStringNullInTextFieldsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
protected readonly logger = new Logger(
|
||||
CleanEmptyStringNullInTextFieldsCommand.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
protected readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
dataSource,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun || false;
|
||||
|
||||
if (!isDefined(dataSource)) {
|
||||
throw new Error(
|
||||
`Could not find data source for workspace ${workspaceId}, should never occur`,
|
||||
);
|
||||
}
|
||||
|
||||
const workspace = await this.workspaceRepository.findOne({
|
||||
where: { id: workspaceId },
|
||||
});
|
||||
|
||||
if (!isDefined(workspace)) {
|
||||
throw new Error(
|
||||
`Could not find workspace ${workspaceId}, should never occur`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
!isDefined(workspace.version) ||
|
||||
!['1.12.0', '1.12.1'].includes(workspace.version)
|
||||
) {
|
||||
this.logger.log(
|
||||
`Workspace ${workspaceId} is not a v1.12.0 or v1.12.1 workspace, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log('Dry run mode: No changes will be applied');
|
||||
}
|
||||
|
||||
const objectMetadataItems = await this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
relations: ['fields'],
|
||||
});
|
||||
|
||||
for (const objectMetadataItem of objectMetadataItems) {
|
||||
const tableName = computeObjectTargetTable(objectMetadataItem);
|
||||
|
||||
if (!objectMetadataItem.isCustom) {
|
||||
await this.cleanUpEmptyStringDefaultsInTextFieldsInStandardObjects(
|
||||
objectMetadataItem,
|
||||
tableName,
|
||||
schemaName,
|
||||
dataSource,
|
||||
isDryRun,
|
||||
);
|
||||
}
|
||||
|
||||
if (objectMetadataItem.isCustom) {
|
||||
await this.cleanUpEmptyStringDefaultsAndSetNullableInNameFieldInCustomObjects(
|
||||
objectMetadataItem,
|
||||
tableName,
|
||||
schemaName,
|
||||
dataSource,
|
||||
isDryRun,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanUpEmptyStringDefaultsInTextFieldsInStandardObjects(
|
||||
objectMetadataItem: ObjectMetadataEntity,
|
||||
tableName: string,
|
||||
schemaName: string,
|
||||
dataSource: WorkspaceDataSource,
|
||||
isDryRun: boolean,
|
||||
): Promise<void> {
|
||||
const textFields = objectMetadataItem.fields.filter(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.TEXT &&
|
||||
field.isNullable === true &&
|
||||
field.defaultValue === null,
|
||||
);
|
||||
|
||||
for (const field of textFields) {
|
||||
this.logger.log(
|
||||
`Checking field ${field.name} on standard object ${objectMetadataItem.nameSingular} (Table: ${tableName})`,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Cleaning up empty string default for field ${field.name} on ${tableName}`,
|
||||
);
|
||||
|
||||
if (!isDryRun) {
|
||||
await dataSource.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "${field.name}" DROP DEFAULT`,
|
||||
[],
|
||||
undefined,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await dataSource.query(
|
||||
`UPDATE "${schemaName}"."${tableName}" SET "${field.name}" = NULL WHERE "${field.name}" = ''`,
|
||||
[],
|
||||
undefined,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanUpEmptyStringDefaultsAndSetNullableInNameFieldInCustomObjects(
|
||||
objectMetadataItem: ObjectMetadataEntity,
|
||||
tableName: string,
|
||||
schemaName: string,
|
||||
dataSource: WorkspaceDataSource,
|
||||
isDryRun: boolean,
|
||||
): Promise<void> {
|
||||
const nameField = objectMetadataItem.fields.find(
|
||||
(field) =>
|
||||
field.name === 'name' &&
|
||||
field.type === FieldMetadataType.TEXT &&
|
||||
field.defaultValue === "''",
|
||||
);
|
||||
|
||||
if (!isDefined(nameField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found "name" field with empty string default on ${objectMetadataItem.nameSingular} (Table: ${tableName})`,
|
||||
);
|
||||
|
||||
if (!isDryRun) {
|
||||
await dataSource.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "name" DROP DEFAULT, ALTER COLUMN "name" DROP NOT NULL`,
|
||||
[],
|
||||
undefined,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await dataSource.query(
|
||||
`UPDATE "${schemaName}"."${tableName}" SET "name" = NULL WHERE "name" = ''`,
|
||||
[],
|
||||
undefined,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await this.fieldMetadataRepository.update(nameField.id, {
|
||||
defaultValue: null,
|
||||
isNullable: true,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Updated "name" field metadata and cleaned empty strings for ${objectMetadataItem.nameSingular}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { CleanEmptyStringNullInTextFieldsCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-clean-empty-string-null-in-text-fields.command';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
ObjectMetadataEntity,
|
||||
FieldMetadataEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
],
|
||||
providers: [CleanEmptyStringNullInTextFieldsCommand],
|
||||
exports: [CleanEmptyStringNullInTextFieldsCommand],
|
||||
})
|
||||
export class V1_13_UpgradeVersionCommandModule {}
|
||||
+2
@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { V1_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module';
|
||||
import { V1_11_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-11/1-11-upgrade-version-command.module';
|
||||
import { V1_12_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-12/1-12-upgrade-version-command.module';
|
||||
import { V1_13_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-13/1-13-upgrade-version-command.module';
|
||||
import { V1_6_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-6/1-6-upgrade-version-command.module';
|
||||
import { V1_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module';
|
||||
import { V1_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-8/1-8-upgrade-version-command.module';
|
||||
@@ -21,6 +22,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
||||
V1_10_UpgradeVersionCommandModule,
|
||||
V1_11_UpgradeVersionCommandModule,
|
||||
V1_12_UpgradeVersionCommandModule,
|
||||
V1_13_UpgradeVersionCommandModule,
|
||||
DataSourceModule,
|
||||
WorkspaceSyncMetadataModule,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user