mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
feat: new array find operators (#8766)
* added new array find operators; delegating how json and jsonb are stored directly to underlying driver (breaking) * array: true must be explicitly defined for array json/jsonb values; strings being JSON-stringified must be manually escpaed
This commit is contained in:
parent
d61f857ce9
commit
9f1b8e3425
@ -426,6 +426,13 @@ This change was required to simplify ORM internals and introduce new features.
|
||||
|
||||
* issue with non-reliable `new Date(ISOString)` parsing ([#7796](https://github.com/typeorm/typeorm/pull/7796))
|
||||
|
||||
* true JSON / JSONB support - manual `JSON.stringify` was removed,
|
||||
instead object handled by underlying driver. This opens ability to properly work with json/jsonb structures,
|
||||
but brings few breaking changes:
|
||||
|
||||
* `array: true` must be explicitly defined for array json/jsonb values
|
||||
* strings being JSON-stringified must be manually escaped
|
||||
|
||||
### DEPRECATIONS
|
||||
|
||||
* all CLI commands do not support `ormconfig` anymore. You must specify a file with data source instance instead.
|
||||
|
||||
@ -488,6 +488,54 @@ will execute following query:
|
||||
SELECT * FROM "post" WHERE "title" IS NULL
|
||||
```
|
||||
|
||||
- `ArrayContains`
|
||||
|
||||
```ts
|
||||
import { ArrayContains } from "typeorm"
|
||||
|
||||
const loadedPosts = await dataSource.getRepository(Post).findBy({
|
||||
categories: ArrayContains(["TypeScript"]),
|
||||
})
|
||||
```
|
||||
|
||||
will execute following query:
|
||||
|
||||
```sql
|
||||
SELECT * FROM "post" WHERE "categories" @> '{TypeScript}'
|
||||
```
|
||||
|
||||
- `ArrayContainedBy`
|
||||
|
||||
```ts
|
||||
import { ArrayContainedBy } from "typeorm"
|
||||
|
||||
const loadedPosts = await dataSource.getRepository(Post).findBy({
|
||||
categories: ArrayContainedBy(["TypeScript"]),
|
||||
})
|
||||
```
|
||||
|
||||
will execute following query:
|
||||
|
||||
```sql
|
||||
SELECT * FROM "post" WHERE "categories" <@ '{TypeScript}'
|
||||
```
|
||||
|
||||
- `ArrayOverlap`
|
||||
|
||||
```ts
|
||||
import { ArrayOverlap } from "typeorm"
|
||||
|
||||
const loadedPosts = await dataSource.getRepository(Post).findBy({
|
||||
categories: ArrayOverlap(["TypeScript"]),
|
||||
})
|
||||
```
|
||||
|
||||
will execute following query:
|
||||
|
||||
```sql
|
||||
SELECT * FROM "post" WHERE "categories" && '{TypeScript}'
|
||||
```
|
||||
|
||||
- `Raw`
|
||||
|
||||
```ts
|
||||
|
||||
@ -622,7 +622,7 @@ export class PostgresDriver implements Driver {
|
||||
columnMetadata.type,
|
||||
) >= 0
|
||||
) {
|
||||
return JSON.stringify(value)
|
||||
return value // JSON.stringify()
|
||||
} else if (columnMetadata.type === "hstore") {
|
||||
if (typeof value === "string") {
|
||||
return value
|
||||
|
||||
@ -15,3 +15,6 @@ export type FindOperatorType =
|
||||
| "ilike"
|
||||
| "like"
|
||||
| "raw"
|
||||
| "arrayContains"
|
||||
| "arrayContainedBy"
|
||||
| "arrayOverlap"
|
||||
|
||||
11
src/find-options/operator/ArrayContainedBy.ts
Normal file
11
src/find-options/operator/ArrayContainedBy.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { FindOperator } from "../FindOperator"
|
||||
|
||||
/**
|
||||
* FindOptions Operator.
|
||||
* Example: { someField: ArrayContainedBy([...]) }
|
||||
*/
|
||||
export function ArrayContainedBy<T>(
|
||||
value: T[] | FindOperator<T>,
|
||||
): FindOperator<any> {
|
||||
return new FindOperator("arrayContainedBy", value as any)
|
||||
}
|
||||
11
src/find-options/operator/ArrayContains.ts
Normal file
11
src/find-options/operator/ArrayContains.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { FindOperator } from "../FindOperator"
|
||||
|
||||
/**
|
||||
* FindOptions Operator.
|
||||
* Example: { someField: ArrayContains([...]) }
|
||||
*/
|
||||
export function ArrayContains<T>(
|
||||
value: T[] | FindOperator<T>,
|
||||
): FindOperator<any> {
|
||||
return new FindOperator("arrayContains", value as any)
|
||||
}
|
||||
11
src/find-options/operator/ArrayOverlap.ts
Normal file
11
src/find-options/operator/ArrayOverlap.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { FindOperator } from "../FindOperator"
|
||||
|
||||
/**
|
||||
* FindOptions Operator.
|
||||
* Example: { someField: ArrayOverlap([...]) }
|
||||
*/
|
||||
export function ArrayOverlap<T>(
|
||||
value: T[] | FindOperator<T>,
|
||||
): FindOperator<any> {
|
||||
return new FindOperator("arrayOverlap", value as any)
|
||||
}
|
||||
@ -993,6 +993,12 @@ export abstract class QueryBuilder<Entity> {
|
||||
return `${condition.parameters[0]} < ${condition.parameters[1]}`
|
||||
case "lessThanOrEqual":
|
||||
return `${condition.parameters[0]} <= ${condition.parameters[1]}`
|
||||
case "arrayContains":
|
||||
return `${condition.parameters[0]} @> ${condition.parameters[1]}`
|
||||
case "arrayContainedBy":
|
||||
return `${condition.parameters[0]} <@ ${condition.parameters[1]}`
|
||||
case "arrayOverlap":
|
||||
return `${condition.parameters[0]} && ${condition.parameters[1]}`
|
||||
case "moreThan":
|
||||
return `${condition.parameters[0]} > ${condition.parameters[1]}`
|
||||
case "moreThanOrEqual":
|
||||
|
||||
@ -13,6 +13,9 @@ type PredicateOperator =
|
||||
| "in"
|
||||
| "any"
|
||||
| "isNull"
|
||||
| "arrayContains"
|
||||
| "arrayContainedBy"
|
||||
| "arrayOverlap"
|
||||
|
||||
export interface WherePredicateOperator {
|
||||
operator: PredicateOperator
|
||||
|
||||
@ -16,7 +16,7 @@ export class Document {
|
||||
@Column("text")
|
||||
context: string
|
||||
|
||||
@Column({ type: "jsonb" })
|
||||
@Column({ type: "jsonb", array: true })
|
||||
distributions: Distribution[]
|
||||
|
||||
@Column({ type: "timestamp with time zone" })
|
||||
|
||||
@ -0,0 +1,253 @@
|
||||
import "../../../utils/test-setup"
|
||||
import { DataSource, EntityManager } from "../../../../src"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../../utils/test-utils"
|
||||
import { Post, PostStatus } from "./entity/Post"
|
||||
import { ArrayContainedBy } from "../../../../src/find-options/operator/ArrayContainedBy"
|
||||
|
||||
describe("find options > find operators > ArrayContainedBy", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
__dirname,
|
||||
enabledDrivers: ["postgres"],
|
||||
// logging: true,
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
async function prepareData(manager: EntityManager) {
|
||||
const post1 = new Post()
|
||||
post1.title = "Post #1"
|
||||
post1.authors = ["dmitry", "olimjon"]
|
||||
post1.categories = [{ name: "typescript" }, { name: "programming" }]
|
||||
post1.statuses = [PostStatus.draft, PostStatus.published]
|
||||
await manager.save(post1)
|
||||
|
||||
const post2 = new Post()
|
||||
post2.title = "Post #2"
|
||||
post2.authors = ["olimjon"]
|
||||
post2.categories = [{ name: "programming" }]
|
||||
post2.statuses = [PostStatus.published]
|
||||
await manager.save(post2)
|
||||
|
||||
const post3 = new Post()
|
||||
post3.title = "Post #3"
|
||||
post3.authors = []
|
||||
post3.categories = []
|
||||
post3.statuses = []
|
||||
await manager.save(post3)
|
||||
}
|
||||
|
||||
it("should find entries in regular arrays", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
authors: ArrayContainedBy(["dmitry", "olimjon"]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Post #3",
|
||||
authors: [],
|
||||
categories: [],
|
||||
statuses: [],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
authors: ArrayContainedBy(["olimjon"]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Post #3",
|
||||
authors: [],
|
||||
categories: [],
|
||||
statuses: [],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
|
||||
it("should find entries in jsonb", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
categories: ArrayContainedBy([
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Post #3",
|
||||
authors: [],
|
||||
categories: [],
|
||||
statuses: [],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
categories: ArrayContainedBy([{ name: "programming" }]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Post #3",
|
||||
authors: [],
|
||||
categories: [],
|
||||
statuses: [],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
|
||||
it("should find entries in enum arrays", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
statuses: ArrayContainedBy([
|
||||
PostStatus.draft,
|
||||
PostStatus.published,
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Post #3",
|
||||
authors: [],
|
||||
categories: [],
|
||||
statuses: [],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
statuses: ArrayContainedBy([PostStatus.published]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Post #3",
|
||||
authors: [],
|
||||
categories: [],
|
||||
statuses: [],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
})
|
||||
@ -0,0 +1,39 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../../src"
|
||||
|
||||
export type PostCategory = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export enum PostStatus {
|
||||
draft = "draft",
|
||||
published = "published",
|
||||
unknown = "unknown",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Post {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
title: string
|
||||
|
||||
@Column({
|
||||
type: "varchar",
|
||||
array: true,
|
||||
})
|
||||
authors: string[]
|
||||
|
||||
@Column({
|
||||
type: "jsonb",
|
||||
array: true,
|
||||
})
|
||||
categories: PostCategory[]
|
||||
|
||||
@Column({
|
||||
type: "enum",
|
||||
enum: PostStatus,
|
||||
array: true,
|
||||
})
|
||||
statuses: PostStatus[]
|
||||
}
|
||||
@ -0,0 +1,214 @@
|
||||
import "../../../utils/test-setup"
|
||||
import { DataSource, EntityManager } from "../../../../src"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../../utils/test-utils"
|
||||
import { Post, PostStatus } from "./entity/Post"
|
||||
import { ArrayContains } from "../../../../src/find-options/operator/ArrayContains"
|
||||
|
||||
describe("find options > find operators > ArrayContains", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
__dirname,
|
||||
enabledDrivers: ["postgres"],
|
||||
logging: true,
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
async function prepareData(manager: EntityManager) {
|
||||
const post1 = new Post()
|
||||
post1.title = "Post #1"
|
||||
post1.authors = ["dmitry", "olimjon"]
|
||||
post1.categories = [{ name: "typescript" }, { name: "programming" }]
|
||||
post1.statuses = [PostStatus.draft, PostStatus.published]
|
||||
await manager.save(post1)
|
||||
|
||||
const post2 = new Post()
|
||||
post2.title = "Post #2"
|
||||
post2.authors = ["olimjon"]
|
||||
post2.categories = [{ name: "programming" }]
|
||||
post2.statuses = [PostStatus.published]
|
||||
await manager.save(post2)
|
||||
|
||||
const post3 = new Post()
|
||||
post3.title = "Post #3"
|
||||
post3.authors = []
|
||||
post3.categories = []
|
||||
post3.statuses = []
|
||||
await manager.save(post3)
|
||||
}
|
||||
|
||||
it("should find entries in regular arrays", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
authors: ArrayContains(["dmitry"]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
authors: ArrayContains(["olimjon"]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
|
||||
it("should find entries in jsonb", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
categories: ArrayContains([{ name: "typescript" }]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
categories: ArrayContains([{ name: "programming" }]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
|
||||
it("should find entries in enum arrays", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
statuses: ArrayContains([PostStatus.draft]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
statuses: ArrayContains([PostStatus.published]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
})
|
||||
@ -0,0 +1,39 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../../src"
|
||||
|
||||
export type PostCategory = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export enum PostStatus {
|
||||
draft = "draft",
|
||||
published = "published",
|
||||
unknown = "unknown",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Post {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
title: string
|
||||
|
||||
@Column({
|
||||
type: "varchar",
|
||||
array: true,
|
||||
})
|
||||
authors: string[]
|
||||
|
||||
@Column({
|
||||
type: "jsonb",
|
||||
array: true,
|
||||
})
|
||||
categories: PostCategory[]
|
||||
|
||||
@Column({
|
||||
type: "enum",
|
||||
enum: PostStatus,
|
||||
array: true,
|
||||
})
|
||||
statuses: PostStatus[]
|
||||
}
|
||||
@ -0,0 +1,226 @@
|
||||
import "../../../utils/test-setup"
|
||||
import { DataSource, EntityManager } from "../../../../src"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../../utils/test-utils"
|
||||
import { Post, PostStatus } from "./entity/Post"
|
||||
import { ArrayOverlap } from "../../../../src/find-options/operator/ArrayOverlap"
|
||||
|
||||
describe("find options > find operators > ArrayOverlap", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
__dirname,
|
||||
enabledDrivers: ["postgres"],
|
||||
// logging: true,
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
async function prepareData(manager: EntityManager) {
|
||||
const post1 = new Post()
|
||||
post1.title = "Post #1"
|
||||
post1.authors = ["dmitry", "olimjon"]
|
||||
post1.categories = [{ name: "typescript" }, { name: "programming" }]
|
||||
post1.statuses = [PostStatus.draft, PostStatus.published]
|
||||
await manager.save(post1)
|
||||
|
||||
const post2 = new Post()
|
||||
post2.title = "Post #2"
|
||||
post2.authors = ["olimjon"]
|
||||
post2.categories = [{ name: "programming" }]
|
||||
post2.statuses = [PostStatus.published]
|
||||
await manager.save(post2)
|
||||
|
||||
const post3 = new Post()
|
||||
post3.title = "Post #3"
|
||||
post3.authors = []
|
||||
post3.categories = []
|
||||
post3.statuses = []
|
||||
await manager.save(post3)
|
||||
}
|
||||
|
||||
it("should find entries in regular arrays", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
authors: ArrayOverlap(["dmitry", "umed"]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
authors: ArrayOverlap(["olimjon", "umed"]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
|
||||
it("should find entries in jsonb", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
categories: ArrayOverlap([
|
||||
{ name: "typescript" },
|
||||
{ name: "python" },
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
categories: ArrayOverlap([
|
||||
{ name: "programming" },
|
||||
{ name: "python" },
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
|
||||
it("should find entries in enum arrays", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await prepareData(connection.manager)
|
||||
|
||||
const loadedPost1 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
statuses: ArrayOverlap([
|
||||
PostStatus.draft,
|
||||
PostStatus.unknown,
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost1.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
])
|
||||
|
||||
const loadedPost2 = await connection.manager.find(Post, {
|
||||
where: {
|
||||
statuses: ArrayOverlap([
|
||||
PostStatus.published,
|
||||
PostStatus.unknown,
|
||||
]),
|
||||
},
|
||||
order: {
|
||||
id: "asc",
|
||||
},
|
||||
})
|
||||
loadedPost2.should.be.eql([
|
||||
{
|
||||
id: 1,
|
||||
title: "Post #1",
|
||||
authors: ["dmitry", "olimjon"],
|
||||
categories: [
|
||||
{ name: "typescript" },
|
||||
{ name: "programming" },
|
||||
],
|
||||
statuses: [PostStatus.draft, PostStatus.published],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Post #2",
|
||||
authors: ["olimjon"],
|
||||
categories: [{ name: "programming" }],
|
||||
statuses: [PostStatus.published],
|
||||
},
|
||||
])
|
||||
}),
|
||||
))
|
||||
})
|
||||
39
test/functional/find-options/array-overlap/entity/Post.ts
Normal file
39
test/functional/find-options/array-overlap/entity/Post.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../../src"
|
||||
|
||||
export type PostCategory = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export enum PostStatus {
|
||||
draft = "draft",
|
||||
published = "published",
|
||||
unknown = "unknown",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Post {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
title: string
|
||||
|
||||
@Column({
|
||||
type: "varchar",
|
||||
array: true,
|
||||
})
|
||||
authors: string[]
|
||||
|
||||
@Column({
|
||||
type: "jsonb",
|
||||
array: true,
|
||||
})
|
||||
categories: PostCategory[]
|
||||
|
||||
@Column({
|
||||
type: "enum",
|
||||
enum: PostStatus,
|
||||
array: true,
|
||||
})
|
||||
statuses: PostStatus[]
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
|
||||
import { Column } from "../../../../src/decorator/columns/Column"
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src"
|
||||
|
||||
/**
|
||||
* For testing Postgres jsonb
|
||||
@ -16,6 +14,9 @@ export class Record {
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
data: any
|
||||
|
||||
@Column({ type: "jsonb", nullable: true, array: true })
|
||||
data2: any
|
||||
|
||||
@Column({
|
||||
type: "jsonb",
|
||||
nullable: true,
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import "reflect-metadata"
|
||||
import "../../utils/test-setup"
|
||||
import { expect } from "chai"
|
||||
import { Record } from "./entity/Record"
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import { DataSource } from "../../../src"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
|
||||
describe("jsonb type", () => {
|
||||
@ -14,9 +15,10 @@ describe("jsonb type", () => {
|
||||
(connections = await createTestingConnections({
|
||||
entities: [Record],
|
||||
enabledDrivers: ["postgres"], // because only postgres supports jsonb type
|
||||
logging: true,
|
||||
})),
|
||||
)
|
||||
// beforeEach(() => reloadTestingDatabases(connections));
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
it("should make correct schema with Postgres' jsonb type", () =>
|
||||
@ -84,7 +86,7 @@ describe("jsonb type", () => {
|
||||
connections.map(async (connection) => {
|
||||
let recordRepo = connection.getRepository(Record)
|
||||
let record = new Record()
|
||||
record.data = "foo"
|
||||
record.data = `"foo"`
|
||||
let persistedRecord = await recordRepo.save(record)
|
||||
let foundRecord = await recordRepo.findOneBy({
|
||||
id: persistedRecord.id,
|
||||
@ -100,13 +102,13 @@ describe("jsonb type", () => {
|
||||
connections.map(async (connection) => {
|
||||
let recordRepo = connection.getRepository(Record)
|
||||
let record = new Record()
|
||||
record.data = [1, "2", { a: 3 }]
|
||||
record.data2 = [1, `"2"`, { a: 3 }]
|
||||
let persistedRecord = await recordRepo.save(record)
|
||||
let foundRecord = await recordRepo.findOneBy({
|
||||
id: persistedRecord.id,
|
||||
})
|
||||
expect(foundRecord).to.be.not.undefined
|
||||
expect(foundRecord!.data).to.deep.include.members([
|
||||
expect(foundRecord!.data2).to.deep.include.members([
|
||||
1,
|
||||
"2",
|
||||
{ a: 3 },
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import "reflect-metadata"
|
||||
import "../../utils/test-setup"
|
||||
import {
|
||||
createTestingConnections,
|
||||
closeTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import { DataSource } from "../../../src"
|
||||
import { expect } from "chai"
|
||||
import { Record } from "./entity/Record"
|
||||
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
|
||||
import { Column } from "../../../../src/decorator/columns/Column"
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src"
|
||||
import { RecordData } from "./RecordData"
|
||||
import { RecordConfig } from "./RecordConfig"
|
||||
|
||||
@ -9,9 +7,9 @@ export class Record {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column({ type: "json" })
|
||||
@Column({ type: "json", array: true })
|
||||
configs: RecordConfig[]
|
||||
|
||||
@Column({ type: "jsonb" })
|
||||
@Column({ type: "jsonb", array: true })
|
||||
datas: RecordData[]
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "reflect-metadata"
|
||||
import "../../utils/test-setup"
|
||||
import { expect } from "chai"
|
||||
import { Record } from "./entity/Record"
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import { DataSource } from "../../../src"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
|
||||
@ -10,6 +10,6 @@ export class Post {
|
||||
@VersionColumn()
|
||||
version: number
|
||||
|
||||
@Column({ type: "jsonb" })
|
||||
@Column({ type: "jsonb", array: true })
|
||||
problems: object
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "reflect-metadata"
|
||||
import "../../utils/test-setup"
|
||||
import { DataSource } from "../../../src"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import "reflect-metadata"
|
||||
import "../../utils/test-setup"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import { DataSource } from "../../../src"
|
||||
import { Post } from "./entity/Post"
|
||||
import { expect } from "chai"
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "reflect-metadata"
|
||||
import "../../utils/test-setup"
|
||||
import { expect } from "chai"
|
||||
import { DataSource } from "../../../src"
|
||||
import {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import "reflect-metadata"
|
||||
import "../../utils/test-setup"
|
||||
import {
|
||||
createTestingConnections,
|
||||
closeTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import { DataSource } from "../../../src"
|
||||
import { expect } from "chai"
|
||||
import { DummyJSONEntity } from "./entity/json-entity"
|
||||
import { DummyJSONBEntity } from "./entity/jsonb-entity"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user