From 0b623fa27adaa893d3a434deff6fd09837eed5ac Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 19 Jun 2024 14:39:33 +0530 Subject: [PATCH] Allow entities to contain pg schema name in their name (#7773) --- .../src/dml/__tests__/entity-builder.spec.ts | 209 +++++++++++++++++- .../dml/helpers/create-mikro-orm-entity.ts | 39 +++- 2 files changed, 237 insertions(+), 11 deletions(-) diff --git a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts index 5105202465..477b2fa553 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -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', }, ]) diff --git a/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts b/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts index baedcba711..5245989bc4 100644 --- a/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts +++ b/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts @@ -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, - 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>(entity: T): Infer { 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) }