Skip to content

Commit c30b485

Browse files
jeremijapleerock
authored andcommitted
fix: check for version of MariaDB before extracting COLUMN_DEFAULT (#4783)
Added VersionUtils module to make it easier to compare coerce and compare versions of MariaDB database. See https://mariadb.com/kb/en/library/information-schema-columns-table/ Relevant excerpt for `COLUMN_DEFAULT`: > Default value for the column. From MariaDB 10.2.7, literals are quoted > to distinguish them from expressions. NULL means that the column has no > default. In MariaDB 10.2.6 and earlier, no quotes were used for any type > of default and NULL can either mean that there is no default, or that > the default column value is NULL.
1 parent d967180 commit c30b485

File tree

5 files changed

+105
-2
lines changed

5 files changed

+105
-2
lines changed

docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ services:
2727

2828
# mariadb
2929
mariadb:
30-
image: "mariadb:10.1.37"
30+
image: "mariadb:10.4.8"
3131
container_name: "typeorm-mariadb"
3232
ports:
3333
- "3307:3306"

src/driver/mysql/MysqlQueryRunner.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {ColumnType, PromiseUtils} from "../../index";
2121
import {TableCheck} from "../../schema-builder/table/TableCheck";
2222
import {IsolationLevel} from "../types/IsolationLevel";
2323
import {TableExclusion} from "../../schema-builder/table/TableExclusion";
24+
import { VersionUtils } from "../../util/VersionUtils";
2425

2526
/**
2627
* Runs queries on a single mysql database connection.
@@ -1243,6 +1244,7 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
12431244
return [];
12441245

12451246
const isMariaDb = this.driver.options.type === "mariadb";
1247+
const dbVersion = await this.getVersion();
12461248

12471249
// create tables for loaded tables
12481250
return Promise.all(dbTables.map(async dbTable => {
@@ -1290,8 +1292,16 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
12901292
|| (isMariaDb && dbColumn["COLUMN_DEFAULT"] === "NULL")) {
12911293
tableColumn.default = undefined;
12921294

1295+
} else if (/^CURRENT_TIMESTAMP(\([0-9]*\))?$/i.test(dbColumn["COLUMN_DEFAULT"])) {
1296+
// New versions of MariaDB return expressions in lowercase. We need to set it in
1297+
// uppercase so the comparison in MysqlDriver#compareDefaultValues does not fail.
1298+
tableColumn.default = dbColumn["COLUMN_DEFAULT"].toUpperCase();
1299+
} else if (isMariaDb && VersionUtils.isGreaterOrEqual(dbVersion, "10.2.7")) {
1300+
// MariaDB started adding quotes to literals in COLUMN_DEFAULT since version 10.2.7
1301+
// See https://mariadb.com/kb/en/library/information-schema-columns-table/
1302+
tableColumn.default = dbColumn["COLUMN_DEFAULT"];
12931303
} else {
1294-
tableColumn.default = dbColumn["COLUMN_DEFAULT"] === "CURRENT_TIMESTAMP" ? dbColumn["COLUMN_DEFAULT"] : `'${dbColumn["COLUMN_DEFAULT"]}'`;
1304+
tableColumn.default = `'${dbColumn["COLUMN_DEFAULT"]}'`;
12951305
}
12961306

12971307
if (dbColumn["EXTRA"].indexOf("on update") !== -1) {
@@ -1659,4 +1669,9 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
16591669
return c;
16601670
}
16611671

1672+
protected async getVersion(): Promise<string> {
1673+
const result = await this.query(`SELECT VERSION() AS \`version\``);
1674+
return result[0]["version"];
1675+
}
1676+
16621677
}

src/util/VersionUtils.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export type Version = [number, number, number];
2+
3+
export class VersionUtils {
4+
static isGreaterOrEqual(version: string, targetVersion: string): boolean {
5+
const v1 = parseVersion(version);
6+
const v2 = parseVersion(targetVersion);
7+
8+
return v1[0] > v2[0] ||
9+
v1[0] === v2[0] && v1[1] > v2[1] ||
10+
v1[0] === v2[0] && v1[1] === v2[1] && v1[2] >= v2[2];
11+
}
12+
}
13+
14+
function parseVersion(version: string = ""): Version {
15+
const v: Version = [0, 0, 0];
16+
17+
version.split(".").forEach((value, i) => v[i] = parseInt(value, 10));
18+
19+
return v;
20+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CreateDateColumn } from "../../../../src";
2+
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
3+
import { Entity } from "../../../../src/decorator/entity/Entity";
4+
5+
@Entity()
6+
export class Item {
7+
@PrimaryGeneratedColumn()
8+
id: number;
9+
10+
@CreateDateColumn()
11+
date: Date;
12+
}

test/github-issues/4782/issue-4782.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import "reflect-metadata";
2+
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
3+
import {Connection} from "../../../src/connection/Connection";
4+
import {expect} from "chai";
5+
import { VersionUtils } from "../../../src/util/VersionUtils";
6+
7+
describe("github issues > 4782 mariadb driver wants to recreate create/update date columns CURRENT_TIMESTAMP(6) === current_timestamp(6)", () => {
8+
9+
let connections: Connection[];
10+
before(async () => connections = await createTestingConnections({
11+
// logging: true,
12+
entities: [__dirname + "/entity/*{.js,.ts}"],
13+
enabledDrivers: ["mysql", "mariadb"]
14+
}));
15+
beforeEach(() => reloadTestingDatabases(connections));
16+
after(() => closeTestingConnections(connections));
17+
18+
it("should not want to execute migrations twice", () => Promise.all(connections.map(async connection => {
19+
const sql1 = await connection.driver.createSchemaBuilder().log();
20+
expect(sql1.upQueries).to.eql([]);
21+
})));
22+
23+
describe("VersionUtils", () => {
24+
describe("isGreaterOrEqual", () => {
25+
it("should return false when comparing invalid versions", () => {
26+
expect(VersionUtils.isGreaterOrEqual("", "")).to.equal(false);
27+
});
28+
29+
it("should return false when targetVersion is larger", () => {
30+
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.2.4")).to.equal(false);
31+
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.4.3")).to.equal(false);
32+
expect(VersionUtils.isGreaterOrEqual("1.2.3", "2.2.3")).to.equal(false);
33+
expect(VersionUtils.isGreaterOrEqual("1.2", "1.3")).to.equal(false);
34+
expect(VersionUtils.isGreaterOrEqual("1", "2")).to.equal(false);
35+
expect(VersionUtils.isGreaterOrEqual(undefined as unknown as string, "0.0.1")).to.equal(false);
36+
});
37+
38+
it("should return true when targetVersion is smaller", () => {
39+
40+
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.2.2")).to.equal(true);
41+
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.1.3")).to.equal(true);
42+
expect(VersionUtils.isGreaterOrEqual("1.2.3", "0.2.3")).to.equal(true);
43+
expect(VersionUtils.isGreaterOrEqual("1.2", "1.2")).to.equal(true);
44+
expect(VersionUtils.isGreaterOrEqual("1", "1")).to.equal(true);
45+
});
46+
47+
it("should work with mariadb-style versions", () => {
48+
const dbVersion = "10.4.8-MariaDB-1:10.4.8+maria~bionic";
49+
expect(VersionUtils.isGreaterOrEqual("10.4.9", dbVersion)).to.equal(true);
50+
expect(VersionUtils.isGreaterOrEqual("10.4.8", dbVersion)).to.equal(true);
51+
expect(VersionUtils.isGreaterOrEqual("10.4.7", dbVersion)).to.equal(false);
52+
});
53+
});
54+
});
55+
56+
});

0 commit comments

Comments
 (0)