mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
feat: mariadb uuid inet4 inet6 column data type support (#9845)
* feat: mariadb inet4, inet6, uuid data type support * refactor: cleanup unnecessary methods * style: mysqldriver formatting * fix: handle length column metadata mariadb uuid * fix: 8832 test suite to verify errors correctly * style: fix 8832 test formatting * fix: 8832 error testing cleanup * fix: remove defaulting column type feature * style: fix formatting * fix: remove unnecessary dbms error test * fix: remove unused import in test * fix: ensure defaulting uuid generation column type
This commit is contained in:
parent
f5b93c14b5
commit
d8a2e3730f
@ -14,7 +14,7 @@ services:
|
||||
|
||||
# mariadb
|
||||
mariadb:
|
||||
image: "mariadb:10.8.4"
|
||||
image: "mariadb:10.10.3"
|
||||
container_name: "typeorm-mariadb"
|
||||
ports:
|
||||
- "3307:3306"
|
||||
|
||||
@ -369,7 +369,10 @@ or
|
||||
`timestamp`, `time`, `year`, `char`, `nchar`, `national char`, `varchar`, `nvarchar`, `national varchar`,
|
||||
`text`, `tinytext`, `mediumtext`, `blob`, `longtext`, `tinyblob`, `mediumblob`, `longblob`, `enum`, `set`,
|
||||
`json`, `binary`, `varbinary`, `geometry`, `point`, `linestring`, `polygon`, `multipoint`, `multilinestring`,
|
||||
`multipolygon`, `geometrycollection`
|
||||
`multipolygon`, `geometrycollection`, `uuid`, `inet4`, `inet6`
|
||||
|
||||
> Note: UUID, INET4, and INET6 are only available for mariadb and for respective versions that made them available.
|
||||
|
||||
|
||||
### Column types for `postgres`
|
||||
|
||||
|
||||
@ -152,6 +152,10 @@ export class MysqlDriver implements Driver {
|
||||
"multilinestring",
|
||||
"multipolygon",
|
||||
"geometrycollection",
|
||||
// additional data types for mariadb
|
||||
"uuid",
|
||||
"inet4",
|
||||
"inet6",
|
||||
]
|
||||
|
||||
/**
|
||||
@ -331,6 +335,9 @@ export class MysqlDriver implements Driver {
|
||||
update: false,
|
||||
}
|
||||
|
||||
/** MariaDB supports uuid type for version 10.7.0 and up */
|
||||
private uuidColumnTypeSuported = false
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
@ -421,6 +428,9 @@ export class MysqlDriver implements Driver {
|
||||
if (VersionUtils.isGreaterOrEqual(dbVersion, "10.2.0")) {
|
||||
this.cteCapabilities.enabled = true
|
||||
}
|
||||
if (VersionUtils.isGreaterOrEqual(dbVersion, "10.7.0")) {
|
||||
this.uuidColumnTypeSuported = true
|
||||
}
|
||||
} else if (this.options.type === "mysql") {
|
||||
if (VersionUtils.isGreaterOrEqual(dbVersion, "8.0.0")) {
|
||||
this.cteCapabilities.enabled = true
|
||||
@ -720,7 +730,7 @@ export class MysqlDriver implements Driver {
|
||||
return "blob"
|
||||
} else if (column.type === Boolean) {
|
||||
return "tinyint"
|
||||
} else if (column.type === "uuid") {
|
||||
} else if (column.type === "uuid" && !this.uuidColumnTypeSuported) {
|
||||
return "varchar"
|
||||
} else if (column.type === "json" && this.options.type === "mariadb") {
|
||||
/*
|
||||
@ -825,8 +835,10 @@ export class MysqlDriver implements Driver {
|
||||
|
||||
/**
|
||||
* fix https://github.com/typeorm/typeorm/issues/1139
|
||||
* note that if the db did support uuid column type it wouldn't have been defaulted to varchar
|
||||
*/
|
||||
if (column.generationStrategy === "uuid") return "36"
|
||||
if (column.generationStrategy === "uuid" && column.type === "varchar")
|
||||
return "36"
|
||||
|
||||
switch (column.type) {
|
||||
case String:
|
||||
|
||||
@ -177,13 +177,15 @@ export type SimpleColumnType =
|
||||
| "set" // mysql
|
||||
| "cidr" // postgres
|
||||
| "inet" // postgres, cockroachdb
|
||||
| "inet4" // mariadb
|
||||
| "inet6" // mariadb
|
||||
| "macaddr" // postgres
|
||||
| "bit" // postgres, mssql
|
||||
| "bit varying" // postgres
|
||||
| "varbit" // postgres
|
||||
| "tsvector" // postgres
|
||||
| "tsquery" // postgres
|
||||
| "uuid" // postgres, cockroachdb
|
||||
| "uuid" // postgres, cockroachdb, mariadb
|
||||
| "xml" // mssql, postgres
|
||||
| "json" // mysql, postgres, cockroachdb, spanner
|
||||
| "jsonb" // postgres, cockroachdb
|
||||
|
||||
@ -102,6 +102,10 @@ export class JunctionEntityMetadataBuilder {
|
||||
) ||
|
||||
this.connection.driver.options.type ===
|
||||
"aurora-mysql") &&
|
||||
// some versions of mariadb support the column type and should not try to provide the length property
|
||||
this.connection.driver.normalizeType(
|
||||
referencedColumn,
|
||||
) !== "uuid" &&
|
||||
(referencedColumn.generationStrategy === "uuid" ||
|
||||
referencedColumn.type === "uuid")
|
||||
? "36"
|
||||
@ -166,6 +170,10 @@ export class JunctionEntityMetadataBuilder {
|
||||
) ||
|
||||
this.connection.driver.options.type ===
|
||||
"aurora-mysql") &&
|
||||
// some versions of mariadb support the column type and should not try to provide the length property
|
||||
this.connection.driver.normalizeType(
|
||||
inverseReferencedColumn,
|
||||
) !== "uuid" &&
|
||||
(inverseReferencedColumn.generationStrategy ===
|
||||
"uuid" ||
|
||||
inverseReferencedColumn.type === "uuid")
|
||||
|
||||
@ -208,6 +208,10 @@ export class RelationJoinColumnBuilder {
|
||||
) ||
|
||||
this.connection.driver.options.type ===
|
||||
"aurora-mysql") &&
|
||||
// some versions of mariadb support the column type and should not try to provide the length property
|
||||
this.connection.driver.normalizeType(
|
||||
referencedColumn,
|
||||
) !== "uuid" &&
|
||||
(referencedColumn.generationStrategy ===
|
||||
"uuid" ||
|
||||
referencedColumn.type === "uuid")
|
||||
|
||||
@ -16,7 +16,11 @@ export enum OrderStatus {
|
||||
|
||||
@Entity()
|
||||
export class Order extends BaseEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
/**
|
||||
* modified to remove the uuid since some versions of mariadb have uuid as a type
|
||||
* which would create an additional upsert between the tests -> https://github.com/typeorm/typeorm/issues/8832
|
||||
*/
|
||||
@PrimaryGeneratedColumn()
|
||||
id: string
|
||||
|
||||
@Column({ type: "enum", enum: OrderStatus })
|
||||
|
||||
10
test/github-issues/8832/badEntity/BadInet4.ts
Normal file
10
test/github-issues/8832/badEntity/BadInet4.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src"
|
||||
|
||||
@Entity()
|
||||
export class BadInet4 {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id?: string
|
||||
|
||||
@Column({ type: "inet4", length: "36" })
|
||||
inet4: string
|
||||
}
|
||||
10
test/github-issues/8832/badEntity/BadInet6.ts
Normal file
10
test/github-issues/8832/badEntity/BadInet6.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src"
|
||||
|
||||
@Entity()
|
||||
export class BadInet6 {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id?: string
|
||||
|
||||
@Column({ type: "inet6", length: "36" })
|
||||
inet6: string
|
||||
}
|
||||
10
test/github-issues/8832/badEntity/BadUuid.ts
Normal file
10
test/github-issues/8832/badEntity/BadUuid.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src"
|
||||
|
||||
@Entity()
|
||||
export class BadUuid {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id?: string
|
||||
|
||||
@Column({ type: "uuid", length: "36" })
|
||||
uuid: string
|
||||
}
|
||||
22
test/github-issues/8832/entity/Address.ts
Normal file
22
test/github-issues/8832/entity/Address.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
} from "../../../../src"
|
||||
import { User } from "./User"
|
||||
|
||||
@Entity()
|
||||
export class Address {
|
||||
@PrimaryGeneratedColumn("increment")
|
||||
id?: number
|
||||
|
||||
@Column()
|
||||
city: string
|
||||
|
||||
@Column()
|
||||
state: string
|
||||
|
||||
@ManyToOne(() => User, (user) => user.addresses)
|
||||
user: User
|
||||
}
|
||||
30
test/github-issues/8832/entity/User.ts
Normal file
30
test/github-issues/8832/entity/User.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
} from "../../../../src"
|
||||
import { Address } from "./Address"
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id?: string
|
||||
|
||||
/** can use a default but testing against mysql since they're shared drivers */
|
||||
@Column({ type: "uuid" })
|
||||
uuid: string
|
||||
|
||||
@Column({ type: "inet4" })
|
||||
inet4: string
|
||||
|
||||
@Column({ type: "inet6" })
|
||||
inet6: string
|
||||
|
||||
/** testing generation */
|
||||
@Column({ type: "uuid", generated: "uuid" })
|
||||
another_uuid_field?: string
|
||||
|
||||
@OneToMany(() => Address, (address) => address.user)
|
||||
addresses?: Address[]
|
||||
}
|
||||
7
test/github-issues/8832/entity/UuidEntity.ts
Normal file
7
test/github-issues/8832/entity/UuidEntity.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Entity, PrimaryGeneratedColumn } from "../../../../src"
|
||||
|
||||
@Entity()
|
||||
export class UuidEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id?: string
|
||||
}
|
||||
248
test/github-issues/8832/issue-8832.ts
Normal file
248
test/github-issues/8832/issue-8832.ts
Normal file
@ -0,0 +1,248 @@
|
||||
import "../../utils/test-setup"
|
||||
import {
|
||||
createTestingConnections,
|
||||
closeTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
import { DataSource } from "../../../src/index"
|
||||
import { expect } from "chai"
|
||||
import { User } from "../8832/entity/User"
|
||||
import { Address } from "./entity/Address"
|
||||
import { ConnectionMetadataBuilder } from "../../../src/connection/ConnectionMetadataBuilder"
|
||||
import { EntityMetadataValidator } from "../../../src/metadata-builder/EntityMetadataValidator"
|
||||
import { UuidEntity } from "./entity/UuidEntity"
|
||||
|
||||
describe("github issues > #8832 Add uuid, inet4, and inet6 types for mariadb", () => {
|
||||
let connections: DataSource[]
|
||||
|
||||
afterEach(() => closeTestingConnections(connections))
|
||||
|
||||
describe("basic use of new maria db types", () => {
|
||||
const newUser: User = {
|
||||
uuid: "ceb2897c-a1cf-11ed-8dbd-040300000000",
|
||||
inet4: "192.0.2.146",
|
||||
inet6: "2001:0db8:0000:0000:0000:ff00:0042:8329",
|
||||
}
|
||||
|
||||
const expectedInet6 = "2001:db8::ff00:42:8329"
|
||||
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
schemaCreate: true,
|
||||
dropSchema: true,
|
||||
enabledDrivers: ["mariadb"],
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
|
||||
it("should create table with uuid, inet4, and inet6 type set to column for relevant mariadb versions", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
const userRepository = connection.getRepository(User)
|
||||
|
||||
// seems there is an issue with the persisting id that crosses over from mysql to mariadb
|
||||
await userRepository.save(newUser)
|
||||
|
||||
const savedUser = await userRepository.findOneOrFail({
|
||||
where: { uuid: newUser.uuid },
|
||||
})
|
||||
|
||||
const foundUser = await userRepository.findOne({
|
||||
where: { id: savedUser.id },
|
||||
})
|
||||
|
||||
expect(foundUser).to.not.be.null
|
||||
expect(foundUser!.uuid).to.deep.equal(newUser.uuid)
|
||||
expect(foundUser!.inet4).to.deep.equal(newUser.inet4)
|
||||
expect(foundUser!.inet6).to.deep.equal(expectedInet6)
|
||||
expect(foundUser!.another_uuid_field).to.not.be.undefined
|
||||
|
||||
const columnTypes: {
|
||||
COLUMN_NAME: string
|
||||
DATA_TYPE: string
|
||||
}[] = await connection.query(
|
||||
`
|
||||
SELECT
|
||||
COLUMN_NAME,
|
||||
DATA_TYPE
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
TABLE_SCHEMA = ?
|
||||
AND TABLE_NAME = ?
|
||||
AND COLUMN_NAME IN (?, ?, ?, ?)
|
||||
`,
|
||||
[
|
||||
connection.driver.database,
|
||||
"user",
|
||||
"id",
|
||||
"uuid",
|
||||
"inet4",
|
||||
"inet6",
|
||||
"anotherUuid",
|
||||
],
|
||||
)
|
||||
const expectedColumnTypes: Record<string, string> = {
|
||||
id: "uuid",
|
||||
uuid: "uuid",
|
||||
inet4: "inet4",
|
||||
inet6: "inet6",
|
||||
another_uuid_field: "uuid",
|
||||
}
|
||||
|
||||
columnTypes.forEach(({ COLUMN_NAME, DATA_TYPE }) => {
|
||||
expect(DATA_TYPE).to.equal(
|
||||
expectedColumnTypes[COLUMN_NAME],
|
||||
)
|
||||
})
|
||||
|
||||
// save a relation
|
||||
const addressRepository = connection.getRepository(Address)
|
||||
|
||||
const newAddress: Address = {
|
||||
city: "Codersville",
|
||||
state: "Coderado",
|
||||
user: foundUser!,
|
||||
}
|
||||
|
||||
await addressRepository.save(newAddress)
|
||||
|
||||
const foundAddress = await addressRepository.findOne({
|
||||
where: { user: { id: foundUser!.id } },
|
||||
})
|
||||
|
||||
expect(foundAddress).to.not.be.null
|
||||
}),
|
||||
))
|
||||
})
|
||||
|
||||
describe("regression test mysql uuid generation", () => {
|
||||
const uuidEntity: UuidEntity = {
|
||||
id: "ceb2897c-a1cf-11ed-8dbd-040300000000",
|
||||
}
|
||||
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
entities: [UuidEntity],
|
||||
schemaCreate: true,
|
||||
dropSchema: true,
|
||||
enabledDrivers: ["mysql", "mariadb"],
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
|
||||
it("should create table with with varchar with length 36 when version is mysql", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
const uuidRepository = connection.getRepository(UuidEntity)
|
||||
|
||||
// seems there is an issue with the persisting id that crosses over from mysql to mariadb
|
||||
await uuidRepository.save(uuidEntity)
|
||||
|
||||
const columnTypes: {
|
||||
DATA_TYPE: string
|
||||
CHARACTER_MAXIMUM_LENGTH: string
|
||||
}[] = await connection.query(
|
||||
`
|
||||
SELECT
|
||||
DATA_TYPE,
|
||||
CHARACTER_MAXIMUM_LENGTH
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
TABLE_SCHEMA = ?
|
||||
AND TABLE_NAME = ?
|
||||
AND COLUMN_NAME = ?
|
||||
`,
|
||||
[connection.driver.database, "UuidEntity", "id"],
|
||||
)
|
||||
|
||||
const isMysql = connection.driver.options.type === "mysql"
|
||||
const expectedType = isMysql ? "varchar" : "uuid"
|
||||
const expectedLength = isMysql ? "36" : null
|
||||
|
||||
columnTypes.forEach(
|
||||
({ DATA_TYPE, CHARACTER_MAXIMUM_LENGTH }) => {
|
||||
expect(DATA_TYPE).to.equal(expectedType)
|
||||
expect(CHARACTER_MAXIMUM_LENGTH).to.equal(
|
||||
expectedLength,
|
||||
)
|
||||
},
|
||||
)
|
||||
}),
|
||||
))
|
||||
})
|
||||
|
||||
describe("entity-metadata-validator", () => {
|
||||
it("should throw error if mariadb uuid is supported and length is provided to property", async () => {
|
||||
Promise.all(
|
||||
["BadInet4", "BadInet6", "BadUuid"].map(async (entity) => {
|
||||
const entityLocation = `${__dirname}/badEntity/${entity}{.js,.ts}"`
|
||||
const connection = new DataSource({
|
||||
// dummy connection options, connection won't be established anyway
|
||||
type: "mariadb",
|
||||
host: "localhost",
|
||||
username: "test",
|
||||
password: "test",
|
||||
database: "test",
|
||||
entities: [entityLocation],
|
||||
})
|
||||
|
||||
// version supports all the new types
|
||||
connection.driver.version = "10.10.0"
|
||||
|
||||
const connectionMetadataBuilder =
|
||||
new ConnectionMetadataBuilder(connection)
|
||||
const entityMetadatas =
|
||||
await connectionMetadataBuilder.buildEntityMetadatas([
|
||||
entityLocation,
|
||||
])
|
||||
const entityMetadataValidator =
|
||||
new EntityMetadataValidator()
|
||||
expect(() =>
|
||||
entityMetadataValidator.validateMany(
|
||||
entityMetadatas,
|
||||
connection.driver,
|
||||
),
|
||||
).to.throw(Error)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should not throw error for mysql when uuid is provided and a length property is provided", async () => {
|
||||
Promise.all(
|
||||
["BadInet4", "BadInet6", "BadUuid"].map(async (entity) => {
|
||||
const entityLocation = `${__dirname}/badEntity/${entity}{.js,.ts}"`
|
||||
const connection = new DataSource({
|
||||
// dummy connection options, connection won't be established anyway
|
||||
type: "mysql",
|
||||
host: "localhost",
|
||||
username: "test",
|
||||
password: "test",
|
||||
database: "test",
|
||||
entities: [entityLocation],
|
||||
})
|
||||
|
||||
// version supports all the new types
|
||||
connection.driver.version = "10.10.0"
|
||||
|
||||
const connectionMetadataBuilder =
|
||||
new ConnectionMetadataBuilder(connection)
|
||||
const entityMetadatas =
|
||||
await connectionMetadataBuilder.buildEntityMetadatas([
|
||||
entityLocation,
|
||||
])
|
||||
const entityMetadataValidator =
|
||||
new EntityMetadataValidator()
|
||||
expect(() =>
|
||||
entityMetadataValidator.validateMany(
|
||||
entityMetadatas,
|
||||
connection.driver,
|
||||
),
|
||||
).not.to.throw()
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user