Add unique indexes and indexes for composite types (#7162)

Add support for indexes on composite fields and unicity constraint on
indexes

This pull request includes several changes across multiple files to
improve error handling, enforce unique constraints, and update database
migrations. The most important changes include updating error messages
for snack bars, adding a new command to enforce unique constraints, and
updating database migrations to include new fields and constraints.

### Error Handling Improvements:
*
[`packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx`](diffhunk://#diff-e7dc05ced8e4730430f5c7fcd0c75b3aa723da438c26e0bef8130b614427dd9aL23-R23):
Updated error messages in `enqueueSnackBar` to use `error.message`
directly.
*
[`packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts`](diffhunk://#diff-74c126d6bc7a5ed6b63be994d298df6669058034bfbc367b11045f9f31a3abe6L44-R46):
Simplified error messages in `enqueueSnackBar`.
*
[`packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts`](diffhunk://#diff-af23a1d99639a66c251f87473e63e2b7bceaa4ee4f70fedfa0fcffe5c7d79181L56-R58):
Simplified error messages in `enqueueSnackBar`.
*
[`packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsError.ts`](diffhunk://#diff-da04296cbe280202a1eaf6b1244a30490d4f400411bee139651172c59719088eL22-R24):
Simplified error messages in `enqueueSnackBar`.

### New Command for Unique Constraints:
*
[`packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-enforce-unique-constraints.command.ts`](diffhunk://#diff-8337096c8c80dd2619a5ba691ae5145101f8ae0368a75192a050047e8c6ab7cbR1-R159):
Added a new command to enforce unique constraints on company domain
names and person emails.
*
[`packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts`](diffhunk://#diff-20215e9981a53c7566e9cbff96715685125878f5bcb84fe461a7440f2e68f6fcR13-R14):
Integrated the new `EnforceUniqueConstraintsCommand` into the upgrade
process.
[[1]](diffhunk://#diff-20215e9981a53c7566e9cbff96715685125878f5bcb84fe461a7440f2e68f6fcR13-R14)
[[2]](diffhunk://#diff-20215e9981a53c7566e9cbff96715685125878f5bcb84fe461a7440f2e68f6fcR31)
[[3]](diffhunk://#diff-20215e9981a53c7566e9cbff96715685125878f5bcb84fe461a7440f2e68f6fcR64-R68)
*
[`packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts`](diffhunk://#diff-da52814efc674c25ed55645f8ee2561013641a407f88423e705dd6c77b405527R7):
Registered the new `EnforceUniqueConstraintsCommand` in the module.
[[1]](diffhunk://#diff-da52814efc674c25ed55645f8ee2561013641a407f88423e705dd6c77b405527R7)
[[2]](diffhunk://#diff-da52814efc674c25ed55645f8ee2561013641a407f88423e705dd6c77b405527R24)

### Database Migrations:
*
[`packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368824-migrationDebt.ts`](diffhunk://#diff-c450aeae7bc0ef4416a0ade2dc613ca3f688629f35d2a32f90a09c3f494febdcR1-R53):
Added a migration to update the `relationMetadata_ondeleteaction_enum`
and set default values.
*
[`packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368825-addIsUniqueToIndexMetadata.ts`](diffhunk://#diff-8f1e14bd7f6835ec2c3bb39bcc51e3c318a3008d576a981e682f4c985e746fbfR1-R19):
Added a migration to include the `isUnique` field in `indexMetadata`.
*
[`packages/twenty-server/src/database/typeorm/metadata/migrations/1726762935841-addCompostiveColumnToIndexFieldMetadata.ts`](diffhunk://#diff-7c96b7276c7722d41ff31de23b2de4d6e09adfdc74815356ba63bc96a2669440R1-R19):
Added a migration to include the `compositeColumn` field in
`indexFieldMetadata`.
*
[`packages/twenty-server/src/database/typeorm/metadata/migrations/1726766871572-addWhereToIndexMetadata.ts`](diffhunk://#diff-26651295a975eb50e672dce0e4e274e861f66feb1b68105eee5a04df32796190R1-R14):
Added a migration to include the `indexWhereClause` field in
`indexMetadata`.

### GraphQL Exception Handling:
*
[`packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts`](diffhunk://#diff-58445eb362dc89e31107777d39b592d7842d2ab09a223012ccd055da325270a8R1-R4):
Enhanced exception handling for `QueryFailedError` to provide more
specific error messages for unique constraint violations.
[[1]](diffhunk://#diff-58445eb362dc89e31107777d39b592d7842d2ab09a223012ccd055da325270a8R1-R4)
[[2]](diffhunk://#diff-58445eb362dc89e31107777d39b592d7842d2ab09a223012ccd055da325270a8R23-R59)
*
[`packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts`](diffhunk://#diff-233d58ab2333586dd45e46e33d4f07e04a4b8adde4a11a48e25d86985e5a7943L58-R58):
Updated the `workspaceQueryRunnerGraphqlApiExceptionHandler` call to
include context.
*
[`packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts`](diffhunk://#diff-68b803f0762c407f5d2d1f5f8d389655a60654a2dd2394a81318655dcd44dc43L58-R58):
Updated the `workspaceQueryRunnerGraphqlApiExceptionHandler` call to
include context.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Félix Malfait
2024-10-13 10:21:03 +02:00
committed by GitHub
parent d1d4af0c63
commit b792d2a4d3
137 changed files with 22351 additions and 17974 deletions

View File

@ -10,6 +10,7 @@ export const emailsCompositeType: CompositeType = {
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
isIncludedInUniqueConstraint: true,
},
{
name: 'additionalEmails',

View File

@ -10,12 +10,14 @@ export const fullNameCompositeType: CompositeType = {
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
isIncludedInUniqueConstraint: true,
},
{
name: 'lastName',
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
isIncludedInUniqueConstraint: true,
},
],
};

View File

@ -16,6 +16,7 @@ export const linksCompositeType: CompositeType = {
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
isIncludedInUniqueConstraint: true,
},
{
name: 'secondaryLinks',

View File

@ -10,6 +10,7 @@ export const phonesCompositeType: CompositeType = {
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
isIncludedInUniqueConstraint: true,
},
{
name: 'primaryPhoneCountryCode',

View File

@ -118,6 +118,11 @@ export class FieldMetadataDTO<
@Field({ nullable: true })
isNullable?: boolean;
@IsBoolean()
@IsOptional()
@Field({ nullable: true })
isUnique?: boolean;
@Validate(IsFieldMetadataDefaultValue)
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })

View File

@ -108,6 +108,9 @@ export class FieldMetadataEntity<
@Column({ nullable: true, default: true })
isNullable: boolean;
@Column({ nullable: true, default: false })
isUnique: boolean;
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@ -126,7 +129,7 @@ export class FieldMetadataEntity<
@OneToMany(
() => IndexFieldMetadataEntity,
(indexFieldMetadata: IndexFieldMetadataEntity) =>
indexFieldMetadata.fieldMetadata,
indexFieldMetadata.indexMetadata,
{
cascade: true,
},

View File

@ -10,6 +10,7 @@ export interface CompositeProperty<
type: Type;
hidden: 'input' | 'output' | true | false;
isRequired: boolean;
isIncludedInUniqueConstraint?: boolean;
isArray?: boolean;
options?: FieldMetadataOptions<Type>;
}

View File

@ -19,6 +19,7 @@ export interface FieldMetadataInterface<
workspaceId?: string;
description?: string;
isNullable?: boolean;
isUnique?: boolean;
fromRelationMetadata?: RelationMetadataEntity;
toRelationMetadata?: RelationMetadataEntity;
isCustom?: boolean;

View File

@ -1,3 +1,5 @@
import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface';
import { FieldMetadataInterface } from './field-metadata.interface';
import { RelationMetadataInterface } from './relation-metadata.interface';
@ -13,6 +15,7 @@ export interface ObjectMetadataInterface {
fromRelations: RelationMetadataInterface[];
toRelations: RelationMetadataInterface[];
fields: FieldMetadataInterface[];
indexMetadatas: IndexMetadataInterface[];
isSystem: boolean;
isCustom: boolean;
isActive: boolean;

View File

@ -0,0 +1,62 @@
import { Field, HideField, ObjectType } from '@nestjs/graphql';
import {
Authorize,
FilterableField,
IDField,
QueryOptions,
Relation,
} from '@ptc-org/nestjs-query-graphql';
import { IsDateString, IsNotEmpty, IsNumber, IsUUID } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import { IndexMetadataDTO } from './index-metadata.dto';
@ObjectType('indexField')
@Authorize({
authorize: (context: any) => ({
workspaceId: { eq: context?.req?.workspace?.id },
}),
})
@QueryOptions({
defaultResultSize: 10,
disableSort: true,
maxResultsSize: 1000,
})
@Relation('indexMetadata', () => IndexMetadataDTO, {
nullable: true,
})
@Relation('fieldMetadata', () => FieldMetadataDTO, {
nullable: true,
})
export class IndexFieldMetadataDTO {
@IsUUID()
@IsNotEmpty()
@IDField(() => UUIDScalarType)
id: string;
indexMetadataId: string;
@IsUUID()
@IsNotEmpty()
@FilterableField(() => UUIDScalarType)
fieldMetadataId: string;
@IsNumber()
@IsNotEmpty()
@Field()
order: number;
@IsDateString()
@Field()
createdAt: Date;
@IsDateString()
@Field()
updatedAt: Date;
@HideField()
workspaceId: string;
}

View File

@ -0,0 +1,93 @@
import {
Field,
HideField,
ObjectType,
registerEnumType,
} from '@nestjs/graphql';
import {
Authorize,
CursorConnection,
FilterableField,
IDField,
QueryOptions,
} from '@ptc-org/nestjs-query-graphql';
import {
IsBoolean,
IsDateString,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
import { IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
registerEnumType(IndexType, {
name: 'IndexType',
description: 'Type of the index',
});
@ObjectType('index')
@Authorize({
authorize: (context: any) => ({
workspaceId: { eq: context?.req?.workspace?.id },
}),
})
@QueryOptions({
defaultResultSize: 10,
disableSort: true,
maxResultsSize: 1000,
})
@CursorConnection('objectMetadata', () => ObjectMetadataDTO)
@CursorConnection('indexFieldMetadatas', () => IndexFieldMetadataDTO)
export class IndexMetadataDTO {
@IsUUID()
@IsNotEmpty()
@IDField(() => UUIDScalarType)
id: string;
@IsString()
@IsNotEmpty()
@Field()
@IsValidMetadataName()
name: string;
@IsBoolean()
@IsOptional()
@FilterableField({ nullable: true })
isCustom?: boolean;
@IsBoolean()
@IsNotEmpty()
@Field()
isUnique: boolean;
@IsString()
@IsOptional()
@Field({ nullable: true })
indexWhereClause?: string;
@IsEnum(IndexType)
@IsNotEmpty()
@Field(() => IndexType)
indexType: IndexType;
objectMetadataId: string;
@IsDateString()
@Field()
createdAt: Date;
@IsDateString()
@Field()
updatedAt: Date;
@HideField()
workspaceId: string;
}

View File

@ -23,6 +23,12 @@ export class IndexMetadataEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@Column({ nullable: false })
name: string;
@ -32,7 +38,7 @@ export class IndexMetadataEntity {
@Column({ nullable: false, type: 'uuid' })
objectMetadataId: string;
@ManyToOne(() => ObjectMetadataEntity, (object) => object.indexes, {
@ManyToOne(() => ObjectMetadataEntity, (object) => object.indexMetadatas, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -48,15 +54,15 @@ export class IndexMetadataEntity {
)
indexFieldMetadatas: Relation<IndexFieldMetadataEntity[]>;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@Column({ default: false })
isCustom: boolean;
@Column({ nullable: false, default: false })
isUnique: boolean;
@Column({ type: 'text', nullable: true })
indexWhereClause: string | null;
@Column({
type: 'enum',
enum: IndexType,

View File

@ -1,14 +1,50 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SortDirection } from '@ptc-org/nestjs-query-core';
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
import { ObjectMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
@Module({
imports: [
TypeOrmModule.forFeature([IndexMetadataEntity], 'metadata'),
WorkspaceMigrationModule,
NestjsQueryGraphQLModule.forFeature({
imports: [
NestjsQueryTypeOrmModule.forFeature(
[IndexMetadataEntity, IndexFieldMetadataEntity],
'metadata',
),
WorkspaceMigrationModule,
],
services: [IndexMetadataService],
resolvers: [
{
EntityClass: IndexMetadataEntity,
DTOClass: IndexMetadataDTO,
read: {
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
many: {
name: 'indexMetadatas', //TODO: check + singular
},
},
create: {
disabled: true,
},
update: { disabled: true },
delete: { disabled: true },
guards: [WorkspaceAuthGuard],
interceptors: [ObjectMetadataGraphqlApiExceptionInterceptor],
},
],
}),
],
providers: [IndexMetadataService],
exports: [IndexMetadataService],

View File

@ -32,8 +32,10 @@ export class IndexMetadataService {
workspaceId: string,
objectMetadata: ObjectMetadataEntity,
fieldMetadataToIndex: Partial<FieldMetadataEntity>[],
isUnique: boolean,
isCustom: boolean,
indexType?: IndexType,
indexWhereClause?: string,
) {
const tableName = computeObjectTargetTable(objectMetadata);
@ -82,6 +84,8 @@ export class IndexMetadataService {
action: WorkspaceMigrationIndexActionType.CREATE,
columns: columnNames,
name: indexName,
isUnique,
where: indexWhereClause,
type: indexType,
},
],

View File

@ -0,0 +1,11 @@
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface';
export interface IndexFieldMetadataInterface {
id: string;
indexMetadataId: string;
fieldMetadataId: string;
fieldMetadata: FieldMetadataInterface;
indexMetadata: IndexMetadataInterface;
order: number;
}

View File

@ -0,0 +1,7 @@
import { IndexFieldMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface';
export interface IndexMetadataInterface {
name: string;
isUnique: boolean;
indexFieldMetadatas: IndexFieldMetadataInterface[];
}

View File

@ -11,6 +11,7 @@ import {
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
import { BeforeDeleteOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-delete-one-object.hook';
@ObjectType('object')
@ -26,6 +27,7 @@ import { BeforeDeleteOneObject } from 'src/engine/metadata-modules/object-metada
})
@BeforeDeleteOne(BeforeDeleteOneObject)
@CursorConnection('fields', () => FieldMetadataDTO)
@CursorConnection('indexMetadatas', () => IndexMetadataDTO)
export class ObjectMetadataDTO {
@IDField(() => UUIDScalarType)
id: string;

View File

@ -86,7 +86,7 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
@OneToMany(() => IndexMetadataEntity, (index) => index.objectMetadata, {
cascade: true,
})
indexes: Relation<IndexMetadataEntity[]>;
indexMetadatas: Relation<IndexMetadataEntity[]>;
@OneToMany(
() => RelationMetadataEntity,

View File

@ -673,6 +673,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
createdObjectMetadata,
[searchVectorFieldMetadata],
false,
false,
IndexType.GIN,
);
}

View File

@ -154,6 +154,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
toObjectMetadata,
[foreignKeyFieldMetadata, deletedFieldMetadata],
false,
false,
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(

View File

@ -77,6 +77,8 @@ export class WorkspaceMetadataCacheService {
'fields',
'fields.fromRelationMetadata',
'fields.toRelationMetadata',
'indexMetadatas',
'indexMetadatas.indexFieldMetadatas',
],
});

View File

@ -49,6 +49,7 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF
columnType: fieldMetadataTypeToColumnType(fieldMetadata.type),
isArray: fieldMetadata.type === FieldMetadataType.ARRAY,
isNullable: fieldMetadata.isNullable ?? true,
isUnique: fieldMetadata.isUnique ?? false,
defaultValue: serializedDefaultValue,
},
];
@ -83,6 +84,7 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF
columnType: fieldMetadataTypeToColumnType(currentFieldMetadata.type),
isArray: currentFieldMetadata.type === FieldMetadataType.ARRAY,
isNullable: currentFieldMetadata.isNullable ?? true,
isUnique: currentFieldMetadata.isUnique ?? false,
defaultValue: serializeDefaultValue(
currentFieldMetadata.defaultValue,
),
@ -92,6 +94,7 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF
columnType: fieldMetadataTypeToColumnType(alteredFieldMetadata.type),
isArray: alteredFieldMetadata.type === FieldMetadataType.ARRAY,
isNullable: alteredFieldMetadata.isNullable ?? true,
isUnique: alteredFieldMetadata.isUnique ?? false,
defaultValue: serializedDefaultValue,
},
},

View File

@ -69,6 +69,7 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co
columnType: fieldMetadataTypeToColumnType(property.type),
enum: enumOptions,
isNullable: fieldMetadata.isNullable || !property.isRequired,
isUnique: fieldMetadata.isUnique,
defaultValue: serializedDefaultValue,
isArray:
property.type === FieldMetadataType.MULTI_SELECT || property.isArray,
@ -168,6 +169,7 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co
: undefined,
isNullable:
currentFieldMetadata.isNullable || !currentProperty.isRequired,
isUnique: currentFieldMetadata.isUnique ?? false,
defaultValue: serializeDefaultValue(
currentFieldMetadata.defaultValue?.[currentProperty.name],
),
@ -181,6 +183,7 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co
enum: enumOptions,
isNullable:
alteredFieldMetadata.isNullable || !alteredProperty.isRequired,
isUnique: alteredFieldMetadata.isUnique ?? false,
defaultValue: serializedDefaultValue,
isArray:
alteredProperty.type === FieldMetadataType.MULTI_SELECT ||

View File

@ -46,6 +46,7 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie
enum: enumOptions,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: fieldMetadata.isNullable ?? true,
isUnique: fieldMetadata.isUnique ?? false,
defaultValue: serializedDefaultValue,
},
];
@ -103,6 +104,7 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie
: undefined,
isArray: currentFieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: currentFieldMetadata.isNullable ?? true,
isUnique: currentFieldMetadata.isUnique ?? false,
defaultValue: serializeDefaultValue(
currentFieldMetadata.defaultValue,
),
@ -113,6 +115,7 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie
enum: enumOptions,
isArray: alteredFieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: alteredFieldMetadata.isNullable ?? true,
isUnique: alteredFieldMetadata.isUnique ?? false,
defaultValue: serializedDefaultValue,
},
},

View File

@ -32,6 +32,7 @@ export class TsVectorColumnActionFactory extends ColumnActionAbstractFactory<TsV
columnName: computeColumnName(fieldMetadata),
columnType: fieldMetadataTypeToColumnType(fieldMetadata.type),
isNullable: fieldMetadata.isNullable ?? true,
isUnique: fieldMetadata.isUnique ?? false,
defaultValue: undefined,
generatedType: fieldMetadata.generatedType,
asExpression: fieldMetadata.asExpression,

View File

@ -30,6 +30,7 @@ export interface WorkspaceMigrationColumnDefinition {
enum?: WorkspaceMigrationEnum[];
isArray?: boolean;
isNullable: boolean;
isUnique?: boolean;
defaultValue: any;
generatedType?: 'STORED' | 'VIRTUAL';
asExpression?: string;
@ -39,6 +40,8 @@ export interface WorkspaceMigrationIndexAction {
action: WorkspaceMigrationIndexActionType;
name: string;
columns: string[];
isUnique: boolean;
where?: string | null;
type?: IndexType;
}