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:
smith-xyz 2023-04-25 02:36:20 -04:00 committed by GitHub
parent f5b93c14b5
commit d8a2e3730f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 376 additions and 6 deletions

View File

@ -14,7 +14,7 @@ services:
# mariadb
mariadb:
image: "mariadb:10.8.4"
image: "mariadb:10.10.3"
container_name: "typeorm-mariadb"
ports:
- "3307:3306"

View File

@ -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`

View File

@ -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:

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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 })

View 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
}

View 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
}

View 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
}

View 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
}

View 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[]
}

View File

@ -0,0 +1,7 @@
import { Entity, PrimaryGeneratedColumn } from "../../../../src"
@Entity()
export class UuidEntity {
@PrimaryGeneratedColumn("uuid")
id?: string
}

View 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()
}),
)
})
})
})