diff --git a/gulpfile.js b/gulpfile.js index 5e795ba..d8dac98 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -24,6 +24,7 @@ gulp.task('clean', function () { gulp.task('prettier', function () { return gulp.src('.prettierrc') .pipe(shell(['prettier ./src/**/*.ts --write'])) + .pipe(shell(['prettier ./src/*.ts --write'])) }); gulp.task('pre-commit', ['prettier'], function () { diff --git a/src/AbstractNamingStrategy.ts b/src/AbstractNamingStrategy.ts index ad4585b..c0ea4bf 100644 --- a/src/AbstractNamingStrategy.ts +++ b/src/AbstractNamingStrategy.ts @@ -2,7 +2,11 @@ import { RelationInfo } from "./models/RelationInfo"; import { DatabaseModel } from "./models/DatabaseModel"; export abstract class AbstractNamingStrategy { - abstract relationName(columnName: string, relation: RelationInfo, dbModel: DatabaseModel): string; + abstract relationName( + columnName: string, + relation: RelationInfo, + dbModel: DatabaseModel + ): string; abstract entityName(entityName: string): string; diff --git a/src/Engine.ts b/src/Engine.ts index 74d5d93..8f86dff 100644 --- a/src/Engine.ts +++ b/src/Engine.ts @@ -22,7 +22,8 @@ export class Engine { this.Options.password, this.Options.schemaName, this.Options.ssl, - this.Options.namingStrategy + this.Options.namingStrategy, + this.Options.relationIds ); if (dbModel.entities.length > 0) { this.createModelFromMetadata(dbModel); @@ -42,7 +43,8 @@ export class Engine { password: string, schemaName: string, ssl: boolean, - namingStrategy: AbstractNamingStrategy + namingStrategy: AbstractNamingStrategy, + relationIds: boolean ): Promise { return await this.driver.GetDataFromServer( database, @@ -52,7 +54,8 @@ export class Engine { password, schemaName, ssl, - namingStrategy + namingStrategy, + relationIds ); } private createModelFromMetadata(databaseModel: DatabaseModel) { @@ -276,4 +279,5 @@ export interface EngineOptions { lazy: boolean; constructor: boolean; namingStrategy: AbstractNamingStrategy; + relationIds: boolean; } diff --git a/src/NamingStrategy.ts b/src/NamingStrategy.ts index aef42a4..01d6b2f 100644 --- a/src/NamingStrategy.ts +++ b/src/NamingStrategy.ts @@ -4,54 +4,64 @@ import { DatabaseModel } from "./models/DatabaseModel"; export class NamingStrategy extends AbstractNamingStrategy { relationName( - columnOldName:string, + columnOldName: string, relation: RelationInfo, - dbModel:DatabaseModel + dbModel: DatabaseModel ): string { - let isRelationToMany = relation.isOneToMany || relation.isManyToMany + let isRelationToMany = relation.isOneToMany || relation.isManyToMany; let ownerEntity = dbModel.entities.filter(v => { - return v.EntityName==relation.ownerTable - })[0] + return v.EntityName == relation.ownerTable; + })[0]; let referencedEntity = dbModel.entities.filter(v => { - return v.EntityName == relation.relatedTable - })[0] + return v.EntityName == relation.relatedTable; + })[0]; - - - let columnName = columnOldName[0].toLowerCase()+ columnOldName.substring(1,columnOldName.length); - if ( columnName.endsWith("id")) { - columnName = columnName.substring(0, columnName.lastIndexOf("id")); + let columnName = + columnOldName[0].toLowerCase() + + columnOldName.substring(1, columnOldName.length); + if ( + columnName + .toLowerCase() + .endsWith( + "id" + ) /*&& !ownerEntity.Columns.some(x=>x.tsName==columnName && x.isPrimary)*/ + ) { + columnName = columnName.substring( + 0, + columnName.toLowerCase().lastIndexOf("id") + ); } - if (!isNaN(parseInt(columnName[columnName.length-1]))) { - let num = columnName[columnName.length-1] - columnName = columnName.substring(0, columnName.length - 1) - columnName += (isRelationToMany ? "s" : "")+num.toString(); - } else { - columnName += isRelationToMany ? "s" : ""; + if (!isNaN(parseInt(columnName[columnName.length - 1]))) { + columnName = columnName.substring(0, columnName.length - 1); } + if (!isNaN(parseInt(columnName[columnName.length - 1]))) { + columnName = columnName.substring(0, columnName.length - 1); + } + columnName += isRelationToMany ? "s" : ""; - if (relation.relationType!="ManyToMany") { - - if (columnOldName!=columnName) { - if (!relation.isOwner) { - if (ownerEntity.Columns.some(v => v.tsName == columnName)) { - columnName = columnName + "_" - for (let i = 2; i <= ownerEntity.Columns.length; i++) { - columnName = columnName.substring(0, columnName.length - 1) + i.toString(); - if (ownerEntity.Columns.every(v => v.tsName != columnName)) break; - } - } - } else { - if (referencedEntity.Columns.some(v => v.tsName == columnName)) { - columnName = columnName + "_" - for (let i = 2; i <= referencedEntity.Columns.length; i++) { - columnName = columnName.substring(0, columnName.length - 1) + i.toString(); - if (referencedEntity.Columns.every(v => v.tsName != columnName)) break; - } + if ( + relation.relationType != "ManyToMany" && + columnOldName != columnName + ) { + if (ownerEntity.Columns.some(v => v.tsName == columnName)) { + columnName = columnName + "_"; + for (let i = 2; i <= ownerEntity.Columns.length; i++) { + columnName = + columnName.substring( + 0, + columnName.length - i.toString().length + ) + i.toString(); + if ( + ownerEntity.Columns.every( + v => + v.tsName != columnName || + columnName == columnOldName + ) + ) + break; } } } - } return columnName; } diff --git a/src/drivers/AbstractDriver.ts b/src/drivers/AbstractDriver.ts index aeb25b5..9baa5c5 100644 --- a/src/drivers/AbstractDriver.ts +++ b/src/drivers/AbstractDriver.ts @@ -158,6 +158,7 @@ export abstract class AbstractDriver { "varbinary" ]; namingStrategy: AbstractNamingStrategy; + generateRelationsIds: boolean; FindManyToManyRelations(dbModel: DatabaseModel) { let manyToManyEntities = dbModel.entities.filter(entity => { @@ -238,8 +239,10 @@ export abstract class AbstractDriver { password: string, schema: string, ssl: boolean, - namingStrategy: AbstractNamingStrategy + namingStrategy: AbstractNamingStrategy, + relationIds: boolean ): Promise { + this.generateRelationsIds = relationIds; let dbModel = {}; this.namingStrategy = namingStrategy; await this.ConnectToServer(database, server, port, user, password, ssl); @@ -259,9 +262,9 @@ export abstract class AbstractDriver { } private ApplyNamingStrategy(dbModel: DatabaseModel) { - this.changeColumnNames(dbModel); - this.changeEntityNames(dbModel); this.changeRelationNames(dbModel); + this.changeEntityNames(dbModel); + this.changeColumnNames(dbModel); } abstract async ConnectToServer( @@ -386,6 +389,7 @@ export abstract class AbstractDriver { ownerRelation.relationType = isOneToMany ? "ManyToOne" : "OneToOne"; + ownerRelation.relationIdField = this.generateRelationsIds; let columnName = ownerEntity.EntityName; if ( @@ -394,8 +398,10 @@ export abstract class AbstractDriver { columnName = columnName + "_"; for (let i = 2; i <= referencedEntity.Columns.length; i++) { columnName = - columnName.substring(0, columnName.length - 1) + - i.toString(); + columnName.substring( + 0, + columnName.length - i.toString().length + ) + i.toString(); if ( referencedEntity.Columns.every( v => v.tsName != columnName diff --git a/src/entity.mst b/src/entity.mst index 9da5ef7..1cba3ed 100644 --- a/src/entity.mst +++ b/src/entity.mst @@ -1,4 +1,4 @@ -import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm"; +import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm"; {{relationImports}}{{#each UniqueImports}}import {{curly true}}{{toEntityName this}}{{curly false}} from "./{{toFileName this}}"; {{/each}} @@ -28,7 +28,11 @@ import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, Man {{#if isManyToMany}}@JoinTable(){{else}}@JoinColumn({ name:'{{ ../sqlName}}'}){{/if}}{{/isOwner}} {{#if (or isOneToMany isManyToMany)}}{{toPropertyName ../tsName}}:{{toLazy (concat (toEntityName relatedTable) "[]")}}; {{else}}{{toPropertyName ../tsName}}:{{toLazy (concat (toEntityName relatedTable) ' | null')}}; - {{/if}}{{/relations}} + {{/if}} + {{#if relationIdField }} + + @RelationId(({{../../EntityName}}: {{../../EntityName}}) => {{../../EntityName}}.{{toPropertyName ../tsName}}) + {{toPropertyName ../tsName}}Id: {{#if isOneToOne}}{{toLazy ../ts_type}}{{else}}{{toLazy (concat ../ts_type "[]")}}{{/if}};{{/if}}{{/relations}} {{/Columns}} {{#if GenerateConstructor}} constructor(init?: Partial<{{toEntityName EntityName}}>) { diff --git a/src/index.ts b/src/index.ts index af8c144..151d6ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,7 +87,12 @@ var argv = Yargs.usage( default: false }) .option("namingStrategy", { - describe: "Use custom naming strategy", + describe: "Use custom naming strategy" + }) + .option("relationIds", { + describe: "Generate RelationId fields", + boolean: true, + default: false }) .option("generateConstructor", { describe: "Generate constructor allowing partial initialization", @@ -118,7 +123,7 @@ switch (argv.e) { standardUser = "root"; break; case "mariadb": - driver = new MysqlDriver(); + driver = new MariaDbDriver(); standardPort = 3306; standardUser = "root"; break; @@ -134,17 +139,15 @@ switch (argv.e) { default: TomgUtils.LogError("Database engine not recognized.", false); throw new Error("Database engine not recognized."); - } - let namingStrategy: AbstractNamingStrategy; -if (argv.namingStrategy && argv.namingStrategy!='') { +if (argv.namingStrategy && argv.namingStrategy != "") { let req = require(argv.namingStrategy); - namingStrategy= new req.NamingStrategy(); + namingStrategy = new req.NamingStrategy(); } else { - namingStrategy= new NamingStrategy(); + namingStrategy = new NamingStrategy(); } -debugger; + let engine = new Engine(driver, { host: argv.h, port: parseInt(argv.p) || standardPort, @@ -161,6 +164,7 @@ let engine = new Engine(driver, { convertCaseProperty: argv.cp, lazy: argv.lazy, constructor: argv.generateConstructor, + relationIds: argv.relationIds, namingStrategy: namingStrategy }); diff --git a/src/models/ColumnInfo.ts b/src/models/ColumnInfo.ts index 9540496..455c4d4 100644 --- a/src/models/ColumnInfo.ts +++ b/src/models/ColumnInfo.ts @@ -25,7 +25,6 @@ export class ColumnInfo { numericScale: number | null = null; enumOptions: string | null = null; relations: RelationInfo[]; - constructor() { this.relations = []; } diff --git a/src/models/RelationInfo.ts b/src/models/RelationInfo.ts index 3a39f87..58fc7fe 100644 --- a/src/models/RelationInfo.ts +++ b/src/models/RelationInfo.ts @@ -7,6 +7,7 @@ export class RelationInfo { ownerColumn: string; actionOnDelete: "RESTRICT" | "CASCADE" | "SET NULL" | null; actionOnUpdate: "RESTRICT" | "CASCADE" | "SET NULL" | null; + relationIdField: boolean = false; get isOneToMany(): boolean { return this.relationType == "OneToMany"; @@ -14,4 +15,10 @@ export class RelationInfo { get isManyToMany(): boolean { return this.relationType == "ManyToMany"; } + get isOneToOne(): boolean { + return this.relationType == "OneToOne"; + } + get isManyToOne(): boolean { + return this.relationType == "OneToOne"; + } } diff --git a/test/drivers/MssqlDriver.test.ts b/test/drivers/MssqlDriver.test.ts index 4a61143..0e28241 100644 --- a/test/drivers/MssqlDriver.test.ts +++ b/test/drivers/MssqlDriver.test.ts @@ -95,7 +95,7 @@ describe('MssqlDriver', function () { ts_type: 'number', enumOptions: null, is_unique:false, - relations: [] + relations: [], }) let result = await driver.GetCoulmnsFromEntity(entities, 'schema'); expect(result).to.be.deep.equal(expected) diff --git a/test/integration/github-issues/65/entity/Post.ts b/test/integration/github-issues/65/entity/Post.ts new file mode 100644 index 0000000..abe57e9 --- /dev/null +++ b/test/integration/github-issues/65/entity/Post.ts @@ -0,0 +1,24 @@ +import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm"; +import { PostAuthor } from "./PostAuthor"; +import { PostReader } from "./PostReader"; + + +@Entity("Post") +export class Post { + + @Column("int",{ + nullable:false, + primary:true, + name:"Id" + }) + Id:number; + + + + @OneToOne(type => PostAuthor, PostAuthor => PostAuthor.Id) + postAuthor: PostAuthor; + + @OneToMany(type => PostReader, PostReader => PostReader.Id) + postReaders: PostReader[]; + +} diff --git a/test/integration/github-issues/65/entity/PostAuthor.ts b/test/integration/github-issues/65/entity/PostAuthor.ts new file mode 100644 index 0000000..8ff3809 --- /dev/null +++ b/test/integration/github-issues/65/entity/PostAuthor.ts @@ -0,0 +1,23 @@ +import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm"; +import { Post } from "./Post"; + + +@Entity("PostAuthor") +export class PostAuthor { + + @Column("int",{ + nullable:false, + primary:true, + name:"Id" + }) + Id:number; + + + + @OneToOne(type => Post, Post => Post.Id) + @JoinColumn() + post:Post; + + @RelationId((postAuthor: PostAuthor) => postAuthor.post) + postId: number; +} diff --git a/test/integration/github-issues/65/entity/PostReader.ts b/test/integration/github-issues/65/entity/PostReader.ts new file mode 100644 index 0000000..1fcc60e --- /dev/null +++ b/test/integration/github-issues/65/entity/PostReader.ts @@ -0,0 +1,21 @@ +import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm"; +import { Post } from "./Post"; + + +@Entity("PostReader") +export class PostReader { + + @Column("int",{ + nullable:false, + primary:true, + name:"Id" + }) + Id:number; + + @ManyToOne(type => Post, Post => Post.Id) + @JoinColumn() + post:Post; + + @RelationId((postReader: PostReader) => postReader.post) + postId: number[]; +} diff --git a/test/integration/githubIssues.test.ts b/test/integration/githubIssues.test.ts index 7a9a4c8..1c348ed 100644 --- a/test/integration/githubIssues.test.ts +++ b/test/integration/githubIssues.test.ts @@ -78,6 +78,14 @@ describe("GitHub issues", async function () { break; } + switch (folder) { + case '65': + engine.Options.relationIds = true; + break; + default: + break; + } + await engine.createModelFromDatabase() let filesGenPath = path.resolve(resultsPath, 'entities') diff --git a/test/utils/GeneralTestUtils.ts b/test/utils/GeneralTestUtils.ts index 38e8d14..b1fa87f 100644 --- a/test/utils/GeneralTestUtils.ts +++ b/test/utils/GeneralTestUtils.ts @@ -67,7 +67,8 @@ export async function createMSSQLModels(filesOrgPath: string, resultsPath: strin convertCaseProperty: 'none', lazy: false, constructor: false, - namingStrategy: namingStrategy + namingStrategy: namingStrategy, + relationIds:false }); conn = await createConnection(connOpt) @@ -134,7 +135,8 @@ export async function createPostgresModels(filesOrgPath: string, resultsPath: st convertCaseProperty: 'none', lazy: false, constructor:false, - namingStrategy: namingStrategy + namingStrategy: namingStrategy, + relationIds: false }); conn = await createConnection(connOpt) @@ -193,7 +195,8 @@ export async function createSQLiteModels(filesOrgPath: string, resultsPath: stri convertCaseProperty: 'none', lazy: false, constructor:false, - namingStrategy: namingStrategy + namingStrategy: namingStrategy, + relationIds: false }); conn = await createConnection(connOpt) @@ -250,7 +253,8 @@ export async function createMysqlModels(filesOrgPath: string, resultsPath: strin convertCaseProperty: 'none', lazy: false, constructor:false, - namingStrategy: namingStrategy + namingStrategy: namingStrategy, + relationIds: false }); return engine; @@ -300,7 +304,8 @@ export async function createMariaDBModels(filesOrgPath: string, resultsPath: str convertCaseProperty: 'none', lazy: false, constructor:false, - namingStrategy: namingStrategy + namingStrategy: namingStrategy, + relationIds: false }); @@ -353,7 +358,8 @@ export async function createOracleDBModels(filesOrgPath: string, resultsPath: st convertCaseProperty: 'none', lazy: false, constructor:false, - namingStrategy: namingStrategy + namingStrategy: namingStrategy, + relationIds: false }); return engine;