diff --git a/src/Engine.ts b/src/Engine.ts index 98f25be..c1d5910 100644 --- a/src/Engine.ts +++ b/src/Engine.ts @@ -46,7 +46,11 @@ export async function createModelFromDatabase( connectionOptions: IConnectionOptions, generationOptions: IGenerationOptions ) { - let dbModel = await dataCollectionPhase(driver, connectionOptions); + let dbModel = await dataCollectionPhase( + driver, + connectionOptions, + generationOptions + ); if (dbModel.length === 0) { TomgUtils.LogError( "Tables not found in selected database. Skipping creation of typeorm model.", @@ -123,9 +127,10 @@ export async function createModelFromDatabase( } export async function dataCollectionPhase( driver: AbstractDriver, - connectionOptions: IConnectionOptions + connectionOptions: IConnectionOptions, + generationOptions: IGenerationOptions ) { - return driver.GetDataFromServer(connectionOptions); + return driver.GetDataFromServer(connectionOptions, generationOptions); } export function modelCustomizationPhase( diff --git a/src/Utils.ts b/src/Utils.ts index eae30f5..38fdb98 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -37,6 +37,9 @@ export function findNameForNewField( ) && entity.relations.every( v => v.fieldName.toLowerCase() !== fieldName.toLowerCase() + ) && + entity.relationIds.every( + v => v.fieldName.toLowerCase() !== fieldName.toLowerCase() )) || (columnOldName && columnOldName.toLowerCase() === fieldName.toLowerCase()); diff --git a/src/drivers/AbstractDriver.ts b/src/drivers/AbstractDriver.ts index ad50998..7963e01 100644 --- a/src/drivers/AbstractDriver.ts +++ b/src/drivers/AbstractDriver.ts @@ -13,6 +13,8 @@ import IConnectionOptions from "../IConnectionOptions"; import { Entity } from "../models/Entity"; import { RelationInternal } from "../models/RelationInternal"; import { Relation } from "../models/Relation"; +import IGenerationOptions from "../IGenerationOptions"; +import { Column } from "../models/Column"; export default abstract class AbstractDriver { public abstract standardPort: number; @@ -175,7 +177,8 @@ export default abstract class AbstractDriver { } public async GetDataFromServer( - connectionOptions: IConnectionOptions + connectionOptions: IConnectionOptions, + generationOptions: IGenerationOptions ): Promise { let dbModel = [] as Entity[]; await this.ConnectToServer(connectionOptions); @@ -200,7 +203,8 @@ export default abstract class AbstractDriver { dbModel = await this.GetRelations( dbModel, sqlEscapedSchema, - connectionOptions.databaseName + connectionOptions.databaseName, + generationOptions ); await this.DisconnectFromServer(); dbModel = AbstractDriver.FindManyToManyRelations(dbModel); @@ -234,6 +238,7 @@ export default abstract class AbstractDriver { columns: [], indices: [], relations: [], + relationIds: [], sqlName: val.TABLE_NAME, tscName: val.TABLE_NAME, database: dbNames.includes(",") ? val.DB_NAME : "", @@ -246,7 +251,8 @@ export default abstract class AbstractDriver { public static GetRelationsFromRelationTempInfo( relationsTemp: RelationInternal[], - entities: Entity[] + entities: Entity[], + generationOptions: IGenerationOptions ) { relationsTemp.forEach(relationTmp => { if (relationTmp.ownerColumns.length > 1) { @@ -298,8 +304,8 @@ export default abstract class AbstractDriver { return; } - let ownerRelation: Relation | undefined; - let relatedRelation: Relation | undefined; + const ownerColumns: Column[] = []; + const relatedColumns: Column[] = []; for ( let relationColumnIndex = 0; relationColumnIndex < relationTmp.ownerColumns.length; @@ -327,77 +333,91 @@ export default abstract class AbstractDriver { ); return; } - let isOneToMany: boolean; - isOneToMany = false; - const index = ownerEntity.indices.find( - ind => - ind.options.unique && - ind.columns.length === 1 && - ind.columns[0] === ownerColumn!.tscName - ); - isOneToMany = !index; - - if (true) { - // TODO: RelationId - ownerEntity.columns = ownerEntity.columns.filter( - v => - !relationTmp.ownerColumns.some( - u => u === v.tscName - ) || v.primary - ); - relationTmp.relatedTable.columns = relationTmp.relatedTable.columns.filter( - v => - !relationTmp.relatedColumns.some( - u => u === v.tscName - ) || v.primary - ); - } - let fieldName = ""; - if (relationTmp.ownerColumns.length === 1) { - fieldName = TomgUtils.findNameForNewField( - ownerColumn.tscName, - ownerEntity - ); - } else { - fieldName = TomgUtils.findNameForNewField( - relationTmp.relatedTable.tscName, - ownerEntity - ); - } - ownerRelation = { - fieldName, - relatedField: TomgUtils.findNameForNewField( - relationTmp.ownerTable.tscName, - relationTmp.relatedTable - ), - relationOptions: { - onDelete: relationTmp.onDelete, - onUpdate: relationTmp.onUpdate - }, - joinColumnOptions: relationTmp.ownerColumns.map( - (v, idx) => { - const retVal: JoinColumnOptions = { - name: v, - referencedColumnName: - relationTmp.relatedColumns[idx] - }; - return retVal; - } - ), - relatedTable: relationTmp.relatedTable.tscName, - relationType: isOneToMany ? "ManyToOne" : "OneToOne" - }; - relatedRelation = { - fieldName: ownerRelation.relatedField, - relatedField: ownerRelation.fieldName, - relatedTable: relationTmp.ownerTable.tscName, - // relationOptions: ownerRelation.relationOptions, - relationType: isOneToMany ? "OneToMany" : "OneToOne" - }; + ownerColumns.push(ownerColumn); + relatedColumns.push(relatedColumn); + } + let isOneToMany: boolean; + isOneToMany = false; + const index = ownerEntity.indices.find( + ind => + ind.options.unique && + ind.columns.length === ownerColumns.length && + ownerColumns.every(ownerColumn => + ind.columns.some(col => col === ownerColumn.tscName) + ) + ); + isOneToMany = !index; + + // TODO: RelationId + ownerEntity.columns = ownerEntity.columns.filter( + v => + !relationTmp.ownerColumns.some(u => u === v.tscName) || + v.primary + ); + relationTmp.relatedTable.columns = relationTmp.relatedTable.columns.filter( + v => + !relationTmp.relatedColumns.some(u => u === v.tscName) || + v.primary + ); + let fieldName = ""; + if (ownerColumns.length === 1) { + fieldName = TomgUtils.findNameForNewField( + ownerColumns[0].tscName, + ownerEntity + ); + } else { + fieldName = TomgUtils.findNameForNewField( + relationTmp.relatedTable.tscName, + ownerEntity + ); + } + + const ownerRelation: Relation = { + fieldName, + relatedField: TomgUtils.findNameForNewField( + relationTmp.ownerTable.tscName, + relationTmp.relatedTable + ), + relationOptions: { + onDelete: relationTmp.onDelete, + onUpdate: relationTmp.onUpdate + }, + joinColumnOptions: relationTmp.ownerColumns.map((v, idx) => { + const retVal: JoinColumnOptions = { + name: v, + referencedColumnName: relationTmp.relatedColumns[idx] + }; + return retVal; + }), + relatedTable: relationTmp.relatedTable.tscName, + relationType: isOneToMany ? "ManyToOne" : "OneToOne" + }; + const relatedRelation: Relation = { + fieldName: ownerRelation.relatedField, + relatedField: ownerRelation.fieldName, + relatedTable: relationTmp.ownerTable.tscName, + // relationOptions: ownerRelation.relationOptions, + relationType: isOneToMany ? "OneToMany" : "OneToOne" + }; + + ownerEntity.relations.push(ownerRelation); + relationTmp.relatedTable.relations.push(relatedRelation); + + if (generationOptions.relationIds && ownerColumns.length === 1) { + let relationIdFieldName = ""; + relationIdFieldName = TomgUtils.findNameForNewField( + ownerColumns[0].tscName, + ownerEntity + ); + ownerEntity.relationIds.push({ + fieldName: relationIdFieldName, // TODO: generate name without number(naming strategy) + fieldType: isOneToMany + ? `${ownerColumns[0].tscType}[]` + : ownerColumns[0].tscType, + relationField: ownerRelation.fieldName // TODO: naming strategy + }); + // TODO: RelationId on ManyToMany } - if (ownerRelation) ownerEntity.relations.push(ownerRelation); - if (relatedRelation) - relationTmp.relatedTable.relations.push(relatedRelation); }); return entities; } @@ -417,7 +437,8 @@ export default abstract class AbstractDriver { public abstract async GetRelations( entities: Entity[], schema: string, - dbNames: string + dbNames: string, + generationOptions: IGenerationOptions ): Promise; public static FindPrimaryColumnsFromIndexes(dbModel: Entity[]) { diff --git a/src/drivers/MysqlDriver.ts b/src/drivers/MysqlDriver.ts index 8531daf..96561fe 100644 --- a/src/drivers/MysqlDriver.ts +++ b/src/drivers/MysqlDriver.ts @@ -14,6 +14,7 @@ import { Entity } from "../models/Entity"; import { Column } from "../models/Column"; import { Index } from "../models/Index"; import { RelationInternal } from "../models/RelationInternal"; +import IGenerationOptions from "../IGenerationOptions"; export default class MysqlDriver extends AbstractDriver { public defaultValues: DataTypeDefaults = new TypeormDriver.MysqlDriver({ @@ -316,7 +317,8 @@ export default class MysqlDriver extends AbstractDriver { public async GetRelations( entities: Entity[], schema: string, - dbNames: string + dbNames: string, + generationOptions: IGenerationOptions ): Promise { const response = await this.ExecQuery<{ TableWithForeignKey: string; @@ -385,7 +387,8 @@ export default class MysqlDriver extends AbstractDriver { const retVal = MysqlDriver.GetRelationsFromRelationTempInfo( relationsTemp, - entities + entities, + generationOptions ); return retVal; } diff --git a/src/drivers/SqliteDriver.ts b/src/drivers/SqliteDriver.ts index 7a84e5d..8904902 100644 --- a/src/drivers/SqliteDriver.ts +++ b/src/drivers/SqliteDriver.ts @@ -44,6 +44,7 @@ export default class SqliteDriver extends AbstractDriver { columns: [], indices: [], relations: [], + relationIds: [], sqlName: val.tbl_name, tscName: val.tbl_name, fileImports: [] diff --git a/src/entity.mst b/src/entity.mst index 8114e10..0c7f940 100644 --- a/src/entity.mst +++ b/src/entity.mst @@ -15,6 +15,11 @@ import { {{toEntityName .}} } from './{{toFileName .}}' {{#if joinTableOptions}}@JoinTable({ {{json joinTableOptions}} }){{/if}} {{toPropertyName fieldName}}:{{toRelation (toEntityName relatedTable) relationType}}; +{{/inline}} +{{#*inline "RelationId"}} +@RelationId(({{toPropertyName entityName}}:{{toEntityName entityName}})=>{{toPropertyName entityName}}.{{toPropertyName relationField}}) +{{toPropertyName fieldName}}:{{fieldType}}; + {{/inline}} {{#*inline "Entity"}} {{#indices}}{{> Index}}{{/indices~}} @@ -23,6 +28,7 @@ export class {{toEntityName tscName}} { {{#columns}}{{> Column}}{{/columns~}} {{#relations}}{{> Relation}}{{/relations~}} +{{#relationIds}}{{> RelationId entityName=../tscName}}{{/relationIds~}} } {{/inline}} diff --git a/src/models/Entity.ts b/src/models/Entity.ts index 9a2c566..019aa20 100644 --- a/src/models/Entity.ts +++ b/src/models/Entity.ts @@ -1,6 +1,7 @@ import { Column } from "./Column"; import { Relation } from "./Relation"; import { Index } from "./Index"; +import { RelationId } from "./RelationId"; export type Entity = { sqlName: string; @@ -10,6 +11,7 @@ export type Entity = { schema?: string; columns: Column[]; + relationIds: RelationId[]; relations: Relation[]; indices: Index[]; fileImports: string[]; diff --git a/src/models/RelationId.ts b/src/models/RelationId.ts new file mode 100644 index 0000000..bef6676 --- /dev/null +++ b/src/models/RelationId.ts @@ -0,0 +1,5 @@ +export type RelationId = { + fieldName: string; + fieldType: string; + relationField: string; +}; diff --git a/test/integration/runTestsFromPath.test.ts b/test/integration/runTestsFromPath.test.ts index 96cbba8..c957d2b 100644 --- a/test/integration/runTestsFromPath.test.ts +++ b/test/integration/runTestsFromPath.test.ts @@ -94,14 +94,16 @@ function runTestForMultipleDrivers( driver, Object.assign(connectionOptions, { databaseName: "db1,db2" - }) + }), + generationOptions ); break; default: dbModel = await dataCollectionPhase( driver, - connectionOptions + connectionOptions, + generationOptions ); break; } @@ -160,7 +162,11 @@ async function runTest( resultsPath, filesOrgPathTS } = await prepareTestRuns(testPartialPath, dbDriver, dbDriver); - let dbModel = await dataCollectionPhase(driver, connectionOptions); + let dbModel = await dataCollectionPhase( + driver, + connectionOptions, + generationOptions + ); dbModel = modelCustomizationPhase( dbModel, generationOptions,