diff --git a/CHANGELOG.md b/CHANGELOG.md index f78c2a78f0..255376cbe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/docs/find-options.md b/docs/find-options.md index 407f216f94..168b8fa18d 100644 --- a/docs/find-options.md +++ b/docs/find-options.md @@ -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 diff --git a/src/driver/postgres/PostgresDriver.ts b/src/driver/postgres/PostgresDriver.ts index d63170836a..7c4e9e2ef6 100644 --- a/src/driver/postgres/PostgresDriver.ts +++ b/src/driver/postgres/PostgresDriver.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 diff --git a/src/find-options/FindOperatorType.ts b/src/find-options/FindOperatorType.ts index 0a4b22cbe3..edda3a2f78 100644 --- a/src/find-options/FindOperatorType.ts +++ b/src/find-options/FindOperatorType.ts @@ -15,3 +15,6 @@ export type FindOperatorType = | "ilike" | "like" | "raw" + | "arrayContains" + | "arrayContainedBy" + | "arrayOverlap" diff --git a/src/find-options/operator/ArrayContainedBy.ts b/src/find-options/operator/ArrayContainedBy.ts new file mode 100644 index 0000000000..a1b6cf7830 --- /dev/null +++ b/src/find-options/operator/ArrayContainedBy.ts @@ -0,0 +1,11 @@ +import { FindOperator } from "../FindOperator" + +/** + * FindOptions Operator. + * Example: { someField: ArrayContainedBy([...]) } + */ +export function ArrayContainedBy( + value: T[] | FindOperator, +): FindOperator { + return new FindOperator("arrayContainedBy", value as any) +} diff --git a/src/find-options/operator/ArrayContains.ts b/src/find-options/operator/ArrayContains.ts new file mode 100644 index 0000000000..703a570419 --- /dev/null +++ b/src/find-options/operator/ArrayContains.ts @@ -0,0 +1,11 @@ +import { FindOperator } from "../FindOperator" + +/** + * FindOptions Operator. + * Example: { someField: ArrayContains([...]) } + */ +export function ArrayContains( + value: T[] | FindOperator, +): FindOperator { + return new FindOperator("arrayContains", value as any) +} diff --git a/src/find-options/operator/ArrayOverlap.ts b/src/find-options/operator/ArrayOverlap.ts new file mode 100644 index 0000000000..2d42acd8fd --- /dev/null +++ b/src/find-options/operator/ArrayOverlap.ts @@ -0,0 +1,11 @@ +import { FindOperator } from "../FindOperator" + +/** + * FindOptions Operator. + * Example: { someField: ArrayOverlap([...]) } + */ +export function ArrayOverlap( + value: T[] | FindOperator, +): FindOperator { + return new FindOperator("arrayOverlap", value as any) +} diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index e2ffa6bc76..d30e6b4994 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -993,6 +993,12 @@ export abstract class QueryBuilder { 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": diff --git a/src/query-builder/WhereClause.ts b/src/query-builder/WhereClause.ts index 048342d62e..e3abd102d8 100644 --- a/src/query-builder/WhereClause.ts +++ b/src/query-builder/WhereClause.ts @@ -13,6 +13,9 @@ type PredicateOperator = | "in" | "any" | "isNull" + | "arrayContains" + | "arrayContainedBy" + | "arrayOverlap" export interface WherePredicateOperator { operator: PredicateOperator diff --git a/test/benchmark/bulk-save-case2/entity/Document.ts b/test/benchmark/bulk-save-case2/entity/Document.ts index 08bcc66183..e7dc1b5ac5 100644 --- a/test/benchmark/bulk-save-case2/entity/Document.ts +++ b/test/benchmark/bulk-save-case2/entity/Document.ts @@ -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" }) diff --git a/test/functional/find-options/array-contained-by-operator/array-contained-by-operator.test.ts b/test/functional/find-options/array-contained-by-operator/array-contained-by-operator.test.ts new file mode 100644 index 0000000000..2560c4d8d0 --- /dev/null +++ b/test/functional/find-options/array-contained-by-operator/array-contained-by-operator.test.ts @@ -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: [], + }, + ]) + }), + )) +}) diff --git a/test/functional/find-options/array-contained-by-operator/entity/Post.ts b/test/functional/find-options/array-contained-by-operator/entity/Post.ts new file mode 100644 index 0000000000..60d8484ca5 --- /dev/null +++ b/test/functional/find-options/array-contained-by-operator/entity/Post.ts @@ -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[] +} diff --git a/test/functional/find-options/array-contains-operator/array-contains-operator.test.ts b/test/functional/find-options/array-contains-operator/array-contains-operator.test.ts new file mode 100644 index 0000000000..4128fe00e0 --- /dev/null +++ b/test/functional/find-options/array-contains-operator/array-contains-operator.test.ts @@ -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], + }, + ]) + }), + )) +}) diff --git a/test/functional/find-options/array-contains-operator/entity/Post.ts b/test/functional/find-options/array-contains-operator/entity/Post.ts new file mode 100644 index 0000000000..60d8484ca5 --- /dev/null +++ b/test/functional/find-options/array-contains-operator/entity/Post.ts @@ -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[] +} diff --git a/test/functional/find-options/array-overlap/array-overlap-operator.test.ts b/test/functional/find-options/array-overlap/array-overlap-operator.test.ts new file mode 100644 index 0000000000..4f9d079457 --- /dev/null +++ b/test/functional/find-options/array-overlap/array-overlap-operator.test.ts @@ -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], + }, + ]) + }), + )) +}) diff --git a/test/functional/find-options/array-overlap/entity/Post.ts b/test/functional/find-options/array-overlap/entity/Post.ts new file mode 100644 index 0000000000..60d8484ca5 --- /dev/null +++ b/test/functional/find-options/array-overlap/entity/Post.ts @@ -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[] +} diff --git a/test/functional/json/entity/Record.ts b/test/functional/json/entity/Record.ts index 843965d287..040a3f1d47 100644 --- a/test/functional/json/entity/Record.ts +++ b/test/functional/json/entity/Record.ts @@ -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, diff --git a/test/functional/json/jsonb.ts b/test/functional/json/jsonb.ts index a6b0a6cedc..9b88743a36 100644 --- a/test/functional/json/jsonb.ts +++ b/test/functional/json/jsonb.ts @@ -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 }, diff --git a/test/github-issues/1314/issue-1314.ts b/test/github-issues/1314/issue-1314.ts index 5e87367678..01abac0042 100644 --- a/test/github-issues/1314/issue-1314.ts +++ b/test/github-issues/1314/issue-1314.ts @@ -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" diff --git a/test/github-issues/204/entity/Record.ts b/test/github-issues/204/entity/Record.ts index bf1a51c52b..eac923085c 100644 --- a/test/github-issues/204/entity/Record.ts +++ b/test/github-issues/204/entity/Record.ts @@ -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[] } diff --git a/test/github-issues/204/issue-204.ts b/test/github-issues/204/issue-204.ts index d0ff5e5bdc..b66360e7bb 100644 --- a/test/github-issues/204/issue-204.ts +++ b/test/github-issues/204/issue-204.ts @@ -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, diff --git a/test/github-issues/3496/entity/Post.ts b/test/github-issues/3496/entity/Post.ts index c05bbedee5..85e4180ca4 100644 --- a/test/github-issues/3496/entity/Post.ts +++ b/test/github-issues/3496/entity/Post.ts @@ -10,6 +10,6 @@ export class Post { @VersionColumn() version: number - @Column({ type: "jsonb" }) + @Column({ type: "jsonb", array: true }) problems: object } diff --git a/test/github-issues/3496/issue-3496.ts b/test/github-issues/3496/issue-3496.ts index ad4b0fae1f..0719ca380b 100644 --- a/test/github-issues/3496/issue-3496.ts +++ b/test/github-issues/3496/issue-3496.ts @@ -1,4 +1,4 @@ -import "reflect-metadata" +import "../../utils/test-setup" import { DataSource } from "../../../src" import { closeTestingConnections, diff --git a/test/github-issues/3636/issue-3636.ts b/test/github-issues/3636/issue-3636.ts index b0df9b6afc..13bd2cbb3c 100644 --- a/test/github-issues/3636/issue-3636.ts +++ b/test/github-issues/3636/issue-3636.ts @@ -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" diff --git a/test/github-issues/7650/issue-7650.ts b/test/github-issues/7650/issue-7650.ts index 235f1b04ad..0c21c5d37c 100644 --- a/test/github-issues/7650/issue-7650.ts +++ b/test/github-issues/7650/issue-7650.ts @@ -1,4 +1,4 @@ -import "reflect-metadata" +import "../../utils/test-setup" import { expect } from "chai" import { DataSource } from "../../../src" import { diff --git a/test/other-issues/transformed-json-column-update-compute/transformed-json-column-update-compute.ts b/test/other-issues/transformed-json-column-update-compute/transformed-json-column-update-compute.ts index a21a40ee09..ddcb9c2b28 100644 --- a/test/other-issues/transformed-json-column-update-compute/transformed-json-column-update-compute.ts +++ b/test/other-issues/transformed-json-column-update-compute/transformed-json-column-update-compute.ts @@ -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"