diff --git a/src/query-builder/SelectQueryBuilder.ts b/src/query-builder/SelectQueryBuilder.ts index 8cc5d404aa..1866c5f6eb 100644 --- a/src/query-builder/SelectQueryBuilder.ts +++ b/src/query-builder/SelectQueryBuilder.ts @@ -4327,10 +4327,20 @@ export class SelectQueryBuilder if (InstanceChecker.isFindOperator(where[key])) { if ( where[key].type === "moreThan" || - where[key].type === "lessThan" + where[key].type === "lessThan" || + where[key].type === "moreThanOrEqual" || + where[key].type === "lessThanOrEqual" ) { - const sqlOperator = - where[key].type === "moreThan" ? ">" : "<" + let sqlOperator = "" + if (where[key].type === "moreThan") { + sqlOperator = ">" + } else if (where[key].type === "lessThan") { + sqlOperator = "<" + } else if (where[key].type === "moreThanOrEqual") { + sqlOperator = ">=" + } else if (where[key].type === "lessThanOrEqual") { + sqlOperator = "<=" + } // basically relation count functionality const qb: QueryBuilder = this.subQuery() if (relation.isManyToManyOwner) { diff --git a/test/github-issues/9977/entity/Author.ts b/test/github-issues/9977/entity/Author.ts new file mode 100644 index 0000000000..e7aeef16c5 --- /dev/null +++ b/test/github-issues/9977/entity/Author.ts @@ -0,0 +1,20 @@ +import { Column, Entity, OneToMany, PrimaryColumn } from "../../../../src" +import { Photo } from "./Photo" + +@Entity() +export class Author { + @PrimaryColumn() + id: number + + @Column() + firstName: string + + @Column() + lastName: string + + @Column() + age: number + + @OneToMany(() => Photo, (photo) => photo.author) + photos: Photo[] +} diff --git a/test/github-issues/9977/entity/Counters.ts b/test/github-issues/9977/entity/Counters.ts new file mode 100644 index 0000000000..3d8d215376 --- /dev/null +++ b/test/github-issues/9977/entity/Counters.ts @@ -0,0 +1,11 @@ +import { Column, JoinTable, ManyToMany } from "../../../../src" +import { Author } from "./Author" + +export class Counters { + @Column() + likes: number + + @ManyToMany(() => Author) + @JoinTable() + likedUsers: Author[] +} diff --git a/test/github-issues/9977/entity/Photo.ts b/test/github-issues/9977/entity/Photo.ts new file mode 100644 index 0000000000..940b90bb87 --- /dev/null +++ b/test/github-issues/9977/entity/Photo.ts @@ -0,0 +1,17 @@ +import { Column, Entity, ManyToOne, PrimaryColumn } from "../../../../src" +import { Author } from "./Author" + +@Entity() +export class Photo { + @PrimaryColumn() + id: number + + @Column() + filename: string + + @Column() + description: string + + @ManyToOne(() => Author, (author) => author.photos) + author: Author +} diff --git a/test/github-issues/9977/entity/Post.ts b/test/github-issues/9977/entity/Post.ts new file mode 100644 index 0000000000..3466241bef --- /dev/null +++ b/test/github-issues/9977/entity/Post.ts @@ -0,0 +1,41 @@ +import { + Column, + Entity, + JoinTable, + ManyToMany, + ManyToOne, + PrimaryColumn, +} from "../../../../src" +import { Tag } from "./Tag" +import { Author } from "./Author" +import { Counters } from "./Counters" + +@Entity() +export class Post { + @PrimaryColumn() + id: number + + @Column() + title: string + + @Column() + text: string + + @ManyToMany(() => Tag, (tag) => tag.posts) + @JoinTable() + tags: Tag[] + + @ManyToOne(() => Author) + author: Author + + @Column(() => Counters) + counters: Counters + + toString() { + return this.title + } + + doSomething() { + return 123 + } +} diff --git a/test/github-issues/9977/entity/Tag.ts b/test/github-issues/9977/entity/Tag.ts new file mode 100644 index 0000000000..8f5277087f --- /dev/null +++ b/test/github-issues/9977/entity/Tag.ts @@ -0,0 +1,14 @@ +import { Column, Entity, ManyToMany, PrimaryColumn } from "../../../../src" +import { Post } from "./Post" + +@Entity() +export class Tag { + @PrimaryColumn() + id: number + + @Column() + name: string + + @ManyToMany(() => Post, (post) => post.tags) + posts: Post[] +} diff --git a/test/github-issues/9977/find-options-test-utils.ts b/test/github-issues/9977/find-options-test-utils.ts new file mode 100644 index 0000000000..ff0768b3e9 --- /dev/null +++ b/test/github-issues/9977/find-options-test-utils.ts @@ -0,0 +1,96 @@ +import "reflect-metadata" +import { EntityManager } from "../../../src" +import { Post } from "./entity/Post" +import { Author } from "./entity/Author" +import { Photo } from "./entity/Photo" +import { Tag } from "./entity/Tag" +import { Counters } from "./entity/Counters" + +export async function prepareData(manager: EntityManager) { + const photo1 = new Photo() + photo1.id = 1 + photo1.filename = "saw.jpg" + photo1.description = "Me and saw" + await manager.save(photo1) + + const photo2 = new Photo() + photo2.id = 2 + photo2.filename = "chain.jpg" + photo2.description = "Me and chain" + await manager.save(photo2) + + const user1 = new Author() + user1.id = 1 + user1.firstName = "Timber" + user1.lastName = "Saw" + user1.age = 25 + user1.photos = [photo1, photo2] + await manager.save(user1) + + const user2 = new Author() + user2.id = 2 + user2.firstName = "Gyro" + user2.lastName = "Copter" + user2.age = 52 + user2.photos = [] + await manager.save(user2) + + const tag1 = new Tag() + tag1.id = 1 + tag1.name = "category #1" + await manager.save(tag1) + + const tag2 = new Tag() + tag2.id = 2 + tag2.name = "category #2" + await manager.save(tag2) + + const tag3 = new Tag() + tag3.id = 3 + tag3.name = "category #3" + await manager.save(tag3) + + const post1 = new Post() + post1.id = 1 + post1.title = "Post #1" + post1.text = "About post #1" + post1.author = user1 + post1.tags = [tag1, tag2] + post1.counters = new Counters() + post1.counters.likes = 1 + post1.counters.likedUsers = [user1] + await manager.save(post1) + + const post2 = new Post() + post2.id = 2 + post2.title = "Post #2" + post2.text = "About post #2" + post2.author = user1 + post2.tags = [tag2] + post2.counters = new Counters() + post2.counters.likes = 2 + post2.counters.likedUsers = [user1, user2] + await manager.save(post2) + + const post3 = new Post() + post3.id = 3 + post3.title = "Post #3" + post3.text = "About post #3" + post3.author = user2 + post3.tags = [tag1] + post3.counters = new Counters() + post3.counters.likes = 1 + post3.counters.likedUsers = [user2] + await manager.save(post3) + + const post4 = new Post() + post4.id = 4 + post4.title = "Post #4" + post4.text = "About post #4" + post4.author = user1 + post4.tags = [] + post4.counters = new Counters() + post4.counters.likes = 1 + post4.counters.likedUsers = [user1] + await manager.save(post4) +} diff --git a/test/github-issues/9977/issue-9977.ts b/test/github-issues/9977/issue-9977.ts new file mode 100644 index 0000000000..8283425e5e --- /dev/null +++ b/test/github-issues/9977/issue-9977.ts @@ -0,0 +1,140 @@ +import "reflect-metadata" +import "../../utils/test-setup" +import { DataSource, LessThanOrEqual, MoreThanOrEqual } from "../../../src" +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../utils/test-utils" +import { Author } from "./entity/Author" +import { Post } from "./entity/Post" +import { Tag } from "./entity/Tag" +import { prepareData } from "./find-options-test-utils" + +describe("github issues > #9977", () => { + let connections: DataSource[] + before( + async () => + (connections = await createTestingConnections({ + __dirname, + })), + ) + beforeEach(() => reloadTestingDatabases(connections)) + after(() => closeTestingConnections(connections)) + + it("where relations with (More|Less)ThanOrEqual operators", () => + Promise.all( + connections.map(async (connection) => { + await prepareData(connection.manager) + + const posts1 = await connection + .createQueryBuilder(Post, "post") + .setFindOptions({ + where: { + tags: MoreThanOrEqual(2), + }, + }) + .getMany() + posts1.should.be.eql([ + { + id: 1, + title: "Post #1", + text: "About post #1", + counters: { likes: 1 }, + }, + ]) + + const posts2 = await connection + .createQueryBuilder(Post, "post") + .setFindOptions({ + where: { + tags: MoreThanOrEqual(1), + counters: { + likedUsers: MoreThanOrEqual(2), + }, + }, + }) + .getMany() + posts2.should.be.eql([ + { + id: 2, + title: "Post #2", + text: "About post #2", + counters: { likes: 2 }, + }, + ]) + + const posts3 = await connection + .createQueryBuilder(Post, "post") + .setFindOptions({ + where: { + author: { + photos: MoreThanOrEqual(2), + }, + }, + order: { + id: "asc", + }, + }) + .getMany() + posts3.should.be.eql([ + { + id: 1, + title: "Post #1", + text: "About post #1", + counters: { likes: 1 }, + }, + { + id: 2, + title: "Post #2", + text: "About post #2", + counters: { likes: 2 }, + }, + { + id: 4, + title: "Post #4", + text: "About post #4", + counters: { likes: 1 }, + }, + ]) + + const authors = await connection + .createQueryBuilder(Author, "author") + .setFindOptions({ + where: { + photos: MoreThanOrEqual(1), + }, + }) + .getMany() + authors.should.be.eql([ + { id: 1, firstName: "Timber", lastName: "Saw", age: 25 }, + ]) + + const tags1 = await connection + .createQueryBuilder(Tag, "tag") + .setFindOptions({ + where: { + posts: MoreThanOrEqual(2), + }, + order: { + id: "asc", + }, + }) + .getMany() + tags1.should.be.eql([ + { id: 1, name: "category #1" }, + { id: 2, name: "category #2" }, + ]) + + const tags2 = await connection + .createQueryBuilder(Tag, "tag") + .setFindOptions({ + where: { + posts: LessThanOrEqual(0), + }, + }) + .getMany() + tags2.should.be.eql([{ id: 3, name: "category #3" }]) + }), + )) +})