From 642e57bf74e44fa5de6929fdb82f7be7d00d88de Mon Sep 17 00:00:00 2001 From: Kononnable Date: Sat, 27 May 2017 00:55:10 +0200 Subject: [PATCH] added relation tests - problems with distinguishing OneTwoMany from OneToOne without indexes --- src/drivers/MssqlDriver.ts | 132 +++++++++++------- src/entity.mst | 14 +- src/models/ColumnInfo.ts | 1 + src/models/RelationInfo.ts | 18 ++- src/models/RelationTempInfo.ts | 1 + test/drivers/MssqlDriver.test.ts | 1 + .../sample2-one-to-one/entity/Post.ts | 68 +++++++++ .../sample2-one-to-one/entity/PostAuthor.ts | 16 +++ .../sample2-one-to-one/entity/PostCategory.ts | 12 ++ .../sample2-one-to-one/entity/PostDetails.ts | 26 ++++ .../sample2-one-to-one/entity/PostImage.ts | 16 +++ .../entity/PostInformation.ts | 18 +++ .../sample2-one-to-one/entity/PostMetadata.ts | 16 +++ test/integration/integration.test.ts | 2 +- test/utils/EntityFileToJson.ts | 69 +++++++-- 15 files changed, 335 insertions(+), 75 deletions(-) create mode 100644 test/integration/examples/sample2-one-to-one/entity/Post.ts create mode 100644 test/integration/examples/sample2-one-to-one/entity/PostAuthor.ts create mode 100644 test/integration/examples/sample2-one-to-one/entity/PostCategory.ts create mode 100644 test/integration/examples/sample2-one-to-one/entity/PostDetails.ts create mode 100644 test/integration/examples/sample2-one-to-one/entity/PostImage.ts create mode 100644 test/integration/examples/sample2-one-to-one/entity/PostInformation.ts create mode 100644 test/integration/examples/sample2-one-to-one/entity/PostMetadata.ts diff --git a/src/drivers/MssqlDriver.ts b/src/drivers/MssqlDriver.ts index 16220fa..c90309b 100644 --- a/src/drivers/MssqlDriver.ts +++ b/src/drivers/MssqlDriver.ts @@ -1,22 +1,23 @@ import { AbstractDriver } from './AbstractDriver' import * as MSSQL from 'mssql' -import {ColumnInfo} from './../models/ColumnInfo' -import {EntityInfo} from './../models/EntityInfo' -import {DatabaseModel} from './../models/DatabaseModel' +import { ColumnInfo } from './../models/ColumnInfo' +import { EntityInfo } from './../models/EntityInfo' +import { RelationInfo } from './../models/RelationInfo' +import { DatabaseModel } from './../models/DatabaseModel' /** * MssqlDriver */ export class MssqlDriver extends AbstractDriver { FindPrimaryColumnsFromIndexes(dbModel: DatabaseModel) { dbModel.entities.forEach(entity => { - let primaryIndex = entity.Indexes.find(v=>v.isPrimaryKey); - if (!primaryIndex){ + let primaryIndex = entity.Indexes.find(v => v.isPrimaryKey); + if (!primaryIndex) { console.error(`Table ${entity.EntityName} has no PK.`) return; } - let pIndex=primaryIndex //typescript error? pIndex:IndexInfo; primaryIndex:IndexInfo|undefined - entity.Columns.forEach(col=>{ - if(pIndex.columns.some( cIndex=> cIndex.name==col.name)) col.isPrimary=true + let pIndex = primaryIndex //typescript error? pIndex:IndexInfo; primaryIndex:IndexInfo|undefined + entity.Columns.forEach(col => { + if (pIndex.columns.some(cIndex => cIndex.name == col.name)) col.isPrimary = true }) }); } @@ -37,9 +38,11 @@ export class MssqlDriver extends AbstractDriver { } async GetCoulmnsFromEntity(entities: EntityInfo[]): Promise { let request = new MSSQL.Request(this.Connection) - let response: { TABLE_NAME: string, COLUMN_NAME: string, COLUMN_DEFAULT: string, - IS_NULLABLE: string, DATA_TYPE: string, CHARACTER_MAXIMUM_LENGTH: number, - NUMERIC_PRECISION:number,NUMERIC_SCALE:number,IsIdentity:number }[] + let response: { + TABLE_NAME: string, COLUMN_NAME: string, COLUMN_DEFAULT: string, + IS_NULLABLE: string, DATA_TYPE: string, CHARACTER_MAXIMUM_LENGTH: number, + NUMERIC_PRECISION: number, NUMERIC_SCALE: number, IsIdentity: number + }[] = await request.query(`SELECT TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,IS_NULLABLE, DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') IsIdentity FROM INFORMATION_SCHEMA.COLUMNS`); @@ -116,8 +119,8 @@ export class MssqlDriver extends AbstractDriver { case "decimal": colInfo.ts_type = "number" colInfo.sql_type = "decimal" - colInfo.numericPrecision=resp.NUMERIC_PRECISION - colInfo.numericScale=resp.NUMERIC_SCALE + colInfo.numericPrecision = resp.NUMERIC_PRECISION + colInfo.numericScale = resp.NUMERIC_SCALE break; case "xml": colInfo.ts_type = "string" @@ -127,7 +130,7 @@ export class MssqlDriver extends AbstractDriver { console.error("Unknown column type:" + resp.DATA_TYPE); break; } - colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH>0?resp.CHARACTER_MAXIMUM_LENGTH:null; + colInfo.char_max_lenght = resp.CHARACTER_MAXIMUM_LENGTH > 0 ? resp.CHARACTER_MAXIMUM_LENGTH : null; if (colInfo.sql_type) ent.Columns.push(colInfo); }) }) @@ -192,7 +195,8 @@ ORDER BY let response: { TableWithForeignKey: string, FK_PartNo: number, ForeignKeyColumn: string, TableReferenced: string, ForeignKeyColumnReferenced: string, - onDelete: "RESTRICT" | "CASCADE" | "SET NULL", object_id: number + onDelete: "RESTRICT" | "CASCADE" | "SET NULL", + onUpdate: "RESTRICT" | "CASCADE" | "SET NULL", object_id: number }[] = await request.query(`select parentTable.name as TableWithForeignKey, @@ -201,6 +205,7 @@ ORDER BY referencedTable.name as TableReferenced, referencedColumn.name as ForeignKeyColumnReferenced, fk.delete_referential_action_desc as onDelete, + fk.update_referential_action_desc as onUpdate, fk.object_id from sys.foreign_keys fk @@ -228,6 +233,7 @@ order by rels.ownerColumnsNames = []; rels.referencedColumnsNames = []; rels.actionOnDelete = resp.onDelete; + rels.actionOnUpdate = resp.onUpdate; rels.object_id = resp.object_id; rels.ownerTable = resp.TableWithForeignKey; rels.referencedTable = resp.TableReferenced; @@ -236,66 +242,84 @@ order by rels.ownerColumnsNames.push(resp.ForeignKeyColumn); rels.referencedColumnsNames.push(resp.ForeignKeyColumnReferenced); }) - relationsTemp.forEach( (relationTmp)=>{ - let ownerEntity = entities.find((entitity)=>{ - return entitity.EntityName==relationTmp.ownerTable; + relationsTemp.forEach((relationTmp) => { + let ownerEntity = entities.find((entitity) => { + return entitity.EntityName == relationTmp.ownerTable; }) - if (!ownerEntity){ + if (!ownerEntity) { console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity model ${relationTmp.ownerTable}.`) return; } - let referencedEntity = entities.find((entitity)=>{ - return entitity.EntityName==relationTmp.referencedTable; + let referencedEntity = entities.find((entitity) => { + return entitity.EntityName == relationTmp.referencedTable; }) - if (!referencedEntity){ + if (!referencedEntity) { console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity model ${relationTmp.referencedTable}.`) return; } - let ownerColumn = ownerEntity.Columns.find((column)=>{ - return column.name==relationTmp.ownerColumnsNames[0]; + let ownerColumn = ownerEntity.Columns.find((column) => { + return column.name == relationTmp.ownerColumnsNames[0]; }) - if(!ownerColumn){ + if (!ownerColumn) { console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity column ${relationTmp.ownerTable}.${ownerColumn}.`) return; } - let relatedColumn = referencedEntity.Columns.find((column)=>{ - return column.name==relationTmp.referencedColumnsNames[0]; + let relatedColumn = referencedEntity.Columns.find((column) => { + return column.name == relationTmp.referencedColumnsNames[0]; }) - if(!relatedColumn){ + if (!relatedColumn) { console.error(`Relation between tables ${relationTmp.ownerTable} and ${relationTmp.referencedTable} didn't found entity column ${relationTmp.referencedTable}.${relatedColumn}.`) return; } - let ownColumn:ColumnInfo = ownerColumn; - let isOneToMany:boolean; - isOneToMany=false; + let ownColumn: ColumnInfo = ownerColumn; + let isOneToMany: boolean; + isOneToMany = false; let index = ownerEntity.Indexes.find( - (index)=>{ - return index.isUnique && index.columns.some(col=>{ - return col.name==ownColumn.name + (index) => { + return index.isUnique && index.columns.some(col => { + return col.name == ownColumn.name }) } ) - if (!index){ - isOneToMany=true; - }else{ - isOneToMany=false; + if (!index) { + isOneToMany = true; + } else { + isOneToMany = false; + } + let ownerRelation=new RelationInfo() + ownerRelation.actionOnDelete= relationTmp.actionOnDelete + ownerRelation.actionOnUpdate= relationTmp.actionOnUpdate + ownerRelation.isOwner= true + ownerRelation.relatedColumn= relatedColumn.name.toLowerCase() + ownerRelation.relatedTable= relationTmp.referencedTable + ownerRelation.relationType= isOneToMany ? "OneToMany" : "OneToOne" + ownerColumn.relations.push(ownerRelation) + if (isOneToMany) { + let col = new ColumnInfo() + col.name = ownerEntity.EntityName.toLowerCase() //+ 's' + let referencedRelation = new RelationInfo(); + col.relations.push(referencedRelation) + referencedRelation.actionOnDelete= relationTmp.actionOnDelete + referencedRelation.actionOnUpdate= relationTmp.actionOnUpdate + referencedRelation.isOwner= false + referencedRelation.relatedColumn= ownerColumn.name + referencedRelation.relatedTable= relationTmp.ownerTable + referencedRelation.relationType= "ManyToOne" + referencedEntity.Columns.push(col) + } else { + let col = new ColumnInfo() + col.name = ownerEntity.EntityName.toLowerCase() + let referencedRelation = new RelationInfo(); + col.relations.push(referencedRelation) + referencedRelation.actionOnDelete= relationTmp.actionOnDelete + referencedRelation.actionOnUpdate= relationTmp.actionOnUpdate + referencedRelation.isOwner= false + referencedRelation.relatedColumn= ownerColumn.name + referencedRelation.relatedTable= relationTmp.ownerTable + referencedRelation.relationType= "OneToOne" + + referencedEntity.Columns.push(col) } - - ownerColumn.relations.push({ - actionOnDelete:relationTmp.actionOnDelete, - isOwner:true, - relatedColumn:relatedColumn.name, - relatedTable:relationTmp.referencedTable, - relationType:isOneToMany?"OneToMany":"OneToOne" - }) - relatedColumn.relations.push({ - actionOnDelete:relationTmp.actionOnDelete, - isOwner:false, - relatedColumn:ownerColumn.name, - relatedTable:relationTmp.ownerTable, - relationType:isOneToMany?"ManyToOne":"OneToOne" - }) - }) return entities; } diff --git a/src/entity.mst b/src/entity.mst index a044d80..ffe45ab 100644 --- a/src/entity.mst +++ b/src/entity.mst @@ -1,4 +1,4 @@ -import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, JoinTable} from "typeorm"; +import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, JoinColumn} from "typeorm"; {{relationImports}} @Entity() @@ -7,7 +7,7 @@ import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, Joi {{#Columns}} - @Column("{{sql_type}}",{ {{#is_generated}} + {{^relations}} @Column("{{sql_type}}",{ {{#is_generated}} generated:true,{{/is_generated}}{{#is_nullable}} nullable:true,{{/is_nullable}}{{^is_nullable}} nullable:false,{{/is_nullable}}{{#char_max_lenght}} @@ -16,9 +16,13 @@ import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, Joi precision:{{numericPrecision}},{{/numericPrecision}}{{#numericScale}} scale:{{numericScale}},{{/numericScale}}{{#isPrimary}} primary:{{isPrimary}},{{/isPrimary}} - }){{#relations}} - @{{relationType}}(type=>{{relatedTable}},x=>x.{{relatedColumn}}){{#isOwner}} - @JoinTable(){{/isOwner}}{{/relations}} + }) {{name}}:{{ts_type}}; + {{/relations}}{{#relations}} + @{{relationType}}(type=>{{relatedTable}},x=>x.{{relatedColumn}}){{#isOwner}} + @JoinColumn(){{/isOwner}} + {{#if isOneToMany}}{{../name}}:{{relatedTable}}[]; + {{else}}{{../name}}:{{relatedTable}}; + {{/if}}{{/relations}} {{/Columns}} } \ No newline at end of file diff --git a/src/models/ColumnInfo.ts b/src/models/ColumnInfo.ts index 5f8a952..a8030e2 100644 --- a/src/models/ColumnInfo.ts +++ b/src/models/ColumnInfo.ts @@ -1,3 +1,4 @@ +import {RelationInfo} from './RelationInfo' /** * ColumnInfo */ diff --git a/src/models/RelationInfo.ts b/src/models/RelationInfo.ts index f9801aa..999e7c5 100644 --- a/src/models/RelationInfo.ts +++ b/src/models/RelationInfo.ts @@ -1,7 +1,13 @@ -interface RelationInfo { - isOwner: boolean, - relationType: "OneToOne", "OneToMany", "ManyToOne" - relatedTable: string, - relatedColumn: string, - actionOnDelete:"RESTRICT"|"CASCADE"|"SET NULL", +export class RelationInfo { + isOwner: boolean + relationType: "OneToOne" | "OneToMany" | "ManyToOne" + relatedTable: string + relatedColumn: string + actionOnDelete: "RESTRICT" | "CASCADE" | "SET NULL" + actionOnUpdate: "RESTRICT" | "CASCADE" | "SET NULL" + + get isOneToMany(): boolean { + return this.relationType == "OneToMany" + } + } \ No newline at end of file diff --git a/src/models/RelationTempInfo.ts b/src/models/RelationTempInfo.ts index 81f00de..5b9e57e 100644 --- a/src/models/RelationTempInfo.ts +++ b/src/models/RelationTempInfo.ts @@ -4,5 +4,6 @@ interface RelationTempInfo{ referencedTable:string, referencedColumnsNames:string[], actionOnDelete:"RESTRICT"|"CASCADE"|"SET NULL", + actionOnUpdate:"RESTRICT"|"CASCADE"|"SET NULL", object_id:number } \ No newline at end of file diff --git a/test/drivers/MssqlDriver.test.ts b/test/drivers/MssqlDriver.test.ts index b42f26f..916a371 100644 --- a/test/drivers/MssqlDriver.test.ts +++ b/test/drivers/MssqlDriver.test.ts @@ -4,6 +4,7 @@ import * as Sinon from 'sinon' import * as MSSQL from 'mssql' import { EntityInfo } from './../../src/models/EntityInfo' import { ColumnInfo } from './../../src/models/ColumnInfo' +import { RelationInfo } from './../../src/models/RelationInfo' describe('MssqlDriver', function () { diff --git a/test/integration/examples/sample2-one-to-one/entity/Post.ts b/test/integration/examples/sample2-one-to-one/entity/Post.ts new file mode 100644 index 0000000..c557b6e --- /dev/null +++ b/test/integration/examples/sample2-one-to-one/entity/Post.ts @@ -0,0 +1,68 @@ +import {PrimaryGeneratedColumn, Column, Entity, OneToOne,JoinColumn} from "typeorm"; +import {PostDetails} from "./PostDetails"; +import {PostCategory} from "./PostCategory"; +import {PostAuthor} from "./PostAuthor"; +import {PostInformation} from "./PostInformation"; +import {PostImage} from "./PostImage"; +import {PostMetadata} from "./PostMetadata"; + +@Entity("Post") +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @Column() + text: string; + + // post has relation with category, however inverse relation is not set (category does not have relation with post set) + @OneToOne(type => PostCategory, { + cascadeInsert: true, + cascadeUpdate: true, + cascadeRemove: true + }) + @JoinColumn() + category: PostCategory; + + // post has relation with details. cascade inserts here means if new PostDetails instance will be set to this + // relation it will be inserted automatically to the db when you save this Post entity + @OneToOne(type => PostDetails, details => details.post, { + cascadeInsert: true + }) + @JoinColumn() + details: PostDetails; + + // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation + // it will be inserted automatically to the db when you save this Post entity + @OneToOne(type => PostImage, image => image.post, { + cascadeUpdate: true + }) + @JoinColumn() + image: PostImage; + + // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation + // it will be inserted automatically to the db when you save this Post entity + @OneToOne(type => PostMetadata, metadata => metadata.post, { + cascadeRemove: true + }) + @JoinColumn() + metadata: PostMetadata|null; + + // post has relation with details. full cascades here + @OneToOne(type => PostInformation, information => information.post, { + cascadeInsert: true, + cascadeUpdate: true, + cascadeRemove: true + }) + @JoinColumn() + information: PostInformation; + + // post has relation with details. not cascades here. means cannot be persisted, updated or removed + @OneToOne(type => PostAuthor, author => author.post) + @JoinColumn() + author: PostAuthor; + +} \ No newline at end of file diff --git a/test/integration/examples/sample2-one-to-one/entity/PostAuthor.ts b/test/integration/examples/sample2-one-to-one/entity/PostAuthor.ts new file mode 100644 index 0000000..9dfb66e --- /dev/null +++ b/test/integration/examples/sample2-one-to-one/entity/PostAuthor.ts @@ -0,0 +1,16 @@ +import {PrimaryGeneratedColumn, Column, Entity, OneToOne,JoinColumn} from "typeorm"; +import {Post} from "./Post"; + +@Entity("PostAuthor") +export class PostAuthor { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @OneToOne(type => Post, post => post.author) + post: Post; + +} \ No newline at end of file diff --git a/test/integration/examples/sample2-one-to-one/entity/PostCategory.ts b/test/integration/examples/sample2-one-to-one/entity/PostCategory.ts new file mode 100644 index 0000000..6b302b0 --- /dev/null +++ b/test/integration/examples/sample2-one-to-one/entity/PostCategory.ts @@ -0,0 +1,12 @@ +import {PrimaryGeneratedColumn, Column, Entity, OneToOne,JoinColumn} from "typeorm"; + +@Entity("PostCategory") +export class PostCategory { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + +} \ No newline at end of file diff --git a/test/integration/examples/sample2-one-to-one/entity/PostDetails.ts b/test/integration/examples/sample2-one-to-one/entity/PostDetails.ts new file mode 100644 index 0000000..ca47932 --- /dev/null +++ b/test/integration/examples/sample2-one-to-one/entity/PostDetails.ts @@ -0,0 +1,26 @@ +import {PrimaryGeneratedColumn, Column, Entity, OneToOne,JoinColumn} from "typeorm"; +import {Post} from "./Post"; + +@Entity("PostDetails") +export class PostDetails { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + authorName: string; + + @Column() + comment: string; + + @Column() + metadata: string; + + @OneToOne(type => Post, post => post.details, { + cascadeInsert: true, + cascadeUpdate: true, + cascadeRemove: true + }) + post: Post; + +} \ No newline at end of file diff --git a/test/integration/examples/sample2-one-to-one/entity/PostImage.ts b/test/integration/examples/sample2-one-to-one/entity/PostImage.ts new file mode 100644 index 0000000..afea4c8 --- /dev/null +++ b/test/integration/examples/sample2-one-to-one/entity/PostImage.ts @@ -0,0 +1,16 @@ +import {PrimaryGeneratedColumn, Column, Entity, OneToOne,JoinColumn} from "typeorm"; +import {Post} from "./Post"; + +@Entity("PostImage") +export class PostImage { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + url: string; + + @OneToOne(type => Post, post => post.image) + post: Post; + +} \ No newline at end of file diff --git a/test/integration/examples/sample2-one-to-one/entity/PostInformation.ts b/test/integration/examples/sample2-one-to-one/entity/PostInformation.ts new file mode 100644 index 0000000..60229c4 --- /dev/null +++ b/test/integration/examples/sample2-one-to-one/entity/PostInformation.ts @@ -0,0 +1,18 @@ +import {PrimaryGeneratedColumn, Column, Entity, OneToOne,JoinColumn} from "typeorm"; +import {Post} from "./Post"; + +@Entity("PostInformation") +export class PostInformation { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + text: string; + + @OneToOne(type => Post, post => post.information, { + cascadeUpdate: true, + }) + post: Post; + +} \ No newline at end of file diff --git a/test/integration/examples/sample2-one-to-one/entity/PostMetadata.ts b/test/integration/examples/sample2-one-to-one/entity/PostMetadata.ts new file mode 100644 index 0000000..c2f75d1 --- /dev/null +++ b/test/integration/examples/sample2-one-to-one/entity/PostMetadata.ts @@ -0,0 +1,16 @@ +import {PrimaryGeneratedColumn, Column, Entity, OneToOne,JoinColumn} from "typeorm"; +import {Post} from "./Post"; + +@Entity("PostMetadata") +export class PostMetadata { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + description: string; + + @OneToOne(type => Post, post => post.metadata) + post: Post; + +} \ No newline at end of file diff --git a/test/integration/integration.test.ts b/test/integration/integration.test.ts index b3031c2..2427477 100644 --- a/test/integration/integration.test.ts +++ b/test/integration/integration.test.ts @@ -80,7 +80,7 @@ describe("integration tests", async function () { let entftj = new EntityFileToJson(); let jsonEntityOrg= entftj.convert(fs.readFileSync(path.resolve(filesOrgPath, file))) let jsonEntityGen= entftj.convert(fs.readFileSync(path.resolve(filesGenPath, file))) - expect(jsonEntityGen).to.containSubset(jsonEntityOrg) + expect(jsonEntityGen,`Error in file ${file}`).to.containSubset(jsonEntityOrg) } }); diff --git a/test/utils/EntityFileToJson.ts b/test/utils/EntityFileToJson.ts index 8a522d7..346bc3d 100644 --- a/test/utils/EntityFileToJson.ts +++ b/test/utils/EntityFileToJson.ts @@ -4,7 +4,12 @@ export class EntityFileToJson { if (decoratorParameters.length > 0) { if (decoratorParameters.search(',') > 0) { - col.columnType = decoratorParameters.substring(0, decoratorParameters.indexOf(',')).trim() + col.columnTypes = decoratorParameters.substring(0, decoratorParameters.indexOf(',')).trim().split('|').map(function (x) { + if (!x.endsWith('[]')) { + x = x + '[]'// can't distinguish OneTwoMany from OneToOne without indexes + } + return x; + }); let badJSON = decoratorParameters.substring(decoratorParameters.indexOf(',') + 1).trim() if (badJSON.lastIndexOf(',') == badJSON.length - 3) { badJSON = badJSON.slice(0, badJSON.length - 3) + badJSON[badJSON.length - 2] + badJSON[badJSON.length - 1] @@ -12,7 +17,12 @@ export class EntityFileToJson { col.columnOptions = JSON.parse(badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')) } else { if (decoratorParameters[0] == '"' && decoratorParameters.endsWith('"')) { - col.columnType = decoratorParameters + col.columnTypes = decoratorParameters.split('|').map(function (x) { + if (!x.endsWith('[]')) { + x = x + '[]'// can't distinguish OneTwoMany from OneToOne without indexes + } + return x; + }); } else { let badJSON = decoratorParameters.substring(decoratorParameters.indexOf(',') + 1).trim() if (badJSON.lastIndexOf(',') == badJSON.length - 3) { @@ -34,6 +44,9 @@ export class EntityFileToJson { let lines = entityFile.toString().replace('\r', '').split('\n'); for (let line of lines) { let trimmedLine = line.trim(); + if (trimmedLine.startsWith('//')) { + continue; //commented line + } if (isMultilineStatement) trimmedLine = priorPartOfMultilineStatement + ' ' + trimmedLine if (trimmedLine.length == 0) @@ -97,8 +110,9 @@ export class EntityFileToJson { continue; } else { isMultilineStatement = false; - retVal.columns.push(new EntityColumn()) - //TODO:Options,relation options if declared + let column = new EntityColumn() + retVal.columns.push(column) + column.relationType = "OneToMany"//"ManyToOne" - can't distinguish OneTwoMany from OneToOne without indexes continue; } } else if (trimmedLine.startsWith('@OneToMany')) { @@ -108,13 +122,47 @@ export class EntityFileToJson { continue; } else { isMultilineStatement = false; - retVal.columns.push(new EntityColumn()) - //TODO:Options, relation options if declared + let column = new EntityColumn() + retVal.columns.push(column) + column.relationType = "OneToMany" + continue; + } + } else if (trimmedLine.startsWith('@OneToOne')) { + if (this.isPartOfMultilineStatement(trimmedLine)) { + isMultilineStatement = true; + priorPartOfMultilineStatement = trimmedLine; + continue; + } else { + isMultilineStatement = false; + let column = new EntityColumn() + retVal.columns.push(column) + column.relationType = "OneToMany"//"OneToOne" - can't distinguish OneTwoMany from OneToOne without indexes + continue; + } + } else if (trimmedLine.startsWith('@JoinColumn')) { + if (this.isPartOfMultilineStatement(trimmedLine)) { + isMultilineStatement = true; + priorPartOfMultilineStatement = trimmedLine; + continue; + } else { + isMultilineStatement = false; + retVal.columns[retVal.columns.length - 1].isOwnerOfRelation = true; continue; } } else if (trimmedLine.split(':').length - 1 > 0) { retVal.columns[retVal.columns.length - 1].columnName = trimmedLine.split(':')[0].trim(); - retVal.columns[retVal.columns.length - 1].columnType = trimmedLine.split(':')[1].split(';')[0].trim(); + //TODO:Should check if null only column is nullable? + retVal.columns[retVal.columns.length - 1].columnTypes = trimmedLine.split(':')[1].split(';')[0].trim().split('|').map(function (x) { + if (!x.endsWith('[]')) { + x = x + '[]'// can't distinguish OneTwoMany from OneToOne without indexes + } + return x; + }); + + if (!retVal.columns[retVal.columns.length - 1].columnTypes.some(function (this, val, ind, arr) { + return val == "null" ? true : false; + })) retVal.columns[retVal.columns.length - 1].columnTypes.push('null[]') + continue } else if (trimmedLine = '}') { isInClassBody = false; @@ -122,7 +170,8 @@ export class EntityFileToJson { } } - console.log(`[EntityFileToJson:convert] Line not recognized: ${trimmedLine}`) + console.log(`[EntityFileToJson:convert] Line not recognized in entity ${retVal.entityName}:`) + console.log(`${trimmedLine}`) } return retVal; } @@ -141,6 +190,8 @@ class EntityJson { } class EntityColumn { columnName: string - columnType: string + columnTypes: string[] columnOptions: any = {} + relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "None" = "None" + isOwnerOfRelation: boolean = false; } \ No newline at end of file