Skip to content

Commit 2a2bb4b

Browse files
authored
fix: allow json as alias for longtext mariadb (#10018)
1 parent 54f4f89 commit 2a2bb4b

File tree

3 files changed

+118
-2
lines changed

3 files changed

+118
-2
lines changed

src/driver/mysql/MysqlDriver.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,12 +732,17 @@ export class MysqlDriver implements Driver {
732732
return "tinyint"
733733
} else if (column.type === "uuid" && !this.uuidColumnTypeSuported) {
734734
return "varchar"
735-
} else if (column.type === "json" && this.options.type === "mariadb") {
735+
} else if (
736+
column.type === "json" &&
737+
this.options.type === "mariadb" &&
738+
!VersionUtils.isGreaterOrEqual(this.version ?? "0.0.0", "10.4.3")
739+
) {
736740
/*
737741
* MariaDB implements this as a LONGTEXT rather, as the JSON data type contradicts the SQL standard,
738742
* and MariaDB's benchmarks indicate that performance is at least equivalent.
739743
*
740744
* @see https://mariadb.com/kb/en/json-data-type/
745+
* if Version is 10.4.3 or greater, JSON is an alias for longtext and an automatic check_json(column) constraint is added
741746
*/
742747
return "longtext"
743748
} else if (
@@ -999,7 +1004,7 @@ export class MysqlDriver implements Driver {
9991004

10001005
const isColumnChanged =
10011006
tableColumn.name !== columnMetadata.databaseName ||
1002-
tableColumn.type !== this.normalizeType(columnMetadata) ||
1007+
this.isColumnDataTypeChanged(tableColumn, columnMetadata) ||
10031008
tableColumn.length !== this.getColumnLength(columnMetadata) ||
10041009
tableColumn.width !== columnMetadata.width ||
10051010
(columnMetadata.precision !== undefined &&
@@ -1358,4 +1363,22 @@ export class MysqlDriver implements Driver {
13581363

13591364
return comment
13601365
}
1366+
1367+
/**
1368+
* A helper to check if column data types have changed
1369+
* This can be used to manage checking any types the
1370+
* database may alias
1371+
*/
1372+
private isColumnDataTypeChanged(
1373+
tableColumn: TableColumn,
1374+
columnMetadata: ColumnMetadata,
1375+
) {
1376+
// this is an exception for mariadb versions where json is an alias for longtext
1377+
if (
1378+
this.normalizeType(columnMetadata) === "json" &&
1379+
tableColumn.type.toLowerCase() === "longtext"
1380+
)
1381+
return false
1382+
return tableColumn.type !== this.normalizeType(columnMetadata)
1383+
}
13611384
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src"
2+
3+
@Entity()
4+
export class User {
5+
@PrimaryGeneratedColumn("increment")
6+
id?: number
7+
8+
@Column({ type: "json" })
9+
jsonData: string
10+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import "../../utils/test-setup"
2+
import {
3+
createTestingConnections,
4+
closeTestingConnections,
5+
reloadTestingDatabases,
6+
} from "../../utils/test-utils"
7+
import { DataSource } from "../../../src/index"
8+
import { expect } from "chai"
9+
import { User } from "./entity/User"
10+
11+
describe("github issues > #9903 json data type", () => {
12+
let connections: DataSource[]
13+
14+
afterEach(() => closeTestingConnections(connections))
15+
16+
describe("json supported type for mariadb", () => {
17+
const expectedJsonString = JSON.stringify({
18+
firstName: "Quality",
19+
lastName: "Tester",
20+
})
21+
const newUser: User = {
22+
jsonData: expectedJsonString,
23+
}
24+
25+
const badJsonUser: User = {
26+
jsonData: `'''faux---'''`,
27+
}
28+
29+
before(
30+
async () =>
31+
(connections = await createTestingConnections({
32+
entities: [__dirname + "/entity/*{.js,.ts}"],
33+
schemaCreate: true,
34+
dropSchema: true,
35+
enabledDrivers: ["mariadb"],
36+
})),
37+
)
38+
beforeEach(() => reloadTestingDatabases(connections))
39+
40+
it("should create table with json constraint", () =>
41+
Promise.all(
42+
connections.map(async (connection) => {
43+
const userRepository = connection.getRepository(User)
44+
45+
await userRepository.save(newUser)
46+
47+
const savedUser = await userRepository.findOneOrFail({
48+
where: { id: newUser.id },
49+
})
50+
51+
expect(savedUser).to.not.be.null
52+
expect(savedUser.jsonData).to.equal(expectedJsonString)
53+
54+
// trying to save bad json
55+
// here when executing the save the value is passed to JSON.stringify(),
56+
// this will ensure its json valid in mariadb so this won't break the constraint
57+
try {
58+
await userRepository.save(badJsonUser)
59+
} catch (err) {
60+
expect.fail(
61+
null,
62+
null,
63+
"Should have not thrown an error",
64+
)
65+
}
66+
67+
try {
68+
await userRepository.query(
69+
"INSERT INTO user values (?, ?)",
70+
[3, badJsonUser.jsonData],
71+
)
72+
expect.fail(null, null, "Should have thrown an error")
73+
} catch (err) {
74+
expect(err).not.to.be.undefined
75+
expect(err.sqlMessage).not.to.be.undefined
76+
expect(err.sqlMessage).to.equal(
77+
"CONSTRAINT `user.jsonData` failed for `test`.`user`",
78+
)
79+
}
80+
}),
81+
))
82+
})
83+
})

0 commit comments

Comments
 (0)