feat(utils): DML generates foreign key indexes by default (#7855)
**what:** - adds default indexes on foreign keys when belongs to relationship is present RESOLVES CORE-2404
This commit is contained in:
@@ -106,12 +106,12 @@ export interface EntityConstructor<Props> extends Function {
|
||||
*/
|
||||
export type InferForeignKeys<T> = T extends IDmlEntity<infer Schema>
|
||||
? {
|
||||
[K in keyof Schema as Schema[K] extends RelationshipType<any>
|
||||
? Schema[K]["type"] extends "belongsTo"
|
||||
[K in keyof Schema as Schema[K] extends { type: infer Type }
|
||||
? Type extends RelationshipTypes
|
||||
? `${K & string}_id`
|
||||
: K
|
||||
: K]: Schema[K] extends RelationshipType<infer R>
|
||||
? Schema[K]["type"] extends "belongsTo"
|
||||
: K]: Schema[K] extends { type: infer Type }
|
||||
? Type extends RelationshipTypes
|
||||
? string
|
||||
: Schema[K]
|
||||
: Schema[K]
|
||||
@@ -174,8 +174,10 @@ export type InferIndexableProperties<T> = keyof (T extends IDmlEntity<
|
||||
infer Schema
|
||||
>
|
||||
? {
|
||||
[K in keyof Schema as Schema[K] extends RelationshipType<any>
|
||||
? never
|
||||
[K in keyof Schema as Schema[K] extends { type: infer Type }
|
||||
? Type extends RelationshipTypes
|
||||
? never
|
||||
: K
|
||||
: K]: string
|
||||
} & InferForeignKeys<T>
|
||||
: never)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { MetadataStorage } from "@mikro-orm/core"
|
||||
|
||||
export function ForeignKey() {
|
||||
return function (target, propertyName) {
|
||||
const meta = MetadataStorage.getMetadataFromDecorator(target.constructor)
|
||||
const prop = meta.properties[propertyName] || {}
|
||||
prop["isForeignKey"] = true
|
||||
meta.properties[prop.name] = prop
|
||||
}
|
||||
}
|
||||
@@ -2482,6 +2482,7 @@ describe("Entity builder", () => {
|
||||
reference: "scalar",
|
||||
setter: false,
|
||||
type: "string",
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -2598,6 +2599,7 @@ describe("Entity builder", () => {
|
||||
nullable: false,
|
||||
onDelete: undefined,
|
||||
reference: "m:1",
|
||||
isForeignKey: true,
|
||||
},
|
||||
...defaultColumnMetadata,
|
||||
})
|
||||
@@ -2623,6 +2625,11 @@ describe("Entity builder", () => {
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_unique-name" ON "user" (organization, account, group_id) WHERE deleted_at IS NULL',
|
||||
name: "IDX_unique-name",
|
||||
},
|
||||
{
|
||||
expression:
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
||||
name: "IDX_user_group_id",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@@ -2699,6 +2706,11 @@ describe("Entity builder", () => {
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_user_account_group_id" ON "user" (account, group_id) WHERE is_owner IS TRUE AND deleted_at IS NULL',
|
||||
name: "IDX_user_account_group_id",
|
||||
},
|
||||
{
|
||||
expression:
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_user_group_id" ON "user" (group_id) WHERE deleted_at IS NULL',
|
||||
name: "IDX_user_group_id",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@@ -3121,6 +3133,7 @@ describe("Entity builder", () => {
|
||||
nullable: false,
|
||||
onDelete: "cascade",
|
||||
reference: "m:1",
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -3307,6 +3320,7 @@ describe("Entity builder", () => {
|
||||
name: "user_id",
|
||||
getter: false,
|
||||
setter: false,
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -3486,6 +3500,7 @@ describe("Entity builder", () => {
|
||||
name: "user_id",
|
||||
getter: false,
|
||||
setter: false,
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -3664,6 +3679,7 @@ describe("Entity builder", () => {
|
||||
mapToPk: true,
|
||||
fieldName: "user_id",
|
||||
nullable: false,
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -3842,6 +3858,7 @@ describe("Entity builder", () => {
|
||||
mapToPk: true,
|
||||
fieldName: "user_id",
|
||||
nullable: true,
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -4087,6 +4104,7 @@ describe("Entity builder", () => {
|
||||
name: "user_id",
|
||||
getter: false,
|
||||
setter: false,
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -4274,6 +4292,7 @@ describe("Entity builder", () => {
|
||||
name: "user_id",
|
||||
getter: false,
|
||||
setter: false,
|
||||
isForeignKey: true,
|
||||
},
|
||||
created_at: {
|
||||
reference: "scalar",
|
||||
@@ -5644,6 +5663,7 @@ describe("Entity builder", () => {
|
||||
mapToPk: true,
|
||||
fieldName: "user_id",
|
||||
nullable: false,
|
||||
isForeignKey: true,
|
||||
},
|
||||
user: {
|
||||
reference: "scalar",
|
||||
@@ -5662,6 +5682,7 @@ describe("Entity builder", () => {
|
||||
mapToPk: true,
|
||||
fieldName: "team_id",
|
||||
nullable: false,
|
||||
isForeignKey: true,
|
||||
},
|
||||
team: {
|
||||
reference: "scalar",
|
||||
|
||||
@@ -122,6 +122,9 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds indexes to be created at during model creation on the DML entity.
|
||||
*/
|
||||
indexes(indexes: EntityIndex<Schema, string | QueryCondition>[]) {
|
||||
for (const index of indexes) {
|
||||
index.where = transformIndexWhere(index)
|
||||
|
||||
@@ -2,14 +2,11 @@ import type { EntityConstructor, Infer } from "@medusajs/types"
|
||||
import { Entity, Filter } from "@mikro-orm/core"
|
||||
import { mikroOrmSoftDeletableFilterOptions } from "../../dal"
|
||||
import { DmlEntity } from "../entity"
|
||||
import {
|
||||
applyEntityIndexes,
|
||||
applyIndexes,
|
||||
} from "./entity-builder/apply-indexes"
|
||||
import { applySearchable } from "./entity-builder/apply-searchable"
|
||||
import { defineProperty } from "./entity-builder/define-property"
|
||||
import { defineRelationship } from "./entity-builder/define-relationship"
|
||||
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
||||
import { applyEntityIndexes, applyIndexes } from "./mikro-orm/apply-indexes"
|
||||
|
||||
/**
|
||||
* Factory function to create the mikro orm entity builder. The return
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import {
|
||||
EntityConstructor,
|
||||
EntityIndex,
|
||||
PropertyMetadata,
|
||||
} from "@medusajs/types"
|
||||
import { createPsqlIndexStatementHelper } from "../../../common"
|
||||
import { validateIndexFields } from "../mikro-orm/build-indexes"
|
||||
|
||||
/**
|
||||
* Prepares indexes for a given field
|
||||
*/
|
||||
export function applyIndexes(
|
||||
MikroORMEntity: EntityConstructor<any>,
|
||||
tableName: string,
|
||||
field: PropertyMetadata
|
||||
) {
|
||||
field.indexes.forEach((index) => {
|
||||
const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({
|
||||
tableName,
|
||||
columns: [field.fieldName],
|
||||
unique: index.type === "unique",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
RelationshipMetadata,
|
||||
RelationshipType,
|
||||
} from "@medusajs/types"
|
||||
import { DmlEntity } from "../../entity"
|
||||
import { parseEntityName } from "./parse-entity-name"
|
||||
import {
|
||||
BeforeCreate,
|
||||
ManyToMany,
|
||||
@@ -17,9 +15,12 @@ import {
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { camelToSnakeCase, pluralize } from "../../../common"
|
||||
import { ForeignKey } from "../../../dal/mikro-orm/decorators/foreign-key"
|
||||
import { DmlEntity } from "../../entity"
|
||||
import { HasMany } from "../../relations/has-many"
|
||||
import { HasOne } from "../../relations/has-one"
|
||||
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
||||
import { parseEntityName } from "./parse-entity-name"
|
||||
|
||||
type Context = {
|
||||
MANY_TO_MANY_TRACKED_REALTIONS: Record<string, boolean>
|
||||
@@ -138,10 +139,11 @@ export function defineBelongsToRelationship(
|
||||
entity: relatedModelName,
|
||||
columnType: "text",
|
||||
mapToPk: true,
|
||||
fieldName: camelToSnakeCase(`${relationship.name}Id`),
|
||||
fieldName: foreignKeyName,
|
||||
nullable: relationship.nullable,
|
||||
onDelete: shouldCascade ? "cascade" : undefined,
|
||||
})(MikroORMEntity.prototype, camelToSnakeCase(`${relationship.name}Id`))
|
||||
})(MikroORMEntity.prototype, foreignKeyName)
|
||||
ForeignKey()(MikroORMEntity.prototype, foreignKeyName)
|
||||
|
||||
if (DmlManyToMany.isManyToMany(otherSideRelation)) {
|
||||
Property({
|
||||
@@ -190,6 +192,7 @@ export function defineBelongsToRelationship(
|
||||
columnType: "text",
|
||||
nullable: relationship.nullable,
|
||||
})(MikroORMEntity.prototype, foreignKeyName)
|
||||
ForeignKey()(MikroORMEntity.prototype, foreignKeyName)
|
||||
|
||||
applyForeignKeyAssignationHooks(foreignKeyName)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
EntityConstructor,
|
||||
EntityIndex,
|
||||
PropertyMetadata,
|
||||
} from "@medusajs/types"
|
||||
import { MetadataStorage } from "@mikro-orm/core"
|
||||
import { createPsqlIndexStatementHelper } from "../../../common"
|
||||
import { validateIndexFields } from "../mikro-orm/build-indexes"
|
||||
|
||||
/**
|
||||
* Creates indexes for a given field
|
||||
*/
|
||||
export function applyIndexes(
|
||||
MikroORMEntity: EntityConstructor<any>,
|
||||
tableName: string,
|
||||
field: PropertyMetadata
|
||||
) {
|
||||
field.indexes.forEach((index) => {
|
||||
const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({
|
||||
tableName,
|
||||
columns: [field.fieldName],
|
||||
unique: index.type === "unique",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
providerEntityIdIndexStatement.MikroORMIndex()(MikroORMEntity)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates indexes for a MikroORM entity
|
||||
*
|
||||
* Default Indexes:
|
||||
* - Foreign key indexes will be applied to all manyToOne relationships.
|
||||
*/
|
||||
export function applyEntityIndexes(
|
||||
MikroORMEntity: EntityConstructor<any>,
|
||||
tableName: string,
|
||||
entityIndexes: EntityIndex[]
|
||||
) {
|
||||
const foreignKeyIndexes = applyForeignKeyIndexes(MikroORMEntity)
|
||||
const indexes = [...entityIndexes, ...foreignKeyIndexes]
|
||||
|
||||
indexes.forEach((index) => {
|
||||
validateIndexFields(MikroORMEntity, index)
|
||||
|
||||
const entityIndexStatement = createPsqlIndexStatementHelper({
|
||||
tableName,
|
||||
name: index.name,
|
||||
columns: index.on as string[],
|
||||
unique: index.unique,
|
||||
where: index.where,
|
||||
})
|
||||
|
||||
entityIndexStatement.MikroORMIndex()(MikroORMEntity)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
When a "oneToMany" relationship is found on the MikroORM entity, we create an index by default
|
||||
on the foreign key property.
|
||||
*/
|
||||
function applyForeignKeyIndexes(MikroORMEntity: EntityConstructor<any>) {
|
||||
const foreignKeyIndexes: EntityIndex[] = []
|
||||
|
||||
for (const foreignKey of getEntityForeignKeys(MikroORMEntity)) {
|
||||
foreignKeyIndexes.push({
|
||||
on: [foreignKey],
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
}
|
||||
|
||||
return foreignKeyIndexes
|
||||
}
|
||||
|
||||
function getEntityForeignKeys(MikroORMEntity: EntityConstructor<any>) {
|
||||
const properties =
|
||||
MetadataStorage.getMetadataFromDecorator(MikroORMEntity).properties
|
||||
|
||||
return Object.keys(properties).filter(
|
||||
(propertyName) => properties[propertyName].isForeignKey
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user