Search code examples
postgresqlnestjstypeorm

TypeORM PostgreSQL @ManyToOne save violates not-null constraint


I have a basic nestjs app with 3 entities :

  • document: DocumentEntity has pages: PageEntity[] as @OneToMany relation
  • page: PageEntity has words: WordEntity[]as @OneToMany relation + @ManyToOne document
  • word: WordEntity has a @ManyToOne page: PageEntity

This is quite straightforward and the first part of those relations works as expected : I can save new pages doing so :

  async createPage(documentId: number, url: string) {
    const document = await this.documentRepository.findOne({ id: documentId });
    if (document) {
      const pageEntity = new PageEntity();
      pageEntity.imgUrl = url;
      pageEntity.document = document;
      await this.pageRepository.save(pageEntity);
    }
  }

but when I try to apply the same logic to the words/page relation, it fails. I m not sure why this behaves differently

async postWord(pageId: number, word: { text: string }) {
    const page = await this.pageRepository.findOne({ id: pageId });
    if (page) {
      const wordEntity = new WordEntity();
      wordEntity.text = word.text;
      wordEntity.page = page;
      await this.wordRepository.save(wordEntity);
    }
  }

Error Message :

[ExceptionsHandler] null value in column "pageId" of relation "word_entity" violates not-null constraint +107723ms
QueryFailedError: null value in column "pageId" of relation "word_entity" violates not-null constraint

here are the entities declarations :

// document.entity.ts

@Entity()
class DocumentEntity {
  @PrimaryGeneratedColumn()
  public id?: number;

  @Column()
  public name: string;

  @Column()
  public locale: string;

  @Column()
  public pdfUrl: string;

  @Column()
  public folderPath: string;

  @OneToMany(() => PageEntity, (page) => page.document, {
    primary: true,
    eager: true,
    cascade: true,
  })
  public pages?: PageEntity[];
}

export default DocumentEntity;

// page.entity.ts

@Entity()
class PageEntity {
  @PrimaryGeneratedColumn()
  public id?: number;

  @Column({ nullable: true })
  public pageNumber?: number;

  @Column({ nullable: true })
  public text?: string;

  @Column()
  public imgUrl: string;

  @OneToMany(() => WordEntity, (word) => word.page, {
    eager: true,
    onDelete: 'CASCADE',
    primary: true,
  })
  words?: WordEntity[];

  @ManyToOne(() => DocumentEntity, {
    primary: true,
    onDelete: 'CASCADE',
  })
  @JoinColumn()
  public document: DocumentEntity;
}

export default PageEntity;

// word.entity.ts
@Entity()
class WordEntity {
  @PrimaryGeneratedColumn()
  public id?: number;

  @ManyToOne(() => PageEntity, {
    nullable: true,
    primary: true,
    onDelete: 'CASCADE',
  })
  @JoinColumn()
  public page!: PageEntity;

  @Column()
  public text: string;

  @Column({ type: 'decimal', nullable: true })
  public confidence?: number;
}



Solution

  • Try this:

    @Entity()
    class WordEntity {
      .....
    
      @ManyToOne(() => PageEntity, {
        nullable: true,
        primary: true,
        onDelete: 'CASCADE',
      })
      @JoinColumn({
        name: 'pageId',
        referencedColumnName: 'id',
      })
      page?: PageEntity;
    
      @Column({ nullable: true })
      pageId?: number
    
      .....
    
    }
    
    
    async postWord(pageId: number, word: { text: string }) {
          const wordEntity = new WordEntity();
          wordEntity.text = word.text;
          // check if the pageId exists, maybe inside Dto with a decorator
          wordEntity.pageId = pageId;
          await this.wordRepository.save(wordEntity);
      
      }