feat(utils,types): DML can apply composite indexes (#7842)
**what:**
- DML can apply composite indexes
- Where clause is currently a string, QB version will come as a follow up
```
model.define("user", {
email: model.text(),
account: model.text(),
}).indexes([
{
name: "IDX-unique-name",
unique: true,
on: ["email", "account"],
where: "email is NOT NULL",
},
])
```
RESOLVES CORE-2391
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
export const IsDmlEntity = Symbol.for("isDmlEntity")
|
export const IsDmlEntity = Symbol.for("isDmlEntity")
|
||||||
|
|
||||||
export interface IDmlEntity<
|
export type DMLSchema = Record<
|
||||||
Schema extends Record<string, PropertyType<any> | RelationshipType<any>>
|
string,
|
||||||
> {
|
PropertyType<any> | RelationshipType<any>
|
||||||
|
>
|
||||||
|
|
||||||
|
export interface IDmlEntity<Schema extends DMLSchema> {
|
||||||
[IsDmlEntity]: true
|
[IsDmlEntity]: true
|
||||||
schema: Schema
|
schema: Schema
|
||||||
}
|
}
|
||||||
@@ -163,3 +166,23 @@ export type InferTypeOf<T extends IDmlEntity<any>> = InstanceType<Infer<T>>
|
|||||||
export type InferEntityType<T extends any> = T extends IDmlEntity<any>
|
export type InferEntityType<T extends any> = T extends IDmlEntity<any>
|
||||||
? InferTypeOf<T>
|
? InferTypeOf<T>
|
||||||
: T
|
: T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer all indexable properties from a DML entity including inferred foreign keys and excluding relationship
|
||||||
|
*/
|
||||||
|
export type InferIndexableProperties<T> = keyof (T extends IDmlEntity<
|
||||||
|
infer Schema
|
||||||
|
>
|
||||||
|
? {
|
||||||
|
[K in keyof Schema as Schema[K] extends RelationshipType<any>
|
||||||
|
? never
|
||||||
|
: K]: string
|
||||||
|
} & InferForeignKeys<T>
|
||||||
|
: never)
|
||||||
|
|
||||||
|
export type EntityIndex<TSchema extends DMLSchema = DMLSchema> = {
|
||||||
|
name?: string
|
||||||
|
unique?: boolean
|
||||||
|
on: InferIndexableProperties<IDmlEntity<TSchema>>[]
|
||||||
|
where?: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,41 @@ describe("Entity builder", () => {
|
|||||||
MetadataStorage.clear()
|
MetadataStorage.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const defaultColumnMetadata = {
|
||||||
|
created_at: {
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
getter: false,
|
||||||
|
name: "created_at",
|
||||||
|
nullable: false,
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
reference: "scalar",
|
||||||
|
setter: false,
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
columnType: "timestamptz",
|
||||||
|
getter: false,
|
||||||
|
name: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: false,
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
getter: false,
|
||||||
|
name: "updated_at",
|
||||||
|
nullable: false,
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
reference: "scalar",
|
||||||
|
setter: false,
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
describe("Entity builder | properties", () => {
|
describe("Entity builder | properties", () => {
|
||||||
test("should identify a DML entity correctly", () => {
|
test("should identify a DML entity correctly", () => {
|
||||||
const user = model.define("user", {
|
const user = model.define("user", {
|
||||||
@@ -2484,6 +2519,145 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Entity builder | indexes", () => {
|
||||||
|
test("should define indexes for an entity", () => {
|
||||||
|
const group = model.define("group", {
|
||||||
|
id: model.number(),
|
||||||
|
name: model.text(),
|
||||||
|
users: model.hasMany(() => user),
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = model
|
||||||
|
.define("user", {
|
||||||
|
email: model.text(),
|
||||||
|
account: model.text(),
|
||||||
|
organization: model.text(),
|
||||||
|
group: model.belongsTo(() => group, { mappedBy: "users" }),
|
||||||
|
})
|
||||||
|
.indexes([
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
on: ["email", "account"],
|
||||||
|
},
|
||||||
|
{ on: ["email", "account"] },
|
||||||
|
{
|
||||||
|
on: ["organization", "account"],
|
||||||
|
where: "email IS NOT NULL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IDX_unique-name",
|
||||||
|
unique: true,
|
||||||
|
on: ["organization", "account", "group_id"],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const User = toMikroORMEntity(user)
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
email: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "email",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "account",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
columnType: "text",
|
||||||
|
getter: false,
|
||||||
|
name: "organization",
|
||||||
|
nullable: false,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: false,
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
entity: "Group",
|
||||||
|
name: "group",
|
||||||
|
nullable: false,
|
||||||
|
persist: false,
|
||||||
|
reference: "m:1",
|
||||||
|
},
|
||||||
|
group_id: {
|
||||||
|
columnType: "text",
|
||||||
|
entity: "Group",
|
||||||
|
fieldName: "group_id",
|
||||||
|
mapToPk: true,
|
||||||
|
name: "group_id",
|
||||||
|
nullable: false,
|
||||||
|
onDelete: undefined,
|
||||||
|
reference: "m:1",
|
||||||
|
},
|
||||||
|
...defaultColumnMetadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.indexes).toEqual([
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_account_unique" ON "user" (email, account) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_email_account_unique",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_email_account" ON "user" (email, account) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_user_email_account",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_user_organization_account" ON "user" (organization, account) WHERE email IS NOT NULL AND deleted_at IS NULL',
|
||||||
|
name: "IDX_user_organization_account",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression:
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_unique-name" ON "user" (organization, account, group_id) WHERE deleted_at IS NULL',
|
||||||
|
name: "IDX_unique-name",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should throw an error if field is unknown for an index", () => {
|
||||||
|
try {
|
||||||
|
const group = model.define("group", {
|
||||||
|
id: model.number(),
|
||||||
|
name: model.text(),
|
||||||
|
users: model.hasMany(() => user),
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = model
|
||||||
|
.define("user", {
|
||||||
|
email: model.text(),
|
||||||
|
account: model.text(),
|
||||||
|
organization: model.text(),
|
||||||
|
group: model.belongsTo(() => group, { mappedBy: "users" }),
|
||||||
|
})
|
||||||
|
.indexes([
|
||||||
|
{
|
||||||
|
on: ["email", "account", "doesnotexist", "anotherdoesnotexist"],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
toMikroORMEntity(user)
|
||||||
|
|
||||||
|
throw "should not reach"
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toEqual(
|
||||||
|
"Fields (doesnotexist, anotherdoesnotexist) are not found when applying indexes from DML entity"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("Entity builder | hasMany", () => {
|
describe("Entity builder | hasMany", () => {
|
||||||
test("define hasMany relationship", () => {
|
test("define hasMany relationship", () => {
|
||||||
const email = model.define("email", {
|
const email = model.define("email", {
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import type {
|
import type { DMLSchema, RelationshipOptions } from "@medusajs/types"
|
||||||
PropertyType,
|
|
||||||
RelationshipOptions,
|
|
||||||
RelationshipType,
|
|
||||||
} from "@medusajs/types"
|
|
||||||
import { DmlEntity } from "./entity"
|
import { DmlEntity } from "./entity"
|
||||||
import { createBigNumberProperties } from "./helpers/entity-builder/create-big-number-properties"
|
import { createBigNumberProperties } from "./helpers/entity-builder/create-big-number-properties"
|
||||||
import { createDefaultProperties } from "./helpers/entity-builder/create-default-properties"
|
import { createDefaultProperties } from "./helpers/entity-builder/create-default-properties"
|
||||||
import { inferPrimaryKeyProperties } from "./helpers/entity-builder/infer-primary-key-properties"
|
import { inferPrimaryKeyProperties } from "./helpers/entity-builder/infer-primary-key-properties"
|
||||||
|
import { ArrayProperty } from "./properties/array"
|
||||||
import { BigNumberProperty } from "./properties/big-number"
|
import { BigNumberProperty } from "./properties/big-number"
|
||||||
import { BooleanProperty } from "./properties/boolean"
|
import { BooleanProperty } from "./properties/boolean"
|
||||||
import { DateTimeProperty } from "./properties/date-time"
|
import { DateTimeProperty } from "./properties/date-time"
|
||||||
@@ -19,18 +16,12 @@ import { BelongsTo } from "./relations/belongs-to"
|
|||||||
import { HasMany } from "./relations/has-many"
|
import { HasMany } from "./relations/has-many"
|
||||||
import { HasOne } from "./relations/has-one"
|
import { HasOne } from "./relations/has-one"
|
||||||
import { ManyToMany } from "./relations/many-to-many"
|
import { ManyToMany } from "./relations/many-to-many"
|
||||||
import { ArrayProperty } from "./properties/array"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The implicit properties added by EntityBuilder in every schema
|
* The implicit properties added by EntityBuilder in every schema
|
||||||
*/
|
*/
|
||||||
const IMPLICIT_PROPERTIES = ["created_at", "updated_at", "deleted_at"]
|
const IMPLICIT_PROPERTIES = ["created_at", "updated_at", "deleted_at"]
|
||||||
|
|
||||||
export type DMLSchema = Record<
|
|
||||||
string,
|
|
||||||
PropertyType<any> | RelationshipType<any>
|
|
||||||
>
|
|
||||||
|
|
||||||
type DefineOptions = string | { name?: string; tableName: string }
|
type DefineOptions = string | { name?: string; tableName: string }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
|
DMLSchema,
|
||||||
EntityCascades,
|
EntityCascades,
|
||||||
|
EntityIndex,
|
||||||
ExtractEntityRelations,
|
ExtractEntityRelations,
|
||||||
IDmlEntity,
|
IDmlEntity,
|
||||||
IsDmlEntity,
|
IsDmlEntity,
|
||||||
PropertyType,
|
|
||||||
RelationshipType,
|
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { DMLSchema } from "./entity-builder"
|
|
||||||
import { BelongsTo } from "./relations/belongs-to"
|
|
||||||
import { isObject, isString, toCamelCase } from "../common"
|
import { isObject, isString, toCamelCase } from "../common"
|
||||||
|
import { transformIndexWhere } from "./helpers/entity-builder/build-indexes"
|
||||||
|
import { BelongsTo } from "./relations/belongs-to"
|
||||||
|
|
||||||
type Config = string | { name?: string; tableName: string }
|
type Config = string | { name?: string; tableName: string }
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
|||||||
|
|
||||||
readonly #tableName: string
|
readonly #tableName: string
|
||||||
#cascades: EntityCascades<string[]> = {}
|
#cascades: EntityCascades<string[]> = {}
|
||||||
|
#indexes: EntityIndex<Schema>[] = []
|
||||||
|
|
||||||
constructor(nameOrConfig: Config, public schema: Schema) {
|
constructor(nameOrConfig: Config, public schema: Schema) {
|
||||||
const { name, tableName } = extractNameAndTableName(nameOrConfig)
|
const { name, tableName } = extractNameAndTableName(nameOrConfig)
|
||||||
@@ -78,16 +79,16 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
|||||||
parse(): {
|
parse(): {
|
||||||
name: string
|
name: string
|
||||||
tableName: string
|
tableName: string
|
||||||
schema: PropertyType<any> | RelationshipType<any>
|
schema: DMLSchema
|
||||||
cascades: EntityCascades<string[]>
|
cascades: EntityCascades<string[]>
|
||||||
|
indexes: EntityIndex<Schema>[]
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
tableName: this.#tableName,
|
tableName: this.#tableName,
|
||||||
schema: this.schema as unknown as
|
schema: this.schema,
|
||||||
| PropertyType<any>
|
|
||||||
| RelationshipType<any>,
|
|
||||||
cascades: this.#cascades,
|
cascades: this.#cascades,
|
||||||
|
indexes: this.#indexes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,4 +120,14 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
|||||||
this.#cascades = options
|
this.#cascades = options
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexes(indexes: EntityIndex<Schema>[]) {
|
||||||
|
for (const index of indexes) {
|
||||||
|
index.where = transformIndexWhere(index)
|
||||||
|
index.unique = index.unique ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#indexes = indexes
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import type { EntityConstructor, Infer } from "@medusajs/types"
|
|||||||
import { Entity, Filter } from "@mikro-orm/core"
|
import { Entity, Filter } from "@mikro-orm/core"
|
||||||
import { mikroOrmSoftDeletableFilterOptions } from "../../dal"
|
import { mikroOrmSoftDeletableFilterOptions } from "../../dal"
|
||||||
import { DmlEntity } from "../entity"
|
import { DmlEntity } from "../entity"
|
||||||
import { defineProperty } from "./entity-builder/define-property"
|
import {
|
||||||
import { applyIndexes } from "./entity-builder/apply-indexes"
|
applyEntityIndexes,
|
||||||
|
applyIndexes,
|
||||||
|
} from "./entity-builder/apply-indexes"
|
||||||
import { applySearchable } from "./entity-builder/apply-searchable"
|
import { applySearchable } from "./entity-builder/apply-searchable"
|
||||||
|
import { defineProperty } from "./entity-builder/define-property"
|
||||||
import { defineRelationship } from "./entity-builder/define-relationship"
|
import { defineRelationship } from "./entity-builder/define-relationship"
|
||||||
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ export function createMikrORMEntity() {
|
|||||||
return function createEntity<T extends DmlEntity<any>>(entity: T): Infer<T> {
|
return function createEntity<T extends DmlEntity<any>>(entity: T): Infer<T> {
|
||||||
class MikroORMEntity {}
|
class MikroORMEntity {}
|
||||||
|
|
||||||
const { schema, cascades } = entity.parse()
|
const { schema, cascades, indexes: entityIndexes = [] } = entity.parse()
|
||||||
const { modelName, tableName } = parseEntityName(entity)
|
const { modelName, tableName } = parseEntityName(entity)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +72,10 @@ export function createMikrORMEntity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (entityIndexes?.length) {
|
||||||
|
applyEntityIndexes(MikroORMEntity, tableName, entityIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converting class to a MikroORM entity
|
* Converting class to a MikroORM entity
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { EntityConstructor, PropertyMetadata } from "@medusajs/types"
|
import {
|
||||||
|
EntityConstructor,
|
||||||
|
EntityIndex,
|
||||||
|
PropertyMetadata,
|
||||||
|
} from "@medusajs/types"
|
||||||
import { createPsqlIndexStatementHelper } from "../../../common"
|
import { createPsqlIndexStatementHelper } from "../../../common"
|
||||||
|
import { validateIndexFields } from "../mikro-orm/build-indexes"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares indexes for a given field
|
* Prepares indexes for a given field
|
||||||
@@ -20,3 +25,26 @@ export function applyIndexes(
|
|||||||
providerEntityIdIndexStatement.MikroORMIndex()(MikroORMEntity)
|
providerEntityIdIndexStatement.MikroORMIndex()(MikroORMEntity)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares indexes for a given field
|
||||||
|
*/
|
||||||
|
export function applyEntityIndexes(
|
||||||
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
|
tableName: string,
|
||||||
|
indexes: EntityIndex[]
|
||||||
|
) {
|
||||||
|
indexes.forEach((index) => {
|
||||||
|
validateIndexFields(MikroORMEntity, index)
|
||||||
|
|
||||||
|
const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({
|
||||||
|
tableName,
|
||||||
|
name: index.name,
|
||||||
|
columns: index.on as string[],
|
||||||
|
unique: index.unique,
|
||||||
|
where: index.where,
|
||||||
|
})
|
||||||
|
|
||||||
|
providerEntityIdIndexStatement.MikroORMIndex()(MikroORMEntity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { DMLSchema, EntityIndex } from "@medusajs/types"
|
||||||
|
import { isPresent } from "../../../common"
|
||||||
|
|
||||||
|
/*
|
||||||
|
The DML provides an opinionated soft deletable entity as a part of every model
|
||||||
|
We assume that deleted_at would be scoped in indexes in all cases as an index without the scope
|
||||||
|
doesn't seem to be valid. If a case presents itself where one would like to remove the scope,
|
||||||
|
this will need to be updated to include that case.
|
||||||
|
*/
|
||||||
|
export function transformIndexWhere<TSchema extends DMLSchema>(
|
||||||
|
index: EntityIndex<TSchema>
|
||||||
|
) {
|
||||||
|
let where = index.where
|
||||||
|
const lowerCaseWhere = where?.toLowerCase()
|
||||||
|
const whereIncludesDeleteable =
|
||||||
|
lowerCaseWhere?.includes("deleted_at is null") ||
|
||||||
|
lowerCaseWhere?.includes("deleted_at is not null")
|
||||||
|
|
||||||
|
// If where scope does not include a deleted_at scope, we add a soft deletable scope to it
|
||||||
|
if (where && !whereIncludesDeleteable) {
|
||||||
|
where = where + ` AND deleted_at IS NULL`
|
||||||
|
}
|
||||||
|
|
||||||
|
// If where scope isn't present, we will set an opinionated where scope to the index
|
||||||
|
if (!isPresent(where)) {
|
||||||
|
where = "deleted_at IS NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
return where
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DMLSchema } from "../../entity-builder"
|
import { DMLSchema } from "@medusajs/types"
|
||||||
import { BigNumberProperty } from "../../properties/big-number"
|
import { BigNumberProperty } from "../../properties/big-number"
|
||||||
import { JSONProperty } from "../../properties/json"
|
import { JSONProperty } from "../../properties/json"
|
||||||
import { NullableModifier } from "../../properties/nullable"
|
import { NullableModifier } from "../../properties/nullable"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DMLSchema } from "../../entity-builder"
|
import { DMLSchema } from "@medusajs/types"
|
||||||
import { IdProperty } from "../../properties/id"
|
import { IdProperty } from "../../properties/id"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { DMLSchema, EntityConstructor, EntityIndex } from "@medusajs/types"
|
||||||
|
import { MetadataStorage } from "@mikro-orm/core"
|
||||||
|
import { arrayDifference } from "../../../common"
|
||||||
|
|
||||||
|
/*
|
||||||
|
The DML should strictly define indexes where the fields provided for the index are
|
||||||
|
already present in the schema definition. If not, we throw an error.
|
||||||
|
*/
|
||||||
|
export function validateIndexFields<TSchema extends DMLSchema>(
|
||||||
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
|
index: EntityIndex<TSchema>
|
||||||
|
) {
|
||||||
|
const fields: string[] = index.on
|
||||||
|
|
||||||
|
if (!fields?.length) {
|
||||||
|
throw new Error(
|
||||||
|
`"on" is a required property when applying indexes on a DML entity`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(MikroORMEntity)
|
||||||
|
const entityFields: string[] = Object.keys(metaData.properties)
|
||||||
|
const invalidFields = arrayDifference(fields, entityFields)
|
||||||
|
|
||||||
|
if (invalidFields.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Fields (${invalidFields.join(
|
||||||
|
", "
|
||||||
|
)}) are not found when applying indexes from DML entity`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user