Skip to content

Commit

Permalink
fix: check for version of MariaDB before extracting COLUMN_DEFAULT (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
jeremija authored and pleerock committed Oct 18, 2019
1 parent d967180 commit c30b485
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ services:

# mariadb
mariadb:
image: "mariadb:10.1.37"
image: "mariadb:10.4.8"
container_name: "typeorm-mariadb"
ports:
- "3307:3306"
Expand Down
17 changes: 16 additions & 1 deletion src/driver/mysql/MysqlQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 changes: 20 additions & 0 deletions src/util/VersionUtils.ts
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 12 additions & 0 deletions test/github-issues/4782/entity/Item.ts
Original file line number Diff line number Diff line change
@@ -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;
}
56 changes: 56 additions & 0 deletions test/github-issues/4782/issue-4782.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});

});

0 comments on commit c30b485

Please sign in to comment.