diff --git a/docs/find-options.md b/docs/find-options.md index 0c47e88c3..2fae333ab 100644 --- a/docs/find-options.md +++ b/docs/find-options.md @@ -606,7 +606,9 @@ SELECT * FROM "post" WHERE "titles" IN ('Go To Statement Considered Harmful', 'S ## Combining Advanced Options -Also you can combine these operators with `Not` operator: +Also you can combine these operators with below: + +- `Not` ```ts import { Not, MoreThan, Equal } from "typeorm" @@ -622,3 +624,19 @@ will execute following query: ```sql SELECT * FROM "post" WHERE NOT("likes" > 10) AND NOT("title" = 'About #2') ``` + +- `Or` + +```ts +import { Not, MoreThan, ILike } from "typeorm" + +const loadedPosts = await dataSource.getRepository(Post).findBy({ + title: Or(Equal("About #2"), ILike("About%")), +}) +``` + +will execute following query: + +```sql +SELECT * FROM "post" WHERE "title" = 'About #2' OR "title" ILIKE 'About%' +``` \ No newline at end of file diff --git a/src/find-options/FindOperatorType.ts b/src/find-options/FindOperatorType.ts index dfd03fef1..b51069f8e 100644 --- a/src/find-options/FindOperatorType.ts +++ b/src/find-options/FindOperatorType.ts @@ -20,3 +20,4 @@ export type FindOperatorType = | "arrayOverlap" | "and" | "jsonContains" + | "or" diff --git a/src/find-options/operator/Or.ts b/src/find-options/operator/Or.ts new file mode 100644 index 000000000..5105abf36 --- /dev/null +++ b/src/find-options/operator/Or.ts @@ -0,0 +1,5 @@ +import { FindOperator } from "../FindOperator" + +export function Or(...values: FindOperator[]): FindOperator { + return new FindOperator("or", values as any, true, true) +} diff --git a/src/index.ts b/src/index.ts index ace384d2a..68a6a6c3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,6 +67,7 @@ export * from "./decorator/Exclusion" export * from "./decorator/Generated" export * from "./decorator/EntityRepository" export * from "./find-options/operator/And" +export * from "./find-options/operator/Or" export * from "./find-options/operator/Any" export * from "./find-options/operator/ArrayContainedBy" export * from "./find-options/operator/ArrayContains" diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index 4fa1486e8..1a4a7d7c7 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -1127,6 +1127,8 @@ export abstract class QueryBuilder { )}` case "and": return condition.parameters.join(" AND ") + case "or": + return condition.parameters.join(" OR ") } throw new TypeError( @@ -1542,6 +1544,20 @@ export abstract class QueryBuilder { } else if (parameterValue.type === "and") { const values: FindOperator[] = parameterValue.value + return { + operator: parameterValue.type, + parameters: values.map((operator) => + this.createWhereConditionExpression( + this.getWherePredicateCondition( + aliasPath, + operator, + ), + ), + ), + } + } else if (parameterValue.type === "or") { + const values: FindOperator[] = parameterValue.value + return { operator: parameterValue.type, parameters: values.map((operator) => diff --git a/src/query-builder/WhereClause.ts b/src/query-builder/WhereClause.ts index 0ab126e33..299c9a278 100644 --- a/src/query-builder/WhereClause.ts +++ b/src/query-builder/WhereClause.ts @@ -18,6 +18,7 @@ type PredicateOperator = | "arrayOverlap" | "and" | "jsonContains" + | "or" export interface WherePredicateOperator { operator: PredicateOperator diff --git a/test/github-issues/10054/entity/Person.ts b/test/github-issues/10054/entity/Person.ts new file mode 100644 index 000000000..0267efd55 --- /dev/null +++ b/test/github-issues/10054/entity/Person.ts @@ -0,0 +1,13 @@ +import { Entity, Column, PrimaryGeneratedColumn } from "../../../../src" + +@Entity({ name: "person" }) +export class Person { + @PrimaryGeneratedColumn() + id: number + + @Column() + name: string + + @Column({ type: "int", nullable: true }) + age: number | null +} diff --git a/test/github-issues/10054/issue-10054.ts b/test/github-issues/10054/issue-10054.ts new file mode 100644 index 000000000..d81242f8a --- /dev/null +++ b/test/github-issues/10054/issue-10054.ts @@ -0,0 +1,65 @@ +import { Or, DataSource, ILike, Equal } from "../../../src" +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../utils/test-utils" +import { Person } from "./entity/Person" +import { expect } from "chai" + +describe("github issues > #10054 Nested 'Or' Condition/Operation Support in Repository Where condition", () => { + let dataSources: DataSource[] + + before(async () => { + dataSources = await createTestingConnections({ + entities: [Person], + enabledDrivers: ["postgres", "mysql"], + schemaCreate: true, + dropSchema: true, + }) + }) + + beforeEach(() => reloadTestingDatabases(dataSources)) + after(() => closeTestingConnections(dataSources)) + + it("should find person where name starts with foo or equal to jane", async () => { + await Promise.all( + dataSources.map(async (dataSource) => { + debugger + const foo = new Person() + foo.name = "Foo" + foo.age = null + + const john = new Person() + john.name = "John" + john.age = 11 + + const dave = new Person() + dave.name = "Jane" + dave.age = 12 + + const foobar = new Person() + foobar.name = "FooBar" + foobar.age = 14 + + await dataSource.manager.save([foo, john, dave, foobar]) + const persons = await dataSource.manager.find(Person, { + where: { + name: Or(ILike("foo%"), Equal("Jane")), + }, + }) + + expect(persons).to.have.length(3) + + expect(persons.find((user) => user.name === "Foo")).to.be.not + .undefined + + expect(persons.find((user) => user.name === "Jane")).to.be.not + .undefined + + expect(persons.find((user) => user.name === "FooBar")).to.be.not + .undefined + }), + ) + }) +})