crud: 'WHERE' of the join query is generated "wrong"

Hello guys, I did a test using ManyToOne but I get the following error:

error: error: invalid reference to FROM-clause entry for table "comment"

The error occurs when I run the following route

GET http://localhost:3000/post/1/comment

Looking at the log, I saw the following QUERY:

SELECT "Comment"."id" AS "Comment_id", "Comment"."text" AS "Comment_text", "post"."id" AS "post_id", "post"."body" AS "post_body", "post"."commentsId" FROM "comment" "Comment" LEFT JOIN "post" "post" ON "post"."commentsId"="Comment"."id" WHERE (Comment.postId = $1)

And doing tests directly in the database and works with this SQL (I added the quotes on WHERE it)

SELECT "Comment"."id" AS "Comment_id", "Comment"."text" AS "Comment_text", "post"."id" AS "post_id", "post"."body" AS "post_body", "post"."commentsId" FROM "comment" "Comment" LEFT JOIN "post" "post" ON "post"."commentsId"="Comment"."id" WHERE ("Comment"."postId" = $1)

It is probably some wrong setting I made. I did some tests and research, both in TypeORM, and directly in the PG plugin but I was not successful.

follows some useful files

dependencies in package.json

  "dependencies": {
    "@nestjs/common": "^6.7.2",
    "@nestjs/core": "^6.7.2",
    "@nestjs/platform-express": "^6.7.2",
    "@nestjs/typeorm": "^6.2.0",
    "@nestjsx/crud": "^4.4.1",
    "@nestjsx/crud-typeorm": "^4.4.1",
    "class-transformer": "^0.2.3",
    "class-validator": "^0.11.0",
    "pg": "^7.17.1",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.0",
    "rxjs": "^6.5.3",
    "typeorm": "^0.2.22"
  },

Main Module

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostModule } from './post/post.module';
import { CommentModule } from './comment/comment.module';
import { Comment } from './comment/comment.entity';
import { Post } from './post/post.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'test',
      logging: true,
      entities: [
        Post, Comment
      ],
      synchronize: true,
    }),
    PostModule,
    CommentModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Entities

Post

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { Comment } from '../comment/comment.entity';

@Entity()
export class Post {

    @PrimaryGeneratedColumn()
    id: number

    @Column()
    body: string

    @ManyToOne(type => Comment, c => c.post)
    comments: Post[]
}

Comment

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Post } from '../post/post.entity';


@Entity()
export class Comment {

    @PrimaryGeneratedColumn()
    id: number

    @Column()
    text: string

    @OneToMany(type => Post, p => p.comments)
    post: Post
}

Modules

Post

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";

import { Comment } from "./comment.entity";
import { CommentService } from "./comment.service";
import { CommentController } from "./comment.controller";

@Module({
  imports: [TypeOrmModule.forFeature([Comment])],
  providers: [CommentService],
  exports: [CommentService],
  controllers: [CommentController]
})
export class CommentModule {}

Comment

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";

import { Post } from "./post.entity";
import { PostService } from "./post.service";
import { PostController } from "./post.controller";

@Module({
  imports: [TypeOrmModule.forFeature([Post])],
  providers: [PostService],
  exports: [PostService],
  controllers: [PostController]
})
export class PostModule {}

Services

Post

import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { TypeOrmCrudService } from "@nestjsx/crud-typeorm";

import { Comment } from "./comment.entity";

@Injectable()
export class CommentService extends TypeOrmCrudService<Comment> {
  constructor(@InjectRepository(Comment) repo) {
    super(repo);
  }
}

Comment

import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { TypeOrmCrudService } from "@nestjsx/crud-typeorm";

import { Comment } from "./comment.entity";

@Injectable()
export class CommentService extends TypeOrmCrudService<Comment> {
  constructor(@InjectRepository(Comment) repo) {
    super(repo);
  }
}

Controller

Post

import { Controller } from "@nestjs/common";
import { Crud, CrudController } from "@nestjsx/crud";

import { Comment } from "./comment.entity";
import { CommentService } from "./comment.service";

@Crud({
  model: {
    type: Comment
  },
  params: {
    id: {
        field: 'id',
        type: 'number',
        primary: true,
    },
      postId: {
          field: 'postId',
          type: 'number'
      },
    
  },
  query: {
    join: {
        post: {
            eager: true
        }
    }
  }
})
@Controller("post/:postId/comment")
export class CommentController implements CrudController<Comment> {
  constructor(public service: CommentService) {}
}

Comment

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

What am I doing wrong? I already appreciate the help šŸ˜„

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 3
  • Comments: 30 (10 by maintainers)

Most upvoted comments

hey sorry to comment in a closed ticket, i just updated nextjs crud and i’m having same issue in postgresql when filtering results in CrudAuth by a related column for example :

@CrudAuth({
  property: 'user',
  filter: (user: UserEntity): SCondition => {
    const { account } = user;
    const sCondition = [];
    sCondition.push({ createdBy: { $eq: account.id } });
    return {
      $or: sCondition,
    };
  },
})

generates this error

QueryFailedError: column Customer.createdById.id does not exist

But if i use

@CrudAuth({
  property: 'user',
  filter: (user: UserEntity): SCondition => {
    const { account } = user;
    const sCondition = [];
    sCondition.push({ 'Customer.createdBy': { $eq: account.id } });
    return {
      $or: sCondition,
    };
  },
})

works!

thanks

I’ll publish it today. Sorry for the delay

fixed in v4.6.0

@zMotivat0r, guys, I think this fix may have leaked over into mysql and into other things. I had a working simple filter that I haven’t touched in a week or two:

return axios.get<GameTheme[]>(`/game-theme?filter=is_public||$eq||false&sort=name,ASC`);

It’s hitting a pretty simple entity on the other end. I even commented out all the joins so it’s just a couple of primitive columns.

@Entity("game_themes")
export class GameTheme {
  @PrimaryGeneratedColumn()
  theme_id: number;

  ...

  @Column()
  is_public: boolean;
}

and all of a sudden I’m getting the following 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 '.ā€œis_publicā€ = 0) ORDER BY GameTheme.name ASC' at line 1’

and this is what typeorm is spitting out

query failed: SELECT `GameTheme`.`theme_id` AS `GameTheme_theme_id`, `GameTheme`.`name` AS `GameTheme_name`, `GameTheme`.`created_by` AS `GameTheme_created_by`, `GameTheme`.`created_on` AS `GameTheme_created_on`, `GameTheme`.`is_public` AS `GameTheme_is_public`, `GameTheme`.`background_url` AS `GameTheme_background_url`, `GameTheme`.`floater_ids` AS `GameTheme_floater_ids`, `GameTheme`.`container_style` AS `GameTheme_container_style`, `GameTheme`.`font_style` AS `GameTheme_font_style`, `GameTheme`.`item_style` AS `GameTheme_item_style`, `GameTheme`.`more_options` AS `GameTheme_more_options`, `GameTheme`.`icon` AS `GameTheme_icon` FROM `game_themes` `GameTheme` WHERE ("GameTheme"."is_public" = ?) ORDER BY `GameTheme`.`name` ASC -- PARAMETERS: [false]

notice the WHERE ("GameTheme"."is_public" = ?) - it’s in double quotes which is illegal in mysql. When I paste it into workbench and change the double quotes to backticks, it runs fine. I have a feeling this issue is the cause.

Update – the following function seems to be missing the quoting:

image

This line: https://github.com/nestjsx/crud/blob/76dc6c2bc841a9b6fdc68b730aa87043f327117b/packages/crud-typeorm/src/typeorm-crud.service.ts#L782

Not 100% sure the fix is on that line, or if it’s intended to delegate to the underlying library but adding the quotes here fixes it for me for postgres. I can try to put up a PR this weekend. Tagging: @zMotivat0r if you have a chance to peek at this.

Reminded by @mkelandis , I think I found the root cause. I read the code in packages/crud-typeorm/src/typeorm-crud.service.ts and find this function:

  protected getFieldWithAlias(field: string, sort: boolean = false) {
    /* istanbul ignore next */
    const i = this.dbName === 'mysql' ? '`' : '"';
    const cols = field.split('.');

    switch (cols.length) {
      case 1:
        if (sort) {
          return `${this.alias}.${field}`;
        }

        const dbColName =
          this.entityColumnsHash[field] !== field ? this.entityColumnsHash[field] : field;

        return `${i}${this.alias}${i}.${i}${dbColName}${i}`;
      case 2:
        return field;
      default:
        return cols.slice(cols.length - 2, cols.length).join('.');
    }
  }

in the first line we can see that the value of i is base on dbName, only check if the value of dbName is mysql or not, but in my project I set mariadb to the TypeOrmModuleOptions.type (ormconfig.json). I think both mysql and mariadb should be supported. Now I just change it to mysql then it works.

fixed in v4.6.1