-
-
Notifications
You must be signed in to change notification settings - Fork 34
Description
Describe the bug
TLDR
- Given we are using Entity Schema definitions
- And we have a
User
entity class with its schema - And we have an
Employee
entity class that shares some fields with theUser
(and resides in the same table) - And we have an
EmployeeDetail
class and entity with a One-To-One reference toEmployee
- And we persist a new user
- When we try to create and persist a new
EmployeeDetail
entity, passing theemployee
as an attribute - Then we got an error:
insert into "user" ("id") values (default) returning "id" - null value in column "email" violates not-null constraint
Consider the following entity relationships:
---
title: Read-Only association example
---
erDiagram
company ||--o{ user : "employs"
user ||--o{ address : "resides_at"
user ||--o{ employee_detail : "has"
company {
int id
string name
}
user {
int id
string email
string first_name
string last_name
int company_id
date created_at
date updated_at
}
address {
int id
int user_id
string street
}
employee_detail {
int id
int user_id
float salary
}
Also, consider that we have an Employee
entity that is not described in a table above. That happens because an Employee
is a "sub-type" of User
, with the association with a company:
export class Employee {
public readonly id: number;
public readonly email: string;
public readonly company: Reference<Company>;
constructor(props) {
Object.assign(this, props);
}
}
EmployeeDetail
in the database, pointing to a reference of Employee
, Mikro-ORM throws an error: insert into "user" ("id") values (default) returning "id" - null value in column "email" violates not-null constraint
Stack trace
src/mikro-orm/mikro-orm-internal.module.spec.ts > MikroOrmInternalModule > creates a new employee detail
NotNullConstraintViolationException: insert into "user" ("id") values (default) returning "id" - null value in column "email" violates not-null constraint
❯ PostgreSqlExceptionConverter.convertException node_modules/@mikro-orm/postgresql/PostgreSqlExceptionConverter.js:24:24
❯ PostgreSqlDriver.convertException node_modules/@mikro-orm/core/drivers/DatabaseDriver.js:197:54
❯ node_modules/@mikro-orm/core/drivers/DatabaseDriver.js:201:24
❯ PostgreSqlDriver.nativeInsertMany node_modules/@mikro-orm/knex/AbstractSqlDriver.js:303:21
❯ ChangeSetPersister.persistNewEntity node_modules/@mikro-orm/core/unit-of-work/ChangeSetPersister.js:85:21
❯ ChangeSetPersister.executeInserts node_modules/@mikro-orm/core/unit-of-work/ChangeSetPersister.js:29:13
❯ ChangeSetPersister.runForEachSchema node_modules/@mikro-orm/core/unit-of-work/ChangeSetPersister.js:68:13
❯ UnitOfWork.commitCreateChangeSets node_modules/@mikro-orm/core/unit-of-work/UnitOfWork.js:739:9
❯ UnitOfWork.persistToDatabase node_modules/@mikro-orm/core/unit-of-work/UnitOfWork.js:703:13
❯ Parser.parseErrorMessage node_modules/pg-protocol/src/parser.ts:369:69
❯ Parser.handlePacket node_modules/pg-protocol/src/parser.ts:188:21
❯ Parser.parse node_modules/pg-protocol/src/parser.ts:103:30
❯ Socket.<anonymous> node_modules/pg-protocol/src/index.ts:7:48
❯ Socket.emit node:events:513:28
To Reproduce
Steps to reproduce the behavior:
- Clone the minimum reproducible code repository: https://github.com/thiagomini/nest-mikro-orm-example/tree/bug/read-only-association
- run
yarn
- run
docker compose up -d
- run
yarn test
Expected behavior
A new employee_detail
record should be created, given that we have the existing user
in the database already.
Additional context
You can look at the schemas in the repo linked, but here's both the employee
and employee_detail
schemas:
Employee Schema
import { BigIntType, EntitySchema } from '@mikro-orm/core';
import { Company } from './company.entity';
import { Employee } from './employee.entity';
import { EmployeeDetail } from './employee-detail.entity';
export const employeeSchema = new EntitySchema<Employee>({
class: Employee,
tableName: 'user',
forceConstructor: true,
properties: {
id: {
type: BigIntType,
primary: true,
autoincrement: true,
},
email: {
type: String,
persist: false
},
company: {
entity: () => Company,
reference: 'm:1',
ref: true,
persist: false
},
detail: {
entity: () => EmployeeDetail,
reference: '1:1',
mappedBy: 'employee',
}
},
});
EmployeeDetail Schema:
import { BigIntType, EntitySchema } from '@mikro-orm/core';
import { Company } from './company.entity';
import { Employee } from './employee.entity';
import { EmployeeDetail } from './employee-detail.entity';
export const employeeDetailSchema = new EntitySchema<EmployeeDetail>({
class: EmployeeDetail,
tableName: 'employee_detail',
forceConstructor: true,
properties: {
id: {
type: BigIntType,
primary: true,
autoincrement: true,
},
employee: {
entity: () => Employee,
reference: '1:1',
ref: true,
inversedBy: 'detail'
},
salary: {
type: Number
}
},
});
Versions
Dependency | Version |
---|---|
node | 18.12.1 |
typescript | 5.1.3 |
mikro-orm | next |
pg | 8.11.2 |