Skip to content

Commit

Permalink
feat: add set datatype support for MySQL/MariaDB (#4538)
Browse files Browse the repository at this point in the history
Set possible values defined using existing enum column option.
Sets are implemented as arrays.

Closes: #2779
  • Loading branch information
hauau authored and pleerock committed Sep 5, 2019
1 parent 5c311ed commit 19e2179
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 2 deletions.
48 changes: 47 additions & 1 deletion docs/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions src/decorator/columns/Column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions src/driver/mysql/MysqlDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class MysqlDriver implements Driver {
"longblob",
"longtext",
"enum",
"set",
"binary",
"varbinary",
// json data type
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion src/driver/mysql/MysqlQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
1 change: 1 addition & 0 deletions src/driver/types/ColumnTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export type SimpleColumnType =

// other types
|"enum" // mysql, postgres
|"set" // mysql
|"cidr" // postgres
|"inet" // postgres, cockroachdb
|"macaddr"// postgres
Expand Down
15 changes: 15 additions & 0 deletions test/github-issues/2779/entity/Post.ts
Original file line number Diff line number Diff line change
@@ -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[];
}
44 changes: 44 additions & 0 deletions test/github-issues/2779/issue-2779.ts
Original file line number Diff line number Diff line change
@@ -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);
})));

});
5 changes: 5 additions & 0 deletions test/github-issues/2779/set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum Role {
Admin = "Admin",
Support = "Support",
Developer = "Developer"
}

0 comments on commit 19e2179

Please sign in to comment.