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:
1 parent
6e65158af3
commit
91faa5ffdb
8 files changed
+132
-63
No files matched your search
@@ -106,12 +106,12 @@ export interface EntityConstructor<Props> extends Function {
|
|||||||
*/
|
*/
|
||||||
export type InferForeignKeys<T> = T extends IDmlEntity<infer Schema>
|
export type InferForeignKeys<T> = T extends IDmlEntity<infer Schema>
|
||||||
? {
|
? {
|
||||||
[K in keyof Schema as Schema[K] extends RelationshipType<any>
|
[K in keyof Schema as Schema[K] extends { type: infer Type }
|
||||||
? Schema[K]["type"] extends "belongsTo"
|
? Type extends RelationshipTypes
|
||||||
? `${K & string}_id`
|
? `${K & string}_id`
|
||||||
: K
|
: K
|
||||||
: K]: Schema[K] extends RelationshipType<infer R>
|
: K]: Schema[K] extends { type: infer Type }
|
||||||
? Schema[K]["type"] extends "belongsTo"
|
? Type extends RelationshipTypes
|
||||||
? string
|
? string
|
||||||
: Schema[K]
|
: Schema[K]
|
||||||
: Schema[K]
|
: Schema[K]
|
||||||
@@ -174,8 +174,10 @@ export type InferIndexableProperties<T> = keyof (T extends IDmlEntity<
|
|||||||
infer Schema
|
infer Schema
|
||||||
>
|
>
|
||||||
? {
|
? {
|
||||||
[K in keyof Schema as Schema[K] extends RelationshipType<any>
|
[K in keyof Schema as Schema[K] extends { type: infer Type }
|
||||||
|
? Type extends RelationshipTypes
|
||||||
? never
|
? never
|
||||||
|
: K
|
||||||
: K]: string
|
: K]: string
|
||||||
} & InferForeignKeys<T>
|
} & InferForeignKeys<T>
|
||||||
: never)
|
: 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",
|
reference: "scalar",
|
||||||
setter: false,
|
setter: false,
|
||||||
type: "string",
|
type: "string",
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -2598,6 +2599,7 @@ describe("Entity builder", () => {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: undefined,
|
onDelete: undefined,
|
||||||
reference: "m:1",
|
reference: "m:1",
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
...defaultColumnMetadata,
|
...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',
|
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_unique-name" ON "user" (organization, account, group_id) WHERE deleted_at IS NULL',
|
||||||
name: "IDX_unique-name",
|
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',
|
'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",
|
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,
|
nullable: false,
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
reference: "m:1",
|
reference: "m:1",
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -3307,6 +3320,7 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -3486,6 +3500,7 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -3664,6 +3679,7 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "user_id",
|
fieldName: "user_id",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -3842,6 +3858,7 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "user_id",
|
fieldName: "user_id",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -4087,6 +4104,7 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -4274,6 +4292,7 @@ describe("Entity builder", () => {
|
|||||||
name: "user_id",
|
name: "user_id",
|
||||||
getter: false,
|
getter: false,
|
||||||
setter: false,
|
setter: false,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -5644,6 +5663,7 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "user_id",
|
fieldName: "user_id",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -5662,6 +5682,7 @@ describe("Entity builder", () => {
|
|||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: "team_id",
|
fieldName: "team_id",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
isForeignKey: true,
|
||||||
},
|
},
|
||||||
team: {
|
team: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds indexes to be created at during model creation on the DML entity.
|
||||||
|
*/
|
||||||
indexes(indexes: EntityIndex<Schema, string | QueryCondition>[]) {
|
indexes(indexes: EntityIndex<Schema, string | QueryCondition>[]) {
|
||||||
for (const index of indexes) {
|
for (const index of indexes) {
|
||||||
index.where = transformIndexWhere(index)
|
index.where = transformIndexWhere(index)
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ 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 {
|
|
||||||
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 { 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"
|
||||||
|
import { applyEntityIndexes, applyIndexes } from "./mikro-orm/apply-indexes"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create the mikro orm entity builder. The return
|
* 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,
|
RelationshipMetadata,
|
||||||
RelationshipType,
|
RelationshipType,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { DmlEntity } from "../../entity"
|
|
||||||
import { parseEntityName } from "./parse-entity-name"
|
|
||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
@@ -17,9 +15,12 @@ import {
|
|||||||
Property,
|
Property,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import { camelToSnakeCase, pluralize } from "../../../common"
|
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 { HasMany } from "../../relations/has-many"
|
||||||
import { HasOne } from "../../relations/has-one"
|
import { HasOne } from "../../relations/has-one"
|
||||||
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
||||||
|
import { parseEntityName } from "./parse-entity-name"
|
||||||
|
|
||||||
type Context = {
|
type Context = {
|
||||||
MANY_TO_MANY_TRACKED_REALTIONS: Record<string, boolean>
|
MANY_TO_MANY_TRACKED_REALTIONS: Record<string, boolean>
|
||||||
@@ -138,10 +139,11 @@ export function defineBelongsToRelationship(
|
|||||||
entity: relatedModelName,
|
entity: relatedModelName,
|
||||||
columnType: "text",
|
columnType: "text",
|
||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
fieldName: camelToSnakeCase(`${relationship.name}Id`),
|
fieldName: foreignKeyName,
|
||||||
nullable: relationship.nullable,
|
nullable: relationship.nullable,
|
||||||
onDelete: shouldCascade ? "cascade" : undefined,
|
onDelete: shouldCascade ? "cascade" : undefined,
|
||||||
})(MikroORMEntity.prototype, camelToSnakeCase(`${relationship.name}Id`))
|
})(MikroORMEntity.prototype, foreignKeyName)
|
||||||
|
ForeignKey()(MikroORMEntity.prototype, foreignKeyName)
|
||||||
|
|
||||||
if (DmlManyToMany.isManyToMany(otherSideRelation)) {
|
if (DmlManyToMany.isManyToMany(otherSideRelation)) {
|
||||||
Property({
|
Property({
|
||||||
@@ -190,6 +192,7 @@ export function defineBelongsToRelationship(
|
|||||||
columnType: "text",
|
columnType: "text",
|
||||||
nullable: relationship.nullable,
|
nullable: relationship.nullable,
|
||||||
})(MikroORMEntity.prototype, foreignKeyName)
|
})(MikroORMEntity.prototype, foreignKeyName)
|
||||||
|
ForeignKey()(MikroORMEntity.prototype, foreignKeyName)
|
||||||
|
|
||||||
applyForeignKeyAssignationHooks(foreignKeyName)
|
applyForeignKeyAssignationHooks(foreignKeyName)
|
||||||
return
|
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