fix: do not rely on model loading order to find an implicit owner (#10360)

This commit is contained in:
Harminder Virk
2024-11-29 16:02:34 +05:30
committed by GitHub
parent ef6dc53b87
commit 194361b95a
5 changed files with 131 additions and 89 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/utils": patch
---
fix: do not rely on model loading order to find an implicit owner

View File

@@ -5149,9 +5149,9 @@ describe("Entity builder", () => {
reference: "m:n",
name: "teams",
entity: "Team",
owner: true,
owner: false,
pivotTable: "team_users",
inversedBy: "users",
mappedBy: "users",
},
created_at: {
reference: "scalar",
@@ -5217,9 +5217,9 @@ describe("Entity builder", () => {
users: {
reference: "m:n",
name: "users",
mappedBy: "teams",
inversedBy: "teams",
entity: "User",
owner: false,
owner: true,
pivotTable: "team_users",
},
created_at: {
@@ -5330,9 +5330,9 @@ describe("Entity builder", () => {
reference: "m:n",
name: "teams",
entity: "Team",
owner: true,
owner: false,
pivotTable: "team_users",
inversedBy: "users",
mappedBy: "users",
},
created_at: {
reference: "scalar",
@@ -5399,8 +5399,8 @@ describe("Entity builder", () => {
reference: "m:n",
name: "users",
entity: "User",
owner: false,
mappedBy: "teams",
owner: true,
inversedBy: "teams",
pivotTable: "team_users",
},
created_at: {
@@ -5545,9 +5545,9 @@ describe("Entity builder", () => {
reference: "m:n",
name: "teams",
entity: "Team",
owner: true,
owner: false,
pivotTable: "team_users",
inversedBy: "users",
mappedBy: "users",
},
created_at: {
reference: "scalar",
@@ -5614,9 +5614,9 @@ describe("Entity builder", () => {
reference: "m:n",
name: "users",
entity: "User",
owner: false,
owner: true,
pivotTable: "team_users",
mappedBy: "teams",
inversedBy: "teams",
},
created_at: {
reference: "scalar",
@@ -6125,9 +6125,9 @@ describe("Entity builder", () => {
reference: "m:n",
name: "teams",
entity: "Team",
owner: true,
owner: false,
pivotTable: "platform.team_users",
inversedBy: "users",
mappedBy: "users",
},
created_at: {
reference: "scalar",
@@ -6195,8 +6195,8 @@ describe("Entity builder", () => {
reference: "m:n",
name: "users",
entity: "User",
owner: false,
mappedBy: "teams",
owner: true,
inversedBy: "teams",
pivotTable: "platform.team_users",
},
created_at: {
@@ -6719,10 +6719,10 @@ describe("Entity builder", () => {
},
})
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.properties).toEqual({
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
expect(teamMetaData.className).toEqual("Team")
expect(teamMetaData.path).toEqual("Team")
expect(teamMetaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
@@ -6733,23 +6733,23 @@ describe("Entity builder", () => {
getter: false,
setter: false,
},
username: {
name: {
reference: "scalar",
type: "string",
columnType: "text",
name: "username",
fieldName: "username",
name: "name",
fieldName: "name",
nullable: false,
getter: false,
setter: false,
},
teams: {
users: {
reference: "m:n",
name: "teams",
entity: "Team",
name: "users",
entity: "User",
owner: true,
inversedBy: "teams",
pivotEntity: "TeamUsers",
inversedBy: "users",
},
created_at: {
reference: "scalar",
@@ -6788,10 +6788,10 @@ describe("Entity builder", () => {
},
})
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
expect(teamMetaData.className).toEqual("Team")
expect(teamMetaData.path).toEqual("Team")
expect(teamMetaData.properties).toEqual({
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
@@ -6802,23 +6802,23 @@ describe("Entity builder", () => {
getter: false,
setter: false,
},
name: {
username: {
reference: "scalar",
type: "string",
columnType: "text",
name: "name",
fieldName: "name",
name: "username",
fieldName: "username",
nullable: false,
getter: false,
setter: false,
},
users: {
teams: {
reference: "m:n",
name: "users",
entity: "User",
name: "teams",
entity: "Team",
owner: false,
mappedBy: "teams",
pivotEntity: "TeamUsers",
mappedBy: "users",
},
created_at: {
reference: "scalar",
@@ -6857,5 +6857,28 @@ describe("Entity builder", () => {
},
})
})
test("throw error when both sides of relationship defines the pivot table", () => {
const team = model.define("team", {
id: model.number(),
name: model.text(),
users: model.manyToMany(() => user, {
pivotTable: "user_teams",
}),
})
const user = model.define("user", {
id: model.number(),
username: model.text(),
teams: model.manyToMany(() => team, {
pivotTable: "team_users",
mappedBy: "users",
}),
})
expect(() => toMikroORMEntity(user)).toThrow(
`Invalid relationship reference for "User.teams". Define "pivotTable", "joinColumn", or "inverseJoinColumn" on only one side of the relationship`
)
})
})
})

View File

@@ -15,13 +15,13 @@ import {
Property,
rel,
} from "@mikro-orm/core"
import { camelToSnakeCase, pluralize } from "../../../common"
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 { applyEntityIndexes } from "../mikro-orm/apply-indexes"
import { HasMany } from "../../relations/has-many"
import { parseEntityName } from "./parse-entity-name"
import { camelToSnakeCase, pluralize } from "../../../common"
import { applyEntityIndexes } from "../mikro-orm/apply-indexes"
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
type Context = {
MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean>
@@ -375,12 +375,10 @@ export function defineManyToManyRelationship(
>,
{
relatedModelName,
relatedTableName,
pgSchema,
}: {
relatedModelName: string
pgSchema: string | undefined
relatedTableName: string
},
{ MANY_TO_MANY_TRACKED_RELATIONS }: Context
) {
@@ -450,11 +448,12 @@ export function defineManyToManyRelationship(
pivotEntityName = parseEntityName(pivotEntity).modelName
}
if (!pivotEntityName) {
const { tableName } = parseEntityName(entity)
let tableNameWithoutSchema: string
let relatedTableNameWithoutSchema: string
const tableName = parseEntityName(entity).tableNameWithoutSchema
const relatedTableName = parseEntityName(relatedEntity).tableNameWithoutSchema
const sortedTableNames = [tableName, relatedTableName].sort()
const otherSideRelationOptions = otherSideRelationship.parse("").options
if (!pivotEntityName) {
/**
* Pivot table name is created as follows (when not explicitly provided)
*
@@ -464,25 +463,10 @@ export function defineManyToManyRelationship(
* - And finally pluralizing the second entity name.
*/
let [schema, ...tableTokens] = tableName.split(".")
if (!tableTokens.length) {
tableNameWithoutSchema = schema
} else {
tableNameWithoutSchema = tableTokens.join(".")
}
const [relatedSchema, ...relatedTableTokens] = relatedTableName.split(".")
if (!relatedTableTokens.length) {
relatedTableNameWithoutSchema = relatedSchema
} else {
relatedTableNameWithoutSchema = relatedTableTokens.join(".")
}
pivotTableName =
relationship.options.pivotTable ??
otherSideRelationship.parse("").options.pivotTable ??
[tableNameWithoutSchema, relatedTableNameWithoutSchema]
.sort()
sortedTableNames
.map((token, index) => {
if (index === 1) {
return pluralize(token)
@@ -492,22 +476,51 @@ export function defineManyToManyRelationship(
.join("_")
}
const otherSideRelationOptions = otherSideRelationship.parse("").options
let isOwner: boolean | undefined = undefined
const isOwner =
!!joinColumn ||
!!inverseJoinColumn ||
!!relationship.options.pivotTable ||
/**
* We can't infer it from the current entity so lets
* look at the otherside configuration as well to make a choice
*/
(!otherSideRelationOptions.pivotTable &&
!otherSideRelationOptions.joinColumn &&
!otherSideRelationOptions.inverseJoinColumn &&
!MANY_TO_MANY_TRACKED_RELATIONS[
`${relatedModelName}.${otherSideRelationshipProperty}`
])
const configuresRelationship = !!(
joinColumn ||
inverseJoinColumn ||
relationship.options.pivotTable
)
const relatedOneConfiguresRelationship = !!(
otherSideRelationOptions.pivotTable ||
otherSideRelationOptions.joinColumn ||
otherSideRelationOptions.inverseJoinColumn
)
/**
* Both sides are configuring the properties that must be on one
* side only
*/
if (configuresRelationship && relatedOneConfiguresRelationship) {
throw new Error(
`Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". Define "pivotTable", "joinColumn", or "inverseJoinColumn" on only one side of the relationship`
)
}
/**
* If any of the following properties are provided, we consider
* the current side to be the owner
*/
if (configuresRelationship) {
isOwner = true
}
/**
* If any of the properties are provided on the other side,
* then we do not expect the current side to be the owner
*/
if (isOwner === undefined && relatedOneConfiguresRelationship) {
isOwner = false
}
/**
* Finally, we consider the current side as owner, if it is
* the first one in alphabetical order. The same logic is
* applied to pivot table name as well.
*/
isOwner ??= sortedTableNames[0] === tableName
const mappedByProp = isOwner ? "inversedBy" : "mappedBy"
const mappedByPropValue =

View File

@@ -25,5 +25,6 @@ export function parseEntityName(entity: DmlEntity<any, any>) {
tableName,
modelName: upperCaseFirst(toCamelCase(parsedEntity.name)),
pgSchema: rest.length ? pgSchema : undefined,
tableNameWithoutSchema: rest.length ? rest.join(".") : pgSchema,
}
}

View File

@@ -217,13 +217,13 @@ describe("manyToMany - manyToMany", () => {
;[User, Team] = toMikroOrmEntities([user, team])
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
expect((teamMetaData.properties as any).users.mappedBy).toBe("squads")
expect((teamMetaData.properties as any).users.owner).toBe(false)
expect((teamMetaData.properties as any).users.inversedBy).toBe("squads")
expect((teamMetaData.properties as any).users.owner).toBe(true)
const userMetaData = MetadataStorage.getMetadataFromDecorator(User)
expect((userMetaData.properties as any).squads.mappedBy).not.toBeDefined()
expect((userMetaData.properties as any).squads.inversedBy).toBe("users")
expect((userMetaData.properties as any).squads.owner).toBe(true)
expect((userMetaData.properties as any).squads.inversedBy).not.toBeDefined()
expect((userMetaData.properties as any).squads.mappedBy).toBe("users")
expect((userMetaData.properties as any).squads.owner).toBe(false)
})
it(`should load the dml's correclty when both side of the relation are specifying the mappedBy options without pivot table`, () => {
@@ -248,14 +248,14 @@ describe("manyToMany - manyToMany", () => {
let [User, Team] = toMikroOrmEntities([user, team])
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
expect((teamMetaData.properties as any).users.mappedBy).toBe("squads")
expect((teamMetaData.properties as any).users.owner).toBe(false)
expect((teamMetaData.properties as any).users.inversedBy).toBe("squads")
expect((teamMetaData.properties as any).users.owner).toBe(true)
expect((teamMetaData.properties as any).users.pivotTable).toBe("team_users")
const userMetaData = MetadataStorage.getMetadataFromDecorator(User)
expect((userMetaData.properties as any).squads.mappedBy).not.toBeDefined()
expect((userMetaData.properties as any).squads.inversedBy).toBe("users")
expect((userMetaData.properties as any).squads.owner).toBe(true)
expect((userMetaData.properties as any).squads.inversedBy).not.toBeDefined()
expect((userMetaData.properties as any).squads.mappedBy).toBe("users")
expect((userMetaData.properties as any).squads.owner).toBe(false)
expect((userMetaData.properties as any).squads.pivotTable).toBe(
"team_users"
)