Allow entities to contain pg schema name in their name (#7773)

This commit is contained in:
Harminder Virk
2024-06-19 14:39:33 +05:30
committed by GitHub
parent 305e1d66ca
commit 0b623fa27a
2 changed files with 237 additions and 11 deletions

View File

@@ -646,6 +646,101 @@ describe("Entity builder", () => {
'Cannot define field(s) "deleted_at" as they are implicitly defined on every model'
)
})
test("define pg schema name in the entity name", () => {
const model = new EntityBuilder()
const user = model.define("public.user", {
id: model.number(),
username: model.text(),
email: model.text(),
})
const entityBuilder = createMikrORMEntity()
const User = entityBuilder(user)
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
email: string
created_at: Date
updated_at: Date
deleted_at: Date | null
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.filters).toEqual({
softDeletable: {
name: "softDeletable",
cond: expect.any(Function),
default: true,
args: false,
},
})
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: "scalar",
type: "string",
columnType: "text",
name: "email",
nullable: false,
getter: false,
setter: false,
},
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 | indexes", () => {
@@ -672,14 +767,120 @@ describe("Entity builder", () => {
expect(metaData.indexes).toEqual([
{
name: "IDX_users_id",
name: "IDX_user_id",
expression:
'CREATE INDEX IF NOT EXISTS "IDX_users_id" ON "users" (id) WHERE deleted_at IS NULL',
'CREATE INDEX IF NOT EXISTS "IDX_user_id" ON "user" (id) WHERE deleted_at IS NULL',
},
{
name: "IDX_users_email",
name: "IDX_user_email",
expression:
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_users_email" ON "users" (email) WHERE deleted_at IS NULL',
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email" ON "user" (email) WHERE deleted_at IS NULL',
},
])
expect(metaData.filters).toEqual({
softDeletable: {
name: "softDeletable",
cond: expect.any(Function),
default: true,
args: false,
},
})
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: "scalar",
type: "string",
columnType: "text",
name: "email",
nullable: false,
getter: false,
setter: false,
},
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 index when entity is using an explicit pg Schema", () => {
const model = new EntityBuilder()
const user = model.define("platform.user", {
id: model.number().index(),
username: model.text(),
email: model.text().unique(),
})
const entityBuilder = createMikrORMEntity()
const User = entityBuilder(user)
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
email: string
deleted_at: Date | null
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.indexes).toEqual([
{
name: "IDX_user_id",
expression:
'CREATE INDEX IF NOT EXISTS "IDX_user_id" ON "platform"."user" (id) WHERE deleted_at IS NULL',
},
{
name: "IDX_user_email",
expression:
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email" ON "platform"."user" (email) WHERE deleted_at IS NULL',
},
])

View File

@@ -13,6 +13,7 @@ import {
pluralize,
camelToSnakeCase,
createPsqlIndexStatementHelper,
toCamelCase,
} from "../../common"
import { upperCaseFirst } from "../../common/upper-case-first"
import type {
@@ -110,7 +111,7 @@ export function createMikrORMEntity() {
* any user land APIs to explicitly define an owner.
*
* The object contains values as follows.
* - [entityname.relationship]: true // true means, it is already marked as owner
* - [modelName.relationship]: true // true means, it is already marked as owner
*
* Example:
* - [user.teams]: true // the teams relationship on user is an owner
@@ -161,7 +162,10 @@ export function createMikrORMEntity() {
*/
function applyIndexes(
MikroORMEntity: EntityConstructor<any>,
tableName: string,
{
tableName,
pgSchema,
}: { tableName: string; pgSchema: undefined | string },
field: PropertyMetadata
) {
field.indexes.forEach((index) => {
@@ -170,7 +174,7 @@ export function createMikrORMEntity() {
const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({
name,
tableName,
tableName: pgSchema ? `${pgSchema}"."${tableName}` : tableName,
columns: [field.fieldName],
unique: index.type === "unique",
where: "deleted_at IS NULL",
@@ -466,9 +470,26 @@ 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(".")
const className = upperCaseFirst(name)
const tableName = pluralize(camelToSnakeCase(className))
/**
* 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
@@ -476,7 +497,7 @@ export function createMikrORMEntity() {
*/
Object.defineProperty(MikroORMEntity, "name", {
get: function () {
return className
return modelName
},
})
@@ -487,7 +508,11 @@ export function createMikrORMEntity() {
const field = property.parse(name)
if ("fieldName" in field) {
defineProperty(MikroORMEntity, field)
applyIndexes(MikroORMEntity, tableName, field)
applyIndexes(
MikroORMEntity,
{ tableName, pgSchema: rest.length ? pgSchema : undefined },
field
)
} else {
defineRelationship(MikroORMEntity, field, cascades)
}