Handle embedded pg schema name inside the table name when generating indexes (#7774)
This commit is contained in:
@@ -89,4 +89,42 @@ describe("createPsqlIndexStatementHelper", function () {
|
||||
}" (${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({
|
||||
name,
|
||||
tableName,
|
||||
tableName: qualifiedName,
|
||||
columns,
|
||||
type,
|
||||
where,
|
||||
@@ -45,6 +45,20 @@ export function createPsqlIndexStatementHelper({
|
||||
unique?: boolean
|
||||
}) {
|
||||
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
|
||||
name = name || `IDX_${tableName}_${columnsName}${unique ? "_unique" : ""}`
|
||||
@@ -53,7 +67,7 @@ export function createPsqlIndexStatementHelper({
|
||||
const optionsStr = where ? ` WHERE ${where}` : ""
|
||||
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 {
|
||||
toString: () => {
|
||||
return expression
|
||||
|
||||
@@ -669,6 +669,7 @@ describe("Entity builder", () => {
|
||||
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||
expect(metaData.className).toEqual("User")
|
||||
expect(metaData.path).toEqual("User")
|
||||
expect(metaData.tableName).toEqual("public.user")
|
||||
|
||||
expect(metaData.filters).toEqual({
|
||||
softDeletable: {
|
||||
@@ -772,9 +773,9 @@ describe("Entity builder", () => {
|
||||
'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:
|
||||
'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)
|
||||
expect(metaData.className).toEqual("User")
|
||||
expect(metaData.path).toEqual("User")
|
||||
expect(metaData.tableName).toEqual("platform.user")
|
||||
|
||||
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',
|
||||
},
|
||||
{
|
||||
name: "IDX_user_email",
|
||||
name: "IDX_user_email_unique",
|
||||
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()
|
||||
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()
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
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", () => {
|
||||
@@ -3100,7 +3464,7 @@ describe("Entity builder", () => {
|
||||
|
||||
const entityBuilder = createMikrORMEntity()
|
||||
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()
|
||||
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.
|
||||
*/
|
||||
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
|
||||
* between two entities. It is needed because we have to mark one
|
||||
@@ -162,19 +189,12 @@ export function createMikrORMEntity() {
|
||||
*/
|
||||
function applyIndexes(
|
||||
MikroORMEntity: EntityConstructor<any>,
|
||||
{
|
||||
tableName,
|
||||
pgSchema,
|
||||
}: { tableName: string; pgSchema: undefined | string },
|
||||
tableName: string,
|
||||
field: PropertyMetadata
|
||||
) {
|
||||
field.indexes.forEach((index) => {
|
||||
const name =
|
||||
index.name || `IDX_${tableName}_${camelToSnakeCase(field.fieldName)}`
|
||||
|
||||
const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({
|
||||
name,
|
||||
tableName: pgSchema ? `${pgSchema}"."${tableName}` : tableName,
|
||||
tableName,
|
||||
columns: [field.fieldName],
|
||||
unique: index.type === "unique",
|
||||
where: "deleted_at IS NULL",
|
||||
@@ -190,12 +210,9 @@ export function createMikrORMEntity() {
|
||||
function defineHasOneRelationship(
|
||||
MikroORMEntity: EntityConstructor<any>,
|
||||
relationship: RelationshipMetadata,
|
||||
relatedEntity: DmlEntity<
|
||||
Record<string, PropertyType<any> | RelationshipType<any>>
|
||||
>,
|
||||
{ relatedModelName }: { relatedModelName: string },
|
||||
cascades: EntityCascades<string[]>
|
||||
) {
|
||||
const relatedModelName = upperCaseFirst(relatedEntity.name)
|
||||
const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name)
|
||||
|
||||
OneToOne({
|
||||
@@ -214,12 +231,9 @@ export function createMikrORMEntity() {
|
||||
function defineHasManyRelationship(
|
||||
MikroORMEntity: EntityConstructor<any>,
|
||||
relationship: RelationshipMetadata,
|
||||
relatedEntity: DmlEntity<
|
||||
Record<string, PropertyType<any> | RelationshipType<any>>
|
||||
>,
|
||||
{ relatedModelName }: { relatedModelName: string },
|
||||
cascades: EntityCascades<string[]>
|
||||
) {
|
||||
const relatedModelName = upperCaseFirst(relatedEntity.name)
|
||||
const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name)
|
||||
|
||||
OneToMany({
|
||||
@@ -246,7 +260,8 @@ export function createMikrORMEntity() {
|
||||
relationship: RelationshipMetadata,
|
||||
relatedEntity: DmlEntity<
|
||||
Record<string, PropertyType<any> | RelationshipType<any>>
|
||||
>
|
||||
>,
|
||||
{ relatedModelName }: { relatedModelName: string }
|
||||
) {
|
||||
const mappedBy =
|
||||
relationship.mappedBy || camelToSnakeCase(MikroORMEntity.name)
|
||||
@@ -254,7 +269,6 @@ export function createMikrORMEntity() {
|
||||
relatedEntity.parse()
|
||||
|
||||
const otherSideRelation = relationSchema[mappedBy]
|
||||
const relatedModelName = upperCaseFirst(relatedEntity.name)
|
||||
|
||||
/**
|
||||
* In DML the relationships are cascaded from parent to child. A belongsTo
|
||||
@@ -269,7 +283,7 @@ export function createMikrORMEntity() {
|
||||
*/
|
||||
if (!otherSideRelation) {
|
||||
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
|
||||
*/
|
||||
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<
|
||||
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 inversedBy: undefined | string
|
||||
|
||||
@@ -340,7 +356,7 @@ export function createMikrORMEntity() {
|
||||
*/
|
||||
const pivotTableName = [
|
||||
MikroORMEntity.name.toLowerCase(),
|
||||
relatedEntity.name.toLowerCase(),
|
||||
relatedModelName.toLowerCase(),
|
||||
]
|
||||
.sort()
|
||||
.map((token, index) => {
|
||||
@@ -355,13 +371,13 @@ export function createMikrORMEntity() {
|
||||
const otherSideRelation = relatedEntity.parse().schema[mappedBy]
|
||||
if (!otherSideRelation) {
|
||||
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) {
|
||||
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({
|
||||
entity: relatedModelName,
|
||||
pivotTable: pivotTableName,
|
||||
pivotTable: pgSchema ? `${pgSchema}.${pivotTableName}` : pivotTableName,
|
||||
...(mappedBy ? { mappedBy: mappedBy as any } : {}),
|
||||
...(inversedBy ? { inversedBy: inversedBy as any } : {}),
|
||||
})(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
|
||||
*/
|
||||
@@ -437,7 +462,7 @@ export function createMikrORMEntity() {
|
||||
defineHasOneRelationship(
|
||||
MikroORMEntity,
|
||||
relationship,
|
||||
relatedEntity,
|
||||
relatedEntityInfo,
|
||||
cascades
|
||||
)
|
||||
break
|
||||
@@ -445,19 +470,24 @@ export function createMikrORMEntity() {
|
||||
defineHasManyRelationship(
|
||||
MikroORMEntity,
|
||||
relationship,
|
||||
relatedEntity,
|
||||
relatedEntityInfo,
|
||||
cascades
|
||||
)
|
||||
break
|
||||
case "belongsTo":
|
||||
defineBelongsToRelationship(MikroORMEntity, relationship, relatedEntity)
|
||||
defineBelongsToRelationship(
|
||||
MikroORMEntity,
|
||||
relationship,
|
||||
relatedEntity,
|
||||
relatedEntityInfo
|
||||
)
|
||||
break
|
||||
case "manyToMany":
|
||||
defineManyToManyRelationship(
|
||||
MikroORMEntity,
|
||||
relationship,
|
||||
relatedEntity,
|
||||
cascades
|
||||
relatedEntityInfo
|
||||
)
|
||||
break
|
||||
}
|
||||
@@ -470,26 +500,7 @@ export function createMikrORMEntity() {
|
||||
return function createEntity<T extends DmlEntity<any>>(entity: T): Infer<T> {
|
||||
class MikroORMEntity {}
|
||||
const { name, schema, cascades } = entity.parse()
|
||||
const [pgSchema, ...rest] = name.split(".")
|
||||
|
||||
/**
|
||||
* 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)
|
||||
const { modelName, tableName } = parseEntityName(name)
|
||||
|
||||
/**
|
||||
* Assigning name to the class constructor, so that it matches
|
||||
@@ -508,11 +519,7 @@ export function createMikrORMEntity() {
|
||||
const field = property.parse(name)
|
||||
if ("fieldName" in field) {
|
||||
defineProperty(MikroORMEntity, field)
|
||||
applyIndexes(
|
||||
MikroORMEntity,
|
||||
{ tableName, pgSchema: rest.length ? pgSchema : undefined },
|
||||
field
|
||||
)
|
||||
applyIndexes(MikroORMEntity, tableName, field)
|
||||
} else {
|
||||
defineRelationship(MikroORMEntity, field, cascades)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user