feat: naming strategy for legacy Oracle (#9703)

* feat: naming strategy for legacy Oracle

This feature is LegacyOracleNamingStrategy which can be used to handle Oracle error ORA-00972.

* feat: naming strategy for legacy Oracle

This feature is LegacyOracleNamingStrategy which can be used to handle Oracle error ORA-00972.
This commit is contained in:
Filip Wróbel 2023-02-07 07:58:09 +01:00 committed by GitHub
parent 7df2ccf69d
commit 0eb74411d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 197 additions and 0 deletions

View File

@ -161,6 +161,7 @@ export { MigrationExecutor } from "./migration/MigrationExecutor"
export { MigrationInterface } from "./migration/MigrationInterface"
export { DefaultNamingStrategy } from "./naming-strategy/DefaultNamingStrategy"
export { NamingStrategyInterface } from "./naming-strategy/NamingStrategyInterface"
export { LegacyOracleNamingStrategy } from "./naming-strategy/LegacyOracleNamingStrategy"
export { InsertEvent } from "./subscriber/event/InsertEvent"
export { LoadEvent } from "./subscriber/event/LoadEvent"
export { UpdateEvent } from "./subscriber/event/UpdateEvent"

View File

@ -0,0 +1,71 @@
import { NamingStrategyInterface } from "./NamingStrategyInterface"
import { RandomGenerator } from "../util/RandomGenerator"
import { DefaultNamingStrategy } from "./DefaultNamingStrategy"
import { TypeORMError } from "../error"
/**
* Shorten strategy
*/
export type ShortenStrategy = "truncate" | "hash"
/**
* Naming strategy for legacy Oracle database with 30 bytes identifier limit.
*
* Currently, only column name must be shorten in order to avoid ORA-00972.
* Issues with other identifiers were fixed.
*/
export class LegacyOracleNamingStrategy
extends DefaultNamingStrategy
implements NamingStrategyInterface
{
public readonly IDENTIFIER_MAX_SIZE = 30
public readonly DEFAULT_COLUMN_PREFIX = "COL_"
protected shortenStrategy: ShortenStrategy
constructor(shortenStrategy: ShortenStrategy = "hash") {
super()
this.shortenStrategy = shortenStrategy
}
columnName(
propertyName: string,
customName: string,
embeddedPrefixes: string[],
): string {
const longName: string = super.columnName(
propertyName,
customName,
embeddedPrefixes,
)
if (this.shortenStrategy === "truncate") {
return this.truncateIdentifier(longName)
} else if (this.shortenStrategy === "hash") {
return this.hashIdentifier(longName, this.DEFAULT_COLUMN_PREFIX)
} else {
throw new TypeORMError(`Invalid shortenStrategy`)
}
}
protected hashIdentifier(input: string, prefix: string): string {
if (prefix.length >= this.IDENTIFIER_MAX_SIZE) {
throw new TypeORMError(
`Prefix must be shorter than IDENTIFIER_MAX_SIZE`,
)
}
return (
prefix +
RandomGenerator.sha1(input).substring(
0,
this.IDENTIFIER_MAX_SIZE - prefix.length,
)
)
}
protected truncateIdentifier(input: string): string {
if (input.length > this.IDENTIFIER_MAX_SIZE) {
return input.substring(0, this.IDENTIFIER_MAX_SIZE)
} else {
return input
}
}
}

View File

@ -0,0 +1,52 @@
import "reflect-metadata"
import { expect } from "chai"
import { LegacyOracleNamingStrategy } from "../../../../../src/naming-strategy/LegacyOracleNamingStrategy"
import { RandomGenerator } from "../../../../../src/util/RandomGenerator"
describe("LegacyOracleNamingStrategy > column shortening", () => {
it("should truncate column names to the limit", () => {
const legacyOracleNamingStrategy = new LegacyOracleNamingStrategy(
"truncate",
)
expect(
legacyOracleNamingStrategy.columnName("shortName", "", []),
).to.equal("shortName")
expect(
legacyOracleNamingStrategy.columnName(
"veryVeryVeryLongLongLongLongName",
"",
[],
),
).to.equal("veryVeryVeryLongLongLongLongNa")
expect(
legacyOracleNamingStrategy.columnName(
RandomGenerator.sha1("seed1"),
"",
[],
).length,
).to.lessThanOrEqual(legacyOracleNamingStrategy.IDENTIFIER_MAX_SIZE)
})
it("should change column names to hashes within the limit", () => {
const legacyOracleNamingStrategy = new LegacyOracleNamingStrategy(
"hash",
)
const columnName: string =
"veryVeryVeryLongLongLongLongName" + RandomGenerator.sha1("seed2")
const hashedColumnName: string = legacyOracleNamingStrategy.columnName(
columnName,
"",
[],
)
expect(hashedColumnName.length).to.lessThanOrEqual(
legacyOracleNamingStrategy.IDENTIFIER_MAX_SIZE,
)
expect(hashedColumnName)
.to.be.a("string")
.and.satisfy((name: string) =>
name.startsWith(
legacyOracleNamingStrategy.DEFAULT_COLUMN_PREFIX,
),
)
})
})

View File

@ -0,0 +1,31 @@
import "reflect-metadata"
import { expect } from "chai"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../../../utils/test-utils"
import { DataSource } from "../../../../../src/data-source"
import { LegacyOracleNamingStrategy } from "../../../../../src/naming-strategy/LegacyOracleNamingStrategy"
describe("LegacyOracleNamingStrategy > create table using this naming strategy", () => {
let connections: DataSource[]
before(
async () =>
(connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["oracle"],
namingStrategy: new LegacyOracleNamingStrategy("hash"),
})),
)
// without reloadTestingDatabases(connections) -> tables should be created later
after(() => closeTestingConnections(connections))
it("should create the table", () =>
Promise.all(
connections.map(async (connection) => {
await expect(reloadTestingDatabases([connection])).to.be
.fulfilled
}),
))
})

View File

@ -0,0 +1,30 @@
import "reflect-metadata"
import { expect } from "chai"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../../../utils/test-utils"
import { DataSource } from "../../../../../src/data-source"
describe("LegacyOracleNamingStrategy > create table using default naming strategy", () => {
let connections: DataSource[]
before(
async () =>
(connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["oracle"],
})),
)
// without reloadTestingDatabases(connections) -> tables should be created later
after(() => closeTestingConnections(connections))
it("should not create the table and fail due to ORA-00972", () =>
Promise.all(
connections.map(async (connection) => {
await expect(
reloadTestingDatabases([connection]),
).to.be.rejectedWith(/ORA-00972/gi)
}),
))
})

View File

@ -0,0 +1,12 @@
import { Entity } from "../../../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../../../src/decorator/columns/Column"
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
veryLongveryLongveryLongveryLongveryLongveryLongveryLongName: string
}