Skip to content

Commit 7ddaf23

Browse files
fix: unnecessary migrations for unsigned numeric types (#6632)
* fix: Unnecessary migrations for unsigned numeric types In MariaDB, unsigned numeric types (``<tiny,small,medium>int`) without an explicit `width` property set would generate migrations on every run. This is due to an error in setting the default width for unsigned types. Fixes #2943 * test: Enable all tests * refactor: Move isDefaultColumnWidth() method out of BaseQueryRunner See #6632 (review) for discussion as to why. * fix: Correct unsigned int behaviour for MySQL 5.7 * fix: Correct position of zerofill check Plus stylistic change based on code review
1 parent ae3cf0e commit 7ddaf23

File tree

5 files changed

+106
-23
lines changed

5 files changed

+106
-23
lines changed

src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,4 +1623,26 @@ export class AuroraDataApiQueryRunner extends BaseQueryRunner implements QueryRu
16231623
return c;
16241624
}
16251625

1626+
/**
1627+
* Checks if column display width is by default.
1628+
*/
1629+
protected isDefaultColumnWidth(table: Table, column: TableColumn, width: number): boolean {
1630+
// if table have metadata, we check if length is specified in column metadata
1631+
if (this.connection.hasMetadata(table.name)) {
1632+
const metadata = this.connection.getMetadata(table.name);
1633+
const columnMetadata = metadata.findColumnWithDatabaseName(column.name);
1634+
if (columnMetadata && columnMetadata.width)
1635+
return false;
1636+
}
1637+
1638+
const defaultWidthForType = this.connection.driver.dataTypeDefaults
1639+
&& this.connection.driver.dataTypeDefaults[column.type]
1640+
&& this.connection.driver.dataTypeDefaults[column.type].width;
1641+
1642+
if (defaultWidthForType) {
1643+
return defaultWidthForType === width;
1644+
}
1645+
return false;
1646+
}
1647+
16261648
}

src/driver/mysql/MysqlQueryRunner.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,8 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
12961296
tableColumn.name = dbColumn["COLUMN_NAME"];
12971297
tableColumn.type = dbColumn["DATA_TYPE"].toLowerCase();
12981298

1299+
tableColumn.zerofill = dbColumn["COLUMN_TYPE"].indexOf("zerofill") !== -1;
1300+
tableColumn.unsigned = tableColumn.zerofill ? true : dbColumn["COLUMN_TYPE"].indexOf("unsigned") !== -1;
12991301
if (this.driver.withWidthColumnTypes.indexOf(tableColumn.type as ColumnType) !== -1) {
13001302
const width = dbColumn["COLUMN_TYPE"].substring(dbColumn["COLUMN_TYPE"].indexOf("(") + 1, dbColumn["COLUMN_TYPE"].indexOf(")"));
13011303
tableColumn.width = width && !this.isDefaultColumnWidth(table, tableColumn, parseInt(width)) ? parseInt(width) : undefined;
@@ -1332,8 +1334,6 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
13321334
tableColumn.isPrimary = dbPrimaryKeys.some(dbPrimaryKey => {
13331335
return this.driver.buildTableName(dbPrimaryKey["TABLE_NAME"], undefined, dbPrimaryKey["TABLE_SCHEMA"]) === tableFullName && dbPrimaryKey["COLUMN_NAME"] === tableColumn.name;
13341336
});
1335-
tableColumn.zerofill = dbColumn["COLUMN_TYPE"].indexOf("zerofill") !== -1;
1336-
tableColumn.unsigned = tableColumn.zerofill ? true : dbColumn["COLUMN_TYPE"].indexOf("unsigned") !== -1;
13371337
tableColumn.isGenerated = dbColumn["EXTRA"].indexOf("auto_increment") !== -1;
13381338
if (tableColumn.isGenerated)
13391339
tableColumn.generationStrategy = "increment";
@@ -1694,4 +1694,35 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
16941694
return result[0]["version"];
16951695
}
16961696

1697+
/**
1698+
* Checks if column display width is by default.
1699+
*/
1700+
protected isDefaultColumnWidth(table: Table, column: TableColumn, width: number): boolean {
1701+
// if table have metadata, we check if length is specified in column metadata
1702+
if (this.connection.hasMetadata(table.name)) {
1703+
const metadata = this.connection.getMetadata(table.name);
1704+
const columnMetadata = metadata.findColumnWithDatabaseName(column.name);
1705+
if (columnMetadata && columnMetadata.width)
1706+
return false;
1707+
}
1708+
1709+
const defaultWidthForType = this.connection.driver.dataTypeDefaults
1710+
&& this.connection.driver.dataTypeDefaults[column.type]
1711+
&& this.connection.driver.dataTypeDefaults[column.type].width;
1712+
1713+
if (defaultWidthForType) {
1714+
// In MariaDB & MySQL 5.7, the default widths of certain numeric types are 1 less than
1715+
// the usual defaults when the column is unsigned.
1716+
const typesWithReducedUnsignedDefault = ["int", "tinyint", "smallint", "mediumint"];
1717+
const needsAdjustment = typesWithReducedUnsignedDefault.indexOf(column.type) !== -1;
1718+
if (column.unsigned && needsAdjustment) {
1719+
return (defaultWidthForType - 1) === width;
1720+
} else {
1721+
return defaultWidthForType === width;
1722+
}
1723+
}
1724+
1725+
return false;
1726+
}
1727+
16971728
}

src/query-runner/BaseQueryRunner.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -312,27 +312,6 @@ export abstract class BaseQueryRunner {
312312
return false;
313313
}
314314

315-
/**
316-
* Checks if column display width is by default. Used only for MySQL.
317-
*/
318-
protected isDefaultColumnWidth(table: Table, column: TableColumn, width: number): boolean {
319-
// if table have metadata, we check if length is specified in column metadata
320-
if (this.connection.hasMetadata(table.name)) {
321-
const metadata = this.connection.getMetadata(table.name);
322-
const columnMetadata = metadata.findColumnWithDatabaseName(column.name);
323-
if (columnMetadata && columnMetadata.width)
324-
return false;
325-
}
326-
327-
if (this.connection.driver.dataTypeDefaults
328-
&& this.connection.driver.dataTypeDefaults[column.type]
329-
&& this.connection.driver.dataTypeDefaults[column.type].width) {
330-
return this.connection.driver.dataTypeDefaults[column.type].width === width;
331-
}
332-
333-
return false;
334-
}
335-
336315
/**
337316
* Checks if column precision is by default.
338317
*/
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Column, Entity, PrimaryColumn } from "../../../../src";
2+
3+
@Entity()
4+
export class Test {
5+
@PrimaryColumn()
6+
id: number;
7+
8+
@Column({ type: 'int', unsigned: true})
9+
uInt: number;
10+
11+
@Column({ type: 'tinyint', unsigned: true})
12+
uTinyInt: number;
13+
14+
@Column({ type: 'smallint', unsigned: true})
15+
uSmallInt: number;
16+
17+
@Column({ type: 'mediumint', unsigned: true})
18+
uMediumInt: number;
19+
20+
@Column({ type: 'bigint', unsigned: true})
21+
uBigInt: number;
22+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import "reflect-metadata";
2+
import { expect } from "chai";
3+
import { Connection } from "../../../src";
4+
import { closeTestingConnections, createTestingConnections, reloadTestingDatabases } from "../../utils/test-utils";
5+
import { Test } from "./entity/Test";
6+
7+
describe("github issues > #2943 Inappropriate migration generated", () => {
8+
9+
let connections: Connection[];
10+
11+
before(async () => {
12+
connections = await createTestingConnections({
13+
enabledDrivers: ['mariadb', 'mysql'],
14+
entities: [Test],
15+
schemaCreate: true,
16+
dropSchema: true
17+
});
18+
});
19+
beforeEach(() => reloadTestingDatabases(connections));
20+
after(() => closeTestingConnections(connections));
21+
22+
it("should not create migrations for unsigned numeric types with no specified width", () =>
23+
Promise.all(connections.map(async (connection) => {
24+
const sqlInMemory = await connection.driver.createSchemaBuilder().log();
25+
26+
expect(sqlInMemory.upQueries).to.eql([]);
27+
expect(sqlInMemory.downQueries).to.eql([]);
28+
})));
29+
});

0 commit comments

Comments
 (0)