Skip to content

Commit

Permalink
fix: support More/LessThanOrEqual in relations (#9978)
Browse files Browse the repository at this point in the history
* test(select-buildwhere): exercise More/LessThanOrEqual for relations

Extract the relevant test suite from functional/find-options, modifying
the tests to use the equivalent More/LessThanOrEqual operators

* fix(select-buildwhere): support More/LessThanOrEqual for relations

Tweak `buildWhere()` to recognise more find operators and output
the SQL equivalent

- Recognise `moreThanOrEqual` and `lessThanOrEqual`, in addition to
  `moreThan` and `lessThan`
- If the find operator ends with `OrEqual`, append `=` to the
  SQL operator

* code style change

---------

Co-authored-by: Umed Khudoiberdiev <pleerock.me@gmail.com>
  • Loading branch information
LoneRifle and pleerock committed May 9, 2023
1 parent 06c1e98 commit 8795c86
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 3 deletions.
16 changes: 13 additions & 3 deletions src/query-builder/SelectQueryBuilder.ts
Expand Up @@ -4327,10 +4327,20 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
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<any> = this.subQuery()
if (relation.isManyToManyOwner) {
Expand Down
20 changes: 20 additions & 0 deletions 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[]
}
11 changes: 11 additions & 0 deletions 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[]
}
17 changes: 17 additions & 0 deletions 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
}
41 changes: 41 additions & 0 deletions 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
}
}
14 changes: 14 additions & 0 deletions 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[]
}
96 changes: 96 additions & 0 deletions 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)
}
140 changes: 140 additions & 0 deletions 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" }])
}),
))
})

0 comments on commit 8795c86

Please sign in to comment.