Handle embedded pg schema name inside the table name when generating indexes (#7774)

This commit is contained in:
Harminder Virk authored and GitHub committed 2024-06-19 16:48:44 +05:30
1 parent 0b623fa27a
commit fd87858bd9
4 files changed
+660 -66

No files matched your search

@@ -89,4 +89,42 @@ describe("createPsqlIndexStatementHelper", function () {
}" (${options.columns.join(", ")}) WHERE ${options.where}` }" (${options.columns.join(", ")}) WHERE ${options.where}`
) )
}) })
it("should generate index on an explicit pg schema", function () {
const options = {
name: "index_name",
tableName: "public.table_name",
columns: "column_name",
}
const indexStatement = createPsqlIndexStatementHelper(options)
expect(indexStatement + "").toEqual(
`CREATE INDEX IF NOT EXISTS "${options.name}" ON "public"."table_name" (${options.columns})`
)
})
it("generate index name from table name when using explicit pg schema", function () {
const options = {
tableName: "public.table_name",
columns: "column_name",
}
const indexStatement = createPsqlIndexStatementHelper(options)
expect(indexStatement + "").toEqual(
`CREATE INDEX IF NOT EXISTS "IDX_table_name_column_name" ON "public"."table_name" (${options.columns})`
)
})
it("should generate index when table name was previously formatted to allow pg schema name", function () {
const options = {
name: "index_name",
tableName: 'public"."table_name',
columns: "column_name",
}
const indexStatement = createPsqlIndexStatementHelper(options)
expect(indexStatement + "").toEqual(
`CREATE INDEX IF NOT EXISTS "${options.name}" ON "public"."table_name" (${options.columns})`
)
})
}) })
@@ -31,7 +31,7 @@ import { Index } from "@mikro-orm/core"
*/ */
export function createPsqlIndexStatementHelper({ export function createPsqlIndexStatementHelper({
name, name,
tableName, tableName: qualifiedName,
columns, columns,
type, type,
where, where,
@@ -45,6 +45,20 @@ export function createPsqlIndexStatementHelper({
unique?: boolean unique?: boolean
}) { }) {
const columnsName = Array.isArray(columns) ? columns.join("_") : columns const columnsName = Array.isArray(columns) ? columns.join("_") : columns
const tokens = qualifiedName.replace(/"/g, "").split(".")
let pgSchemaName: string | undefined
let tableName: string
let tableReference: string
if (tokens.length > 1) {
pgSchemaName = tokens.shift()
tableName = tokens.join(".")
tableReference = `"${pgSchemaName}"."${tableName}"`
} else {
tableName = qualifiedName
tableReference = `"${tableName}"`
}
columns = Array.isArray(columns) ? columns.join(", ") : columns columns = Array.isArray(columns) ? columns.join(", ") : columns
name = name || `IDX_${tableName}_${columnsName}${unique ? "_unique" : ""}` name = name || `IDX_${tableName}_${columnsName}${unique ? "_unique" : ""}`
@@ -53,7 +67,7 @@ export function createPsqlIndexStatementHelper({
const optionsStr = where ? ` WHERE ${where}` : "" const optionsStr = where ? ` WHERE ${where}` : ""
const uniqueStr = unique ? "UNIQUE " : "" const uniqueStr = unique ? "UNIQUE " : ""
const expression = `CREATE ${uniqueStr}INDEX IF NOT EXISTS "${name}" ON "${tableName}"${typeStr} (${columns})${optionsStr}` const expression = `CREATE ${uniqueStr}INDEX IF NOT EXISTS "${name}" ON ${tableReference}${typeStr} (${columns})${optionsStr}`
return { return {
toString: () => { toString: () => {
return expression return expression
@@ -669,6 +669,7 @@ describe("Entity builder", () => {
const metaData = MetadataStorage.getMetadataFromDecorator(User) const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User") expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User") expect(metaData.path).toEqual("User")
expect(metaData.tableName).toEqual("public.user")
expect(metaData.filters).toEqual({ expect(metaData.filters).toEqual({
softDeletable: { softDeletable: {
@@ -772,9 +773,9 @@ describe("Entity builder", () => {
'CREATE INDEX IF NOT EXISTS "IDX_user_id" ON "user" (id) WHERE deleted_at IS NULL', 'CREATE INDEX IF NOT EXISTS "IDX_user_id" ON "user" (id) WHERE deleted_at IS NULL',
}, },
{ {
name: "IDX_user_email", name: "IDX_user_email_unique",
expression: expression:
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email" ON "user" (email) WHERE deleted_at IS NULL', 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_unique" ON "user" (email) WHERE deleted_at IS NULL',
}, },
]) ])
@@ -870,6 +871,7 @@ describe("Entity builder", () => {
const metaData = MetadataStorage.getMetadataFromDecorator(User) const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User") expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User") expect(metaData.path).toEqual("User")
expect(metaData.tableName).toEqual("platform.user")
expect(metaData.indexes).toEqual([ expect(metaData.indexes).toEqual([
{ {
@@ -878,9 +880,9 @@ describe("Entity builder", () => {
'CREATE INDEX IF NOT EXISTS "IDX_user_id" ON "platform"."user" (id) WHERE deleted_at IS NULL', 'CREATE INDEX IF NOT EXISTS "IDX_user_id" ON "platform"."user" (id) WHERE deleted_at IS NULL',
}, },
{ {
name: "IDX_user_email", name: "IDX_user_email_unique",
expression: expression:
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email" ON "platform"."user" (email) WHERE deleted_at IS NULL', 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_unique" ON "platform"."user" (email) WHERE deleted_at IS NULL',
}, },
]) ])
@@ -2694,7 +2696,7 @@ describe("Entity builder", () => {
const entityBuilder = createMikrORMEntity() const entityBuilder = createMikrORMEntity()
expect(() => entityBuilder(email)).toThrow( expect(() => entityBuilder(email)).toThrow(
'Missing property "email" on "user" entity. Make sure to define it as a relationship' 'Missing property "email" on "User" entity. Make sure to define it as a relationship'
) )
}) })
@@ -2715,7 +2717,7 @@ describe("Entity builder", () => {
const entityBuilder = createMikrORMEntity() const entityBuilder = createMikrORMEntity()
expect(() => entityBuilder(email)).toThrow( expect(() => entityBuilder(email)).toThrow(
'Invalid relationship reference for "email" on "user" entity. Make sure to define a hasOne or hasMany relationship' 'Invalid relationship reference for "email" on "User" entity. Make sure to define a hasOne or hasMany relationship'
) )
}) })
@@ -2743,6 +2745,368 @@ describe("Entity builder", () => {
'Cannot cascade delete "user" relationship(s) from "email" entity. Child to parent cascades are not allowed' 'Cannot cascade delete "user" relationship(s) from "email" entity. Child to parent cascades are not allowed'
) )
}) })
test("define relationships when entity names has pg schema name", () => {
const model = new EntityBuilder()
const email = model.define("platform.email", {
email: model.text(),
isVerified: model.boolean(),
user: model.belongsTo(() => user),
})
const user = model.define("platform.user", {
id: model.number(),
username: model.text(),
email: model.hasOne(() => email),
})
const entityBuilder = createMikrORMEntity()
const User = entityBuilder(user)
const Email = entityBuilder(email)
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
id: number
username: string
deleted_at: Date | null
}>
}>
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: string
isVerified: boolean
deleted_at: Date | null
}>
}>
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.tableName).toEqual("platform.user")
expect(metaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
columnType: "integer",
name: "id",
nullable: false,
getter: false,
setter: false,
},
username: {
reference: "scalar",
type: "string",
columnType: "text",
name: "username",
nullable: false,
getter: false,
setter: false,
},
email: {
reference: "1:1",
name: "email",
entity: "Email",
nullable: false,
mappedBy: "user",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
const emailMetaData = MetadataStorage.getMetadataFromDecorator(Email)
expect(emailMetaData.className).toEqual("Email")
expect(emailMetaData.path).toEqual("Email")
expect(emailMetaData.tableName).toEqual("platform.email")
expect(emailMetaData.properties).toEqual({
email: {
reference: "scalar",
type: "string",
columnType: "text",
name: "email",
nullable: false,
getter: false,
setter: false,
},
isVerified: {
reference: "scalar",
type: "boolean",
columnType: "boolean",
name: "isVerified",
nullable: false,
getter: false,
setter: false,
},
user: {
reference: "1:1",
name: "user",
entity: "User",
nullable: false,
owner: true,
mappedBy: "email",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
})
test("define relationships between cross pg schemas entities", () => {
const model = new EntityBuilder()
const email = model.define("platform.email", {
email: model.text(),
isVerified: model.boolean(),
user: model.belongsTo(() => user),
})
const user = model.define("public.user", {
id: model.number(),
username: model.text(),
email: model.hasOne(() => email),
})
const entityBuilder = createMikrORMEntity()
const User = entityBuilder(user)
const Email = entityBuilder(email)
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
id: number
username: string
deleted_at: Date | null
}>
}>
}>()
expectTypeOf(new Email()).toMatchTypeOf<{
email: string
isVerified: boolean
deleted_at: Date | null
user: EntityConstructor<{
id: number
username: string
deleted_at: Date | null
email: EntityConstructor<{
email: string
isVerified: boolean
deleted_at: Date | null
}>
}>
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.tableName).toEqual("public.user")
expect(metaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
columnType: "integer",
name: "id",
nullable: false,
getter: false,
setter: false,
},
username: {
reference: "scalar",
type: "string",
columnType: "text",
name: "username",
nullable: false,
getter: false,
setter: false,
},
email: {
reference: "1:1",
name: "email",
entity: "Email",
nullable: false,
mappedBy: "user",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
const emailMetaData = MetadataStorage.getMetadataFromDecorator(Email)
expect(emailMetaData.className).toEqual("Email")
expect(emailMetaData.path).toEqual("Email")
expect(emailMetaData.tableName).toEqual("platform.email")
expect(emailMetaData.properties).toEqual({
email: {
reference: "scalar",
type: "string",
columnType: "text",
name: "email",
nullable: false,
getter: false,
setter: false,
},
isVerified: {
reference: "scalar",
type: "boolean",
columnType: "boolean",
name: "isVerified",
nullable: false,
getter: false,
setter: false,
},
user: {
reference: "1:1",
name: "user",
entity: "User",
nullable: false,
owner: true,
mappedBy: "email",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
})
}) })
describe("Entity builder | manyToMany", () => { describe("Entity builder | manyToMany", () => {
@@ -3100,7 +3464,7 @@ describe("Entity builder", () => {
const entityBuilder = createMikrORMEntity() const entityBuilder = createMikrORMEntity()
expect(() => entityBuilder(user)).toThrow( expect(() => entityBuilder(user)).toThrow(
'Missing property "users" on "team" entity. Make sure to define it as a relationship' 'Missing property "users" on "Team" entity. Make sure to define it as a relationship'
) )
}) })
@@ -3120,7 +3484,7 @@ describe("Entity builder", () => {
const entityBuilder = createMikrORMEntity() const entityBuilder = createMikrORMEntity()
expect(() => entityBuilder(user)).toThrow( expect(() => entityBuilder(user)).toThrow(
'Invalid relationship reference for "users" on "team" entity. Make sure to define a manyToMany relationship' 'Invalid relationship reference for "users" on "Team" entity. Make sure to define a manyToMany relationship'
) )
}) })
@@ -3680,5 +4044,176 @@ describe("Entity builder", () => {
}, },
}) })
}) })
test("define manyToMany relationship when entity names has pg schema name", () => {
const model = new EntityBuilder()
const team = model.define("platform.team", {
id: model.number(),
name: model.text(),
users: model.manyToMany(() => user),
})
const user = model.define("platform.user", {
id: model.number(),
username: model.text(),
teams: model.manyToMany(() => team),
})
const entityBuilder = createMikrORMEntity()
const User = entityBuilder(user)
const Team = entityBuilder(team)
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
teams: EntityConstructor<{
id: number
name: string
users: EntityConstructor<{
id: number
username: string
}>
}>
}>()
expectTypeOf(new Team()).toMatchTypeOf<{
id: number
name: string
users: EntityConstructor<{
id: number
username: string
teams: EntityConstructor<{
id: number
name: string
}>
}>
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.tableName).toEqual("platform.user")
expect(metaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
columnType: "integer",
name: "id",
nullable: false,
getter: false,
setter: false,
},
username: {
reference: "scalar",
type: "string",
columnType: "text",
name: "username",
nullable: false,
getter: false,
setter: false,
},
teams: {
reference: "m:n",
name: "teams",
entity: "Team",
pivotTable: "platform.team_users",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
const teamMetaData = MetadataStorage.getMetadataFromDecorator(Team)
expect(teamMetaData.className).toEqual("Team")
expect(teamMetaData.path).toEqual("Team")
expect(teamMetaData.tableName).toEqual("platform.team")
expect(teamMetaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
columnType: "integer",
name: "id",
nullable: false,
getter: false,
setter: false,
},
name: {
reference: "scalar",
type: "string",
columnType: "text",
name: "name",
nullable: false,
getter: false,
setter: false,
},
users: {
reference: "m:n",
name: "users",
entity: "User",
pivotTable: "platform.team_users",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
})
}) })
}) })
@@ -104,6 +104,33 @@ const SPECIAL_PROPERTIES: {
* to Mikro ORM entities. * to Mikro ORM entities.
*/ */
export function createMikrORMEntity() { export function createMikrORMEntity() {
/**
* Parses entity name and returns model and table name from
* it
*/
function parseEntityName(entityName: string) {
/**
* Table name is going to be the snake case version of the entity name.
* Here we should preserve PG schema (if defined).
*
* For example: "platform.user" should stay as "platform.user"
*/
const tableName = camelToSnakeCase(entityName)
/**
* Entity name is going to be the camelCase version of the
* name defined by the user
*/
const [pgSchema, ...rest] = tableName.split(".")
return {
tableName,
modelName: upperCaseFirst(
toCamelCase(rest.length ? rest.join("_") : pgSchema)
),
pgSchema: rest.length ? pgSchema : undefined,
}
}
/** /**
* The following property is used to track many to many relationship * The following property is used to track many to many relationship
* between two entities. It is needed because we have to mark one * between two entities. It is needed because we have to mark one
@@ -162,19 +189,12 @@ export function createMikrORMEntity() {
*/ */
function applyIndexes( function applyIndexes(
MikroORMEntity: EntityConstructor<any>, MikroORMEntity: EntityConstructor<any>,
{ tableName: string,
tableName,
pgSchema,
}: { tableName: string; pgSchema: undefined | string },
field: PropertyMetadata field: PropertyMetadata
) { ) {
field.indexes.forEach((index) => { field.indexes.forEach((index) => {
const name =
index.name || `IDX_${tableName}_${camelToSnakeCase(field.fieldName)}`
const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({ const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({
name, tableName,
tableName: pgSchema ? `${pgSchema}"."${tableName}` : tableName,
columns: [field.fieldName], columns: [field.fieldName],
unique: index.type === "unique", unique: index.type === "unique",
where: "deleted_at IS NULL", where: "deleted_at IS NULL",
@@ -190,12 +210,9 @@ export function createMikrORMEntity() {
function defineHasOneRelationship( function defineHasOneRelationship(
MikroORMEntity: EntityConstructor<any>, MikroORMEntity: EntityConstructor<any>,
relationship: RelationshipMetadata, relationship: RelationshipMetadata,
relatedEntity: DmlEntity< { relatedModelName }: { relatedModelName: string },
Record<string, PropertyType<any> | RelationshipType<any>>
>,
cascades: EntityCascades<string[]> cascades: EntityCascades<string[]>
) { ) {
const relatedModelName = upperCaseFirst(relatedEntity.name)
const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name) const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name)
OneToOne({ OneToOne({
@@ -214,12 +231,9 @@ export function createMikrORMEntity() {
function defineHasManyRelationship( function defineHasManyRelationship(
MikroORMEntity: EntityConstructor<any>, MikroORMEntity: EntityConstructor<any>,
relationship: RelationshipMetadata, relationship: RelationshipMetadata,
relatedEntity: DmlEntity< { relatedModelName }: { relatedModelName: string },
Record<string, PropertyType<any> | RelationshipType<any>>
>,
cascades: EntityCascades<string[]> cascades: EntityCascades<string[]>
) { ) {
const relatedModelName = upperCaseFirst(relatedEntity.name)
const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name) const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name)
OneToMany({ OneToMany({
@@ -246,7 +260,8 @@ export function createMikrORMEntity() {
relationship: RelationshipMetadata, relationship: RelationshipMetadata,
relatedEntity: DmlEntity< relatedEntity: DmlEntity<
Record<string, PropertyType<any> | RelationshipType<any>> Record<string, PropertyType<any> | RelationshipType<any>>
> >,
{ relatedModelName }: { relatedModelName: string }
) { ) {
const mappedBy = const mappedBy =
relationship.mappedBy || camelToSnakeCase(MikroORMEntity.name) relationship.mappedBy || camelToSnakeCase(MikroORMEntity.name)
@@ -254,7 +269,6 @@ export function createMikrORMEntity() {
relatedEntity.parse() relatedEntity.parse()
const otherSideRelation = relationSchema[mappedBy] const otherSideRelation = relationSchema[mappedBy]
const relatedModelName = upperCaseFirst(relatedEntity.name)
/** /**
* In DML the relationships are cascaded from parent to child. A belongsTo * In DML the relationships are cascaded from parent to child. A belongsTo
@@ -269,7 +283,7 @@ export function createMikrORMEntity() {
*/ */
if (!otherSideRelation) { if (!otherSideRelation) {
throw new Error( throw new Error(
`Missing property "${mappedBy}" on "${relatedEntity.name}" entity. Make sure to define it as a relationship` `Missing property "${mappedBy}" on "${relatedModelName}" entity. Make sure to define it as a relationship`
) )
} }
@@ -311,7 +325,7 @@ export function createMikrORMEntity() {
* Other side is some unsupported data-type * Other side is some unsupported data-type
*/ */
throw new Error( throw new Error(
`Invalid relationship reference for "${mappedBy}" on "${relatedEntity.name}" entity. Make sure to define a hasOne or hasMany relationship` `Invalid relationship reference for "${mappedBy}" on "${relatedModelName}" entity. Make sure to define a hasOne or hasMany relationship`
) )
} }
@@ -324,9 +338,11 @@ export function createMikrORMEntity() {
relatedEntity: DmlEntity< relatedEntity: DmlEntity<
Record<string, PropertyType<any> | RelationshipType<any>> Record<string, PropertyType<any> | RelationshipType<any>>
>, >,
cascades: EntityCascades<string[]> {
relatedModelName,
pgSchema,
}: { relatedModelName: string; pgSchema: string | undefined }
) { ) {
const relatedModelName = upperCaseFirst(relatedEntity.name)
let mappedBy = relationship.mappedBy let mappedBy = relationship.mappedBy
let inversedBy: undefined | string let inversedBy: undefined | string
@@ -340,7 +356,7 @@ export function createMikrORMEntity() {
*/ */
const pivotTableName = [ const pivotTableName = [
MikroORMEntity.name.toLowerCase(), MikroORMEntity.name.toLowerCase(),
relatedEntity.name.toLowerCase(), relatedModelName.toLowerCase(),
] ]
.sort() .sort()
.map((token, index) => { .map((token, index) => {
@@ -355,13 +371,13 @@ export function createMikrORMEntity() {
const otherSideRelation = relatedEntity.parse().schema[mappedBy] const otherSideRelation = relatedEntity.parse().schema[mappedBy]
if (!otherSideRelation) { if (!otherSideRelation) {
throw new Error( throw new Error(
`Missing property "${mappedBy}" on "${relatedEntity.name}" entity. Make sure to define it as a relationship` `Missing property "${mappedBy}" on "${relatedModelName}" entity. Make sure to define it as a relationship`
) )
} }
if (otherSideRelation instanceof DmlManyToMany === false) { if (otherSideRelation instanceof DmlManyToMany === false) {
throw new Error( throw new Error(
`Invalid relationship reference for "${mappedBy}" on "${relatedEntity.name}" entity. Make sure to define a manyToMany relationship` `Invalid relationship reference for "${mappedBy}" on "${relatedModelName}" entity. Make sure to define a manyToMany relationship`
) )
} }
@@ -387,7 +403,7 @@ export function createMikrORMEntity() {
ManyToMany({ ManyToMany({
entity: relatedModelName, entity: relatedModelName,
pivotTable: pivotTableName, pivotTable: pgSchema ? `${pgSchema}.${pivotTableName}` : pivotTableName,
...(mappedBy ? { mappedBy: mappedBy as any } : {}), ...(mappedBy ? { mappedBy: mappedBy as any } : {}),
...(inversedBy ? { inversedBy: inversedBy as any } : {}), ...(inversedBy ? { inversedBy: inversedBy as any } : {}),
})(MikroORMEntity.prototype, relationship.name) })(MikroORMEntity.prototype, relationship.name)
@@ -429,6 +445,15 @@ export function createMikrORMEntity() {
) )
} }
const { modelName, tableName, pgSchema } = parseEntityName(
relatedEntity.parse().name
)
const relatedEntityInfo = {
relatedModelName: modelName,
relatedTableName: tableName,
pgSchema,
}
/** /**
* Defining relationships * Defining relationships
*/ */
@@ -437,7 +462,7 @@ export function createMikrORMEntity() {
defineHasOneRelationship( defineHasOneRelationship(
MikroORMEntity, MikroORMEntity,
relationship, relationship,
relatedEntity, relatedEntityInfo,
cascades cascades
) )
break break
@@ -445,19 +470,24 @@ export function createMikrORMEntity() {
defineHasManyRelationship( defineHasManyRelationship(
MikroORMEntity, MikroORMEntity,
relationship, relationship,
relatedEntity, relatedEntityInfo,
cascades cascades
) )
break break
case "belongsTo": case "belongsTo":
defineBelongsToRelationship(MikroORMEntity, relationship, relatedEntity) defineBelongsToRelationship(
MikroORMEntity,
relationship,
relatedEntity,
relatedEntityInfo
)
break break
case "manyToMany": case "manyToMany":
defineManyToManyRelationship( defineManyToManyRelationship(
MikroORMEntity, MikroORMEntity,
relationship, relationship,
relatedEntity, relatedEntity,
cascades relatedEntityInfo
) )
break break
} }
@@ -470,26 +500,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 { name, schema, cascades } = entity.parse() const { name, schema, cascades } = entity.parse()
const [pgSchema, ...rest] = name.split(".") const { modelName, tableName } = parseEntityName(name)
/**
* Entity name is computed by splitting the pgSchema
* from the original name and converting everything
* to camelCase
*/
const entityName = rest.length
? toCamelCase(rest.join("_"))
: toCamelCase(pgSchema)
/**
* Table name is the snake case version of entityName
*/
const tableName = camelToSnakeCase(entityName)
/**
* Table name is the Pascal case version of entityName
*/
const modelName = upperCaseFirst(entityName)
/** /**
* Assigning name to the class constructor, so that it matches * Assigning name to the class constructor, so that it matches
@@ -508,11 +519,7 @@ export function createMikrORMEntity() {
const field = property.parse(name) const field = property.parse(name)
if ("fieldName" in field) { if ("fieldName" in field) {
defineProperty(MikroORMEntity, field) defineProperty(MikroORMEntity, field)
applyIndexes( applyIndexes(MikroORMEntity, tableName, field)
MikroORMEntity,
{ tableName, pgSchema: rest.length ? pgSchema : undefined },
field
)
} else { } else {
defineRelationship(MikroORMEntity, field, cascades) defineRelationship(MikroORMEntity, field, cascades)
} }