Skip to content

Cannot persist Read-Only association #141

@thiagomini

Description

@thiagomini

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 the User (and resides in the same table)
  • And we have an EmployeeDetail class and entity with a One-To-One reference to Employee
  • And we persist a new user
  • When we try to create and persist a new EmployeeDetailentity, passing the employee 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
    }
Loading

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);
  }
}

⚠️ When we try to create a new 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:

  1. Clone the minimum reproducible code repository: https://github.com/thiagomini/nest-mikro-orm-example/tree/bug/read-only-association
  2. run yarn
  3. run docker compose up -d
  4. 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions