Skip to content

andWhere does not isolate condition, causing unexpected query to be generated #7261

@pirtleshell

Description

@pirtleshell

Issue Description

Conditions in an andWhere are not isolated with parentheses. Thus, they can affect other conditions when building a query with a QueryBuilder that results in unexpected sql begin generated and unexpected results.

Expected Behavior

The condition argument to andWhere should get wrapped in parentheses in the generated SQL to ensure the entire condition is truly captured.

Suppose I have a blog with Posts by Users. I have Permissions that join Users to Posts they are allowed to read. There are also admin users (user.admin === true) who can view everything (even though they have no Permission entity).

const readablePostsInCategory = await db.createQueryBuilder(Post, "post")
  .leftJoin("post.permissions", "permission")
  .where("post.category = :category", { category })
  .andWhere(":userIsAdmin = TRUE OR permission.userId = :userId", { userIsAdmin: user.admin, userId: user.id })
  .getMany();

This should create sql with a where condition that looks like
WHERE post.category = $1 AND ($2 = TRUE OR permission.userId = $3)

Note the parentheses. Without them, the condition becomes
WHERE (post.category = $1 AND $2 = TRUE) OR permission.userId = $3

Actual Behavior

The andWhere condition is not isolated, and so the OR inside it overrides the first where condition.

It generates a where condition that looks like
WHERE post.category = $1 AND $2 = TRUE OR permission.userId = $3

Note the lack of parentheses around the andWhere condition. This results in a bypass of the first where (post.category = $1) if permission.userId = $3.

Steps to Reproduce

  1. create entities
  2. generate a query with an andWhere containing an OR.
  3. notice the condition is not isolated with parentheses and the result set is incorrect b/c of it.
createConnection()
  .then(async (connection) => {

    // this query builder generates an incorrect query
    const sql = connection
      .createQueryBuilder(Post, 'post')
      .leftJoin('post.permissions', 'permission')
      .where('post.category = :category', { category: "code" })
      .andWhere(':userIsAdmin = TRUE OR permission.userId = :userId', {
        userIsAdmin: false,
        userId: 1,
      })
      .getSql();

    console.log(sql);
  })
  .catch((error) => console.log(error));

// entities
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  admin: boolean;

  @Column()
  name: string;
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  content: string;

  @Column()
  category: string;

  @OneToMany(() => Permission, (p) => p.post)
  permissions: Permission[];
}


@Entity()
export class Permission {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => User)
  user: User;

  @Column()
  userId: number;

  @ManyToOne(() => Post, (p) => p.permissions)
  post: Post;

  @Column()
  postId: number;
}

The above outputs

SELECT "post"."id" AS "post_id", "post"."content" AS "post_content" 
FROM "post" "post" 
LEFT JOIN "permission" "permission" ON "permission"."postId"="post"."id" 
WHERE post.category = ? AND ? = TRUE OR "permission"."userId" = ?  -- <--- no parentheses here

but should output

SELECT "post"."id" AS "post_id", "post"."content" AS "post_content" 
FROM "post" "post" 
LEFT JOIN "permission" "permission" ON "permission"."postId"="post"."id" 
WHERE post.category = ? AND (? = TRUE OR "permission"."userId" = ?)

My Environment

Dependency Version
Operating System
Node.js version v14.9.0
Typescript version v4.1.3
TypeORM version v0.2.25

Additional Context

Relevant Database Driver(s)

  • aurora-data-api
  • aurora-data-api-pg
  • better-sqlite3
  • cockroachdb
  • cordova
  • expo
  • mongodb
  • mysql
  • nativescript
  • oracle
  • postgres
  • react-native
  • sap
  • sqlite
  • sqlite-abstract
  • sqljs
  • sqlserver

Are you willing to resolve this issue by submitting a Pull Request?

  • Yes, I have the time, and I know how to start.
  • Yes, I have the time, but I don't know how to start. I would need guidance.
  • No, I don't have the time, although I believe I could do it if I had the time...
  • No, I don't have the time and I wouldn't even know how to start.

Thanks for your time! Great project here!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions