Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enum not resolved if it is from an external file #6540

Closed
Ripper346 opened this issue Aug 10, 2020 · 5 comments
Closed

Enum not resolved if it is from an external file #6540

Ripper346 opened this issue Aug 10, 2020 · 5 comments
Assignees

Comments

@Ripper346
Copy link

Issue type:

[ ] question
[x] bug report
[ ] feature request
[ ] documentation issue

Database system/driver:

[ ] cordova
[ ] mongodb
[ ] mssql
[x] mysql / mariadb
[ ] oracle
[ ] postgres
[ ] cockroachdb
[ ] sqlite
[ ] sqljs
[ ] react-native
[ ] expo

TypeORM version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Steps to reproduce or a small repository showing the problem:

I am using nestjs, I have defined an enum in an entity and used there. I needed to use it also in another entity, however, using an import typeorm seems to not resolve it.

In my file order.entity.ts I define

export enum OrderStatus {
    placed = "placed",
    paid = "paid",
    confirmed = "confirmed",
    shipped = "shipped",
    completed = "completed",
    cancelled = "cancelled"
}

@Entity()
export class Order extends BaseEntity {
    @PrimaryGeneratedColumn("uuid")
    id: string

    @IsNotEmpty()
    @Column({ type: "enum", enum: OrderStatus })
    status: OrderStatus
}

and in the issue entity order-product.entity.ts I write:

import { OrderStatus } from "./order.entity";

@Entity()
export class OrderProduct extends BaseEntity {
    @PrimaryGeneratedColumn("increment")
    id: number

    @IsNotEmpty()
    @Column({ type: "enum", enum: OrderStatus })
    status: OrderStatus
}

The resulting query is CREATE TABLE `order_product` (`id` int NOT NULL AUTO_INCREMENT, `status` enum NOT NULL, PRIMARY KEY (`id`)) producing an sql error as the enum is not defined.
The error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'NOT NULL, PRIMARY KEY (`id`))' at line 1

Defining the enum also inside the orderProduct entity file the enum is resolved and the query is executed correctly.

@AlexMesser
Copy link
Collaborator

I've added a test, but I could not reproduce the issue. Can you please add a reproduction repo showing this erroneous behavior?

@Ripper346
Copy link
Author

Ripper346 commented May 17, 2021

@AlexMesser here is an example: https://stackblitz.com/edit/typeorm-not-working-enum. However, you can't run it online, you have to download it, there isn't a database connected (first time using stackblitz). The incriminated file is src/components/orders/order-product.entity.ts L32.
For what I saw, if the enum imported is also used in the entity definition where it is declared, then typeorm can create the column (my first post, maybe I oversimplified it when I copied from my project), otherwise it can't, as in the project example of this post

AlexMesser added a commit that referenced this issue May 18, 2021
* fix #7614

* updated test for #7541

* added test for #7647

* fixed failing test

* fixed failing test;
fixed `enumName` renaming;

* fixed failing test

* added test for #6540
@jeznag
Copy link

jeznag commented Jun 4, 2021

I can replicate this too.

Enum ActionStatus is causing issues. It works in Entity 1 but not if I import it and try and use it in Entity 2.

Entity 1:

import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, OneToMany } from 'typeorm'
import { EntityTimestamps } from './EntityTimestamps'
import { Village } from './Village'
import { ActionMeta } from './ActionMeta'

export enum ActionStatus {
    OPEN = 'OPEN',
    CLOSED = 'CLOSED'
}

@Entity()
export class Action extends EntityTimestamps {

    @PrimaryGeneratedColumn({ name: 'action_pk' })
    id!: number

    @ManyToOne(_type => Village, { nullable: false })
    @JoinColumn({ name: 'village_fk' })
    village!: Village

    @Column({ name: 'title', type: 'varchar', nullable: false, length: 150 })
    title!: string

    @Column({ name: 'description', type: 'varchar', nullable: true, length: 1000 })
    description!: string | null

    @Column({ name: 'priority', type: 'int', nullable: true })
    priority!: number | null

    @Column({ name: 'status', type: 'enum', enum: ActionStatus, nullable: false, default: ActionStatus.OPEN })
    status!: ActionStatus

    @Column({ name: 'opened_at', type: 'datetime', nullable: false })
    openedAt!: Date

    @Column({ name: 'updated_at', type: 'datetime', nullable: false })
    updatedAt!: Date

    @Column({ name: 'closed_at', type: 'datetime', nullable: true })
    closedAt!: Date | null

    @OneToMany(_type => ActionMeta, meta => meta.action)
    actionMeta!: ActionMeta[]
}

Entity 2:

import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn, Unique, Column } from 'typeorm'
import { EntityTimestamps } from './EntityTimestamps'
import { Node } from './Node'
import { Action, ActionStatus } from './Action'
import { Rule } from './Rule'

@Entity()
@Unique(['action', 'rule', 'node'])
export class ActionMeta extends EntityTimestamps {

    @PrimaryGeneratedColumn({ name: 'action_meta_pk' })
    id!: number

    @ManyToOne(_type => Action, action => action.actionMeta, { nullable: false })
    @JoinColumn({ name: 'action_fk' })
    action!: Action

    @ManyToOne(_type => Rule, { nullable: true })
    @JoinColumn({ name: 'rule_fk' })
    rule!: Rule | null

    @ManyToOne(_type => Node, { nullable: true })
    @JoinColumn({ name: 'node_fk' })
    node!: Node | null

    @Column({ name: 'status', type: 'enum', enum: ActionStatus, nullable: true })
    status!: ActionStatus

    @Column({ name: 'updated_at', type: 'datetime', nullable: false })
    updatedAt!: Date

    @Column({ name: 'closed_at', type: 'datetime', nullable: true })
    closedAt!: Date | null

    /**
     * Additional fields for the raw PKs to speed up filtering
     * Prevents needing to join relations
     */
    @Column({ name: 'rule_fk', type: 'int', nullable: true })
    ruleFk!: number | null

    @Column({ name: 'node_fk', type: 'int', nullable: true })
    nodeFk!: number | null
}

Fails with

 query failed: CREATE TABLE `action_meta` (`created_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), `action_meta_pk` int NOT NULL AUTO_INCREMENT, `status` enum NULL, `closed_at` datetime NULL, `rule_fk` int NULL, `node_fk` int NULL, `action_fk` int NOT NULL, UNIQUE INDEX `IDX_dbf5541a0d691c7d3a067bc014` (`action_fk`, `rule_fk`, `node_fk`), PRIMARY KEY (`action_meta_pk`)) ENGINE=InnoDB

      at Function.Object.<anonymous>.PlatformTools.logError (src/platform/PlatformTools.ts:231:17)

  console.log
    error: Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NULL, `closed_at` datetime NULL, `rule_fk` int NULL, `node_fk` int NULL, `action' at line 1

If I redefine the enum in entity 2, it works:

import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn, Unique, Column } from 'typeorm'
import { EntityTimestamps } from './EntityTimestamps'
import { Node } from './Node'
import { Action } from './Action'
import { Rule } from './Rule'

enum ActionStatus {
    OPEN = 'OPEN',
    CLOSED = 'CLOSED'
}

@Entity()
@Unique(['action', 'rule', 'node'])
export class ActionMeta extends EntityTimestamps {

    @PrimaryGeneratedColumn({ name: 'action_meta_pk' })
    id!: number

    @ManyToOne(_type => Action, action => action.actionMeta, { nullable: false })
    @JoinColumn({ name: 'action_fk' })
    action!: Action

    @ManyToOne(_type => Rule, { nullable: true })
    @JoinColumn({ name: 'rule_fk' })
    rule!: Rule | null

    @ManyToOne(_type => Node, { nullable: true })
    @JoinColumn({ name: 'node_fk' })
    node!: Node | null

    @Column({ name: 'status', type: 'enum', enum: ActionStatus, nullable: true })
    status!: ActionStatus

    @Column({ name: 'updated_at', type: 'datetime', nullable: false })
    updatedAt!: Date

    @Column({ name: 'closed_at', type: 'datetime', nullable: true })
    closedAt!: Date | null

    /**
     * Additional fields for the raw PKs to speed up filtering
     * Prevents needing to join relations
     */
    @Column({ name: 'rule_fk', type: 'int', nullable: true })
    ruleFk!: number | null

    @Column({ name: 'node_fk', type: 'int', nullable: true })
    nodeFk!: number | null
}

@cronnosli
Copy link

cronnosli commented Jul 14, 2021

This behaviour happens here as well.
While in version 0.2.29 this was not happening, recently we upgraded to 0.2.34 and it is impossible to run migration:generate without duplicating the enun in each entity that uses it.

I found that the isArraysEqual method on this case passes the first enum array as undefined, while the second one is correct. As undefined is different than an array this method fails.

This bug affects also
[X] postgres

@AlexMesser
Copy link
Collaborator

Okay, I checked @jeznag and know what is the issue. The issue is in JavaScript runtime behavior. You are circular referencing files, and ActionMeta is importing Action while Action imports ActionMeta. And unlucky enum is undefined in this circular mess. While classes somehow work (also using "hacks", like () => ClassDefinition), it looks like this is a dead case for enum. Such circular references always bring problems.

To ensure I'm right, just console.log() your enum inside ActionMeta after class definition.

This issue can't be resolved, because it's related to JavaScript runtime behavior. Solution: extract enum to a separate file with only enum(s) definitions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants