diff --git a/docs/entities.md b/docs/entities.md index c47a6b2d0c..7f212b5160 100644 --- a/docs/entities.md +++ b/docs/entities.md @@ -294,7 +294,7 @@ or `bit`, `int`, `integer`, `tinyint`, `smallint`, `mediumint`, `bigint`, `float`, `double`, `double precision`, `dec`, `decimal`, `numeric`, `fixed`, `bool`, `boolean`, `date`, `datetime`, `timestamp`, `time`, `year`, `char`, `nchar`, `national char`, `varchar`, `nvarchar`, `national varchar`, -`text`, `tinytext`, `mediumtext`, `blob`, `longtext`, `tinyblob`, `mediumblob`, `longblob`, `enum`, +`text`, `tinytext`, `mediumtext`, `blob`, `longtext`, `tinyblob`, `mediumblob`, `longblob`, `enum`, `set`, `json`, `binary`, `varbinary`, `geometry`, `point`, `linestring`, `polygon`, `multipoint`, `multilinestring`, `multipolygon`, `geometrycollection` @@ -390,6 +390,52 @@ export class User { } ``` +### `set` column type + +`set` column type is supported by `mariadb` and `mysql`. There are various possible column definitions: + +Using typescript enums: +```typescript +export enum UserRole { + ADMIN = "admin", + EDITOR = "editor", + GHOST = "ghost" +} + +@Entity() +export class User { + + @PrimaryGeneratedColumn() + id: number; + + @Column({ + type: "set", + enum: UserRole, + default: [UserRole.GHOST, UserRole.EDITOR] + }) + roles: UserRole[] + +} +``` + +Using array with `set` values: +```typescript +export type UserRoleType = "admin" | "editor" | "ghost", + +@Entity() +export class User { + + @PrimaryGeneratedColumn() + id: number; + + @Column({ + type: "set", + enum: ["admin", "editor", "ghost"], + default: ["ghost", "editor"] + }) + roles: UserRoleType[] +} +``` ### `simple-array` column type diff --git a/src/decorator/columns/Column.ts b/src/decorator/columns/Column.ts index ead237747d..834c33119e 100644 --- a/src/decorator/columns/Column.ts +++ b/src/decorator/columns/Column.ts @@ -70,6 +70,12 @@ export function Column(type: "enum", options?: ColumnCommonOptions & ColumnEnumO */ export function Column(type: "simple-enum", options?: ColumnCommonOptions & ColumnEnumOptions): Function; +/** + * Column decorator is used to mark a specific class property as a table column. + * Only properties decorated with this decorator will be persisted to the database when entity be saved. + */ +export function Column(type: "set", options?: ColumnCommonOptions & ColumnEnumOptions): Function; + /** * Column decorator is used to mark a specific class property as a table column. * Only properties decorated with this decorator will be persisted to the database when entity be saved. diff --git a/src/driver/mysql/MysqlDriver.ts b/src/driver/mysql/MysqlDriver.ts index 5c42251a55..bc1dda1241 100644 --- a/src/driver/mysql/MysqlDriver.ts +++ b/src/driver/mysql/MysqlDriver.ts @@ -120,6 +120,7 @@ export class MysqlDriver implements Driver { "longblob", "longtext", "enum", + "set", "binary", "varbinary", // json data type @@ -459,6 +460,9 @@ export class MysqlDriver implements Driver { } else if (columnMetadata.type === "enum" || columnMetadata.type === "simple-enum") { return "" + value; + + } else if (columnMetadata.type === "set") { + return DateUtils.simpleArrayToString(value); } return value; @@ -498,6 +502,8 @@ export class MysqlDriver implements Driver { && columnMetadata.enum.indexOf(parseInt(value)) >= 0) { // convert to number if that exists in possible enum options value = parseInt(value); + } else if (columnMetadata.type === "set") { + value = DateUtils.stringToSimpleArray(value); } if (columnMetadata.transformer) @@ -564,6 +570,10 @@ export class MysqlDriver implements Driver { return `'${defaultValue}'`; } + if ((columnMetadata.type === "set") && defaultValue !== undefined) { + return `'${DateUtils.simpleArrayToString(defaultValue)}'`; + } + if (typeof defaultValue === "number") { return "" + defaultValue; diff --git a/src/driver/mysql/MysqlQueryRunner.ts b/src/driver/mysql/MysqlQueryRunner.ts index 298c6aaf9f..57d97a84d6 100644 --- a/src/driver/mysql/MysqlQueryRunner.ts +++ b/src/driver/mysql/MysqlQueryRunner.ts @@ -1333,7 +1333,7 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner { tableColumn.scale = parseInt(dbColumn["NUMERIC_SCALE"]); } - if (tableColumn.type === "enum" || tableColumn.type === "simple-enum") { + if (tableColumn.type === "enum" || tableColumn.type === "simple-enum" || tableColumn.type === "set") { const colType = dbColumn["COLUMN_TYPE"]; const items = colType.substring(colType.indexOf("(") + 1, colType.indexOf(")")).split(","); tableColumn.enum = (items as string[]).map(item => { diff --git a/src/driver/types/ColumnTypes.ts b/src/driver/types/ColumnTypes.ts index dbe1241b95..cae11f2467 100644 --- a/src/driver/types/ColumnTypes.ts +++ b/src/driver/types/ColumnTypes.ts @@ -161,6 +161,7 @@ export type SimpleColumnType = // other types |"enum" // mysql, postgres + |"set" // mysql |"cidr" // postgres |"inet" // postgres, cockroachdb |"macaddr"// postgres diff --git a/test/github-issues/2779/entity/Post.ts b/test/github-issues/2779/entity/Post.ts new file mode 100644 index 0000000000..1705b4afab --- /dev/null +++ b/test/github-issues/2779/entity/Post.ts @@ -0,0 +1,15 @@ +import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src"; +import { Role } from "../set"; + +@Entity("post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column("set", { + default: [Role.Admin, Role.Developer], + enum: Role + }) + roles: Role[]; +} \ No newline at end of file diff --git a/test/github-issues/2779/issue-2779.ts b/test/github-issues/2779/issue-2779.ts new file mode 100644 index 0000000000..fe0c82dc4d --- /dev/null +++ b/test/github-issues/2779/issue-2779.ts @@ -0,0 +1,44 @@ +import "reflect-metadata"; +import { Connection } from "../../../src/connection/Connection"; +import { closeTestingConnections, createTestingConnections } from "../../utils/test-utils"; +import { Post } from "./entity/Post"; +import { expect } from "chai"; +import { Role } from "./set"; + +describe("github issues > #2779 Could we add support for the MySQL/MariaDB SET data type?", () => { + + let connections: Connection[]; + before(async () => { + connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + enabledDrivers: ["mariadb", "mysql"], + schemaCreate: true, + dropSchema: true, + }); + }); + after(() => closeTestingConnections(connections)); + + it("should create column with SET datatype", () => Promise.all(connections.map(async connection => { + + const queryRunner = connection.createQueryRunner(); + const table = await queryRunner.getTable("post"); + table!.findColumnByName("roles")!.type.should.be.equal("set"); + await queryRunner.release(); + + }))); + + it("should persist and hydrate sets", () => Promise.all(connections.map(async connection => { + + const targetValue = [Role.Support, Role.Developer]; + + const post = new Post(); + post.roles = targetValue; + await connection.manager.save(post); + post.roles.should.be.deep.equal(targetValue); + + const loadedPost = await connection.manager.findOne(Post); + expect(loadedPost).not.to.be.undefined; + loadedPost!.roles.should.be.deep.equal(targetValue); + }))); + +}); diff --git a/test/github-issues/2779/set.ts b/test/github-issues/2779/set.ts new file mode 100644 index 0000000000..54880f5aa3 --- /dev/null +++ b/test/github-issues/2779/set.ts @@ -0,0 +1,5 @@ +export enum Role { + Admin = "Admin", + Support = "Support", + Developer = "Developer" +} \ No newline at end of file