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.
This commit is contained in:
Jerko Steiner 2019-10-18 20:26:16 +09:00 committed by Umed Khudoiberdiev
parent d967180efc
commit c30b4859d4
5 changed files with 105 additions and 2 deletions

View File

@ -27,7 +27,7 @@ services:
# mariadb
mariadb:
image: "mariadb:10.1.37"
image: "mariadb:10.4.8"
container_name: "typeorm-mariadb"
ports:
- "3307:3306"

View File

@ -21,6 +21,7 @@ import {ColumnType, PromiseUtils} from "../../index";
import {TableCheck} from "../../schema-builder/table/TableCheck";
import {IsolationLevel} from "../types/IsolationLevel";
import {TableExclusion} from "../../schema-builder/table/TableExclusion";
import { VersionUtils } from "../../util/VersionUtils";
/**
* Runs queries on a single mysql database connection.
@ -1243,6 +1244,7 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
return [];
const isMariaDb = this.driver.options.type === "mariadb";
const dbVersion = await this.getVersion();
// create tables for loaded tables
return Promise.all(dbTables.map(async dbTable => {
@ -1290,8 +1292,16 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
|| (isMariaDb && dbColumn["COLUMN_DEFAULT"] === "NULL")) {
tableColumn.default = undefined;
} else if (/^CURRENT_TIMESTAMP(\([0-9]*\))?$/i.test(dbColumn["COLUMN_DEFAULT"])) {
// New versions of MariaDB return expressions in lowercase. We need to set it in
// uppercase so the comparison in MysqlDriver#compareDefaultValues does not fail.
tableColumn.default = dbColumn["COLUMN_DEFAULT"].toUpperCase();
} else if (isMariaDb && VersionUtils.isGreaterOrEqual(dbVersion, "10.2.7")) {
// MariaDB started adding quotes to literals in COLUMN_DEFAULT since version 10.2.7
// See https://mariadb.com/kb/en/library/information-schema-columns-table/
tableColumn.default = dbColumn["COLUMN_DEFAULT"];
} else {
tableColumn.default = dbColumn["COLUMN_DEFAULT"] === "CURRENT_TIMESTAMP" ? dbColumn["COLUMN_DEFAULT"] : `'${dbColumn["COLUMN_DEFAULT"]}'`;
tableColumn.default = `'${dbColumn["COLUMN_DEFAULT"]}'`;
}
if (dbColumn["EXTRA"].indexOf("on update") !== -1) {
@ -1659,4 +1669,9 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
return c;
}
protected async getVersion(): Promise<string> {
const result = await this.query(`SELECT VERSION() AS \`version\``);
return result[0]["version"];
}
}

20
src/util/VersionUtils.ts Normal file
View File

@ -0,0 +1,20 @@
export type Version = [number, number, number];
export class VersionUtils {
static isGreaterOrEqual(version: string, targetVersion: string): boolean {
const v1 = parseVersion(version);
const v2 = parseVersion(targetVersion);
return v1[0] > v2[0] ||
v1[0] === v2[0] && v1[1] > v2[1] ||
v1[0] === v2[0] && v1[1] === v2[1] && v1[2] >= v2[2];
}
}
function parseVersion(version: string = ""): Version {
const v: Version = [0, 0, 0];
version.split(".").forEach((value, i) => v[i] = parseInt(value, 10));
return v;
}

View File

@ -0,0 +1,12 @@
import { CreateDateColumn } from "../../../../src";
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import { Entity } from "../../../../src/decorator/entity/Entity";
@Entity()
export class Item {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn()
date: Date;
}

View File

@ -0,0 +1,56 @@
import "reflect-metadata";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {expect} from "chai";
import { VersionUtils } from "../../../src/util/VersionUtils";
describe("github issues > 4782 mariadb driver wants to recreate create/update date columns CURRENT_TIMESTAMP(6) === current_timestamp(6)", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
// logging: true,
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["mysql", "mariadb"]
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("should not want to execute migrations twice", () => Promise.all(connections.map(async connection => {
const sql1 = await connection.driver.createSchemaBuilder().log();
expect(sql1.upQueries).to.eql([]);
})));
describe("VersionUtils", () => {
describe("isGreaterOrEqual", () => {
it("should return false when comparing invalid versions", () => {
expect(VersionUtils.isGreaterOrEqual("", "")).to.equal(false);
});
it("should return false when targetVersion is larger", () => {
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.2.4")).to.equal(false);
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.4.3")).to.equal(false);
expect(VersionUtils.isGreaterOrEqual("1.2.3", "2.2.3")).to.equal(false);
expect(VersionUtils.isGreaterOrEqual("1.2", "1.3")).to.equal(false);
expect(VersionUtils.isGreaterOrEqual("1", "2")).to.equal(false);
expect(VersionUtils.isGreaterOrEqual(undefined as unknown as string, "0.0.1")).to.equal(false);
});
it("should return true when targetVersion is smaller", () => {
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.2.2")).to.equal(true);
expect(VersionUtils.isGreaterOrEqual("1.2.3", "1.1.3")).to.equal(true);
expect(VersionUtils.isGreaterOrEqual("1.2.3", "0.2.3")).to.equal(true);
expect(VersionUtils.isGreaterOrEqual("1.2", "1.2")).to.equal(true);
expect(VersionUtils.isGreaterOrEqual("1", "1")).to.equal(true);
});
it("should work with mariadb-style versions", () => {
const dbVersion = "10.4.8-MariaDB-1:10.4.8+maria~bionic";
expect(VersionUtils.isGreaterOrEqual("10.4.9", dbVersion)).to.equal(true);
expect(VersionUtils.isGreaterOrEqual("10.4.8", dbVersion)).to.equal(true);
expect(VersionUtils.isGreaterOrEqual("10.4.7", dbVersion)).to.equal(false);
});
});
});
});