many to many relations

This commit is contained in:
Kononnable 2018-03-26 22:41:50 +02:00
parent d46738e932
commit f7e7cdd649
12 changed files with 287 additions and 14 deletions

View File

@ -160,6 +160,32 @@ export class Engine {
Handlebars.registerHelper("toLowerCase", str => {
return str.toLowerCase();
});
Handlebars.registerHelper({
eq: function(v1, v2) {
return v1 === v2;
},
ne: function(v1, v2) {
return v1 !== v2;
},
lt: function(v1, v2) {
return v1 < v2;
},
gt: function(v1, v2) {
return v1 > v2;
},
lte: function(v1, v2) {
return v1 <= v2;
},
gte: function(v1, v2) {
return v1 >= v2;
},
and: function(v1, v2) {
return v1 && v2;
},
or: function(v1, v2) {
return v1 || v2;
}
});
}
//TODO:Move to mustache template file

View File

@ -1,10 +1,71 @@
import { EntityInfo } from "./../models/EntityInfo";
import { DatabaseModel } from "./../models/DatabaseModel";
import * as TomgUtils from "./../Utils";
import { RelationInfo } from "../models/RelationInfo";
import { ColumnInfo } from "../models/ColumnInfo";
import { ManyToMany } from "typeorm";
/**
* AbstractDriver
*/
export abstract class AbstractDriver {
FindManyToManyRelations(dbModel: DatabaseModel) {
let manyToManyEntities = dbModel.entities.filter(entity => {
return (
entity.Columns.filter(column => {
return (
column.relations.length == 1 &&
!column.relations[0].isOneToMany &&
column.relations[0].isOwner
);
}).length == entity.Columns.length
);
});
manyToManyEntities.map(entity => {
let relations: RelationInfo[] = [];
relations = entity.Columns.reduce((prev: RelationInfo[], curr) => {
return prev.concat(curr.relations);
}, relations);
//TODO: Composed keys
if (relations.length == 2) {
let relatedTable1 = dbModel.entities.filter(
v => v.EntityName == relations[0].relatedTable
)[0];
relatedTable1.Columns = relatedTable1.Columns.filter(
v => v.name != entity.EntityName
);
let relatedTable2 = dbModel.entities.filter(
v => v.EntityName == relations[1].relatedTable
)[0];
relatedTable2.Columns = relatedTable2.Columns.filter(
v => v.name != entity.EntityName
);
dbModel.entities = dbModel.entities.filter(ent => {
return ent.EntityName != entity.EntityName;
});
let column1 = new ColumnInfo();
column1.name = relations[1].relatedTable;
let col1Rel = new RelationInfo();
col1Rel.relatedTable = relations[1].relatedTable;
col1Rel.relatedColumn = relations[1].relatedTable;
col1Rel.relationType = "ManyToMany";
col1Rel.isOwner = true;
col1Rel.ownerColumn = relations[0].relatedTable;
column1.relations.push(col1Rel);
relatedTable1.Columns.push(column1);
let column2 = new ColumnInfo();
column2.name = relations[0].relatedTable;
let col2Rel = new RelationInfo();
col2Rel.relatedTable = relations[0].relatedTable;
col2Rel.relatedColumn = relations[1].relatedTable;
col2Rel.relationType = "ManyToMany";
col2Rel.isOwner = false;
column2.relations.push(col2Rel);
relatedTable2.Columns.push(column2);
}
});
}
async GetDataFromServer(
database: string,
server: string,
@ -25,6 +86,7 @@ export abstract class AbstractDriver {
sqlEscapedSchema
);
await this.DisconnectFromServer();
this.FindManyToManyRelations(dbModel);
this.FindPrimaryColumnsFromIndexes(dbModel);
return dbModel;
}

View File

@ -1,4 +1,4 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, JoinColumn} from "typeorm";
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm";
{{relationImports}}{{#each UniqueImports}}import {{curly true}}{{toEntityName this}}{{curly false}} from "./{{toFileName this}}";
{{/each}}
@ -23,8 +23,8 @@ import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, Joi
{{toPropertyName name}}:{{ts_type}};
{{/relations}}{{#relations}}
@{{relationType}}(type=>{{toEntityName relatedTable}}, {{toPropertyName ../name}}=>{{toPropertyName ../name}}.{{#if isOwner}}{{toPropertyName ownerColumn}}{{else}}{{toPropertyName relatedColumn}}{{/if}}){{#isOwner}}
@JoinColumn({ name:'{{ ../name}}'}){{/isOwner}}
{{#if isOneToMany}}{{toPropertyName ../name}}:{{toEntityName relatedTable}}[];
{{#if isManyToMany}}@JoinTable(){{else}}@JoinColumn({ name:'{{ ../name}}'}){{/if}}{{/isOwner}}
{{#if (or isOneToMany isManyToMany)}}{{toPropertyName ../name}}:{{toEntityName relatedTable}}[];
{{else}}{{toPropertyName ../name}}:{{toEntityName relatedTable}};
{{/if}}{{/relations}}
{{/Columns}}

View File

@ -2,7 +2,7 @@ export class RelationInfo {
[x: string]: any;
isOwner: boolean;
relationType: "OneToOne" | "OneToMany" | "ManyToOne";
relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany";
relatedTable: string;
relatedColumn: string;
ownerTable: string;
@ -13,4 +13,7 @@ export class RelationInfo {
get isOneToMany(): boolean {
return this.relationType == "OneToMany";
}
get isManyToMany(): boolean {
return this.relationType == "ManyToMany";
}
}

View File

@ -0,0 +1,62 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } 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)
@ManyToMany(type => PostCategory, {
cascade: true
})
@JoinTable()
PostCategory: 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
@ManyToMany(type => PostDetails, details => details.Post, {
cascade: true
})
@JoinTable()
PostDetails: 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
@ManyToMany(type => PostImage, image => image.Post, {
cascade: true
})
@JoinTable()
PostImage: 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
@ManyToMany(type => PostMetadata, metadata => metadata.Post)
@JoinTable()
PostMetadata: PostMetadata[];
// post has relation with details. full cascades here
@ManyToMany(type => PostInformation, information => information.Post, {
cascade: true
})
@JoinTable()
PostInformation: PostInformation[];
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
@ManyToMany(type => PostAuthor, author => author.Post)
@JoinTable()
PostAuthor: PostAuthor[];
}

View File

@ -0,0 +1,16 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {Post} from "./Post";
@Entity("PostAuthor")
export class PostAuthor {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(type => Post, post => post.PostAuthor)
Post: Post[];
}

View File

@ -0,0 +1,12 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
@Entity("PostCategory")
export class PostCategory {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}

View File

@ -0,0 +1,22 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {Post} from "./Post";
@Entity("PostDetails")
export class PostDetails {
@PrimaryGeneratedColumn()
id: number;
@Column()
authorName: string;
@Column()
comment: string;
@Column()
metadata: string;
@ManyToMany(type => Post, post => post.PostDetails)
Post: Post[];
}

View File

@ -0,0 +1,16 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {Post} from "./Post";
@Entity("PostImage")
export class PostImage {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@ManyToMany(type => Post, post => post.PostImage)
Post: Post[];
}

View File

@ -0,0 +1,16 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {Post} from "./Post";
@Entity("PostInformation")
export class PostInformation {
@PrimaryGeneratedColumn()
id: number;
@Column()
text: string;
@ManyToMany(type => Post, post => post.PostInformation)
Post: Post[];
}

View File

@ -0,0 +1,16 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {Post} from "./Post";
@Entity("PostMetadata")
export class PostMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column()
description: string;
@ManyToMany(type => Post, post => post.PostMetadata)
Post: Post[];
}

View File

@ -5,15 +5,15 @@ export class EntityFileToJson {
let decoratorParameters = trimmedLine.slice(trimmedLine.indexOf('(') + 1, trimmedLine.lastIndexOf(')'))
if (decoratorParameters.length > 0) {
if (decoratorParameters[0] == '"' && decoratorParameters.endsWith('"')) {
if (decoratorParameters[0] == '"' && decoratorParameters.endsWith('"')) {
} else {
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]
}
ent.entityOptions = JSON.parse(badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '))
} else {
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]
}
ent.entityOptions = JSON.parse(badJSON.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '))
}
}
}
getColumnOptionsAndType(trimmedLine: string, col: EntityColumn) {
@ -126,8 +126,8 @@ export class EntityFileToJson {
priorPartOfMultilineStatement = trimmedLine;
continue;
} else {
let options = trimmedLine.substring(trimmedLine.lastIndexOf('{'), trimmedLine.lastIndexOf('}')+1).trim().toLowerCase()
this.getEntityOptions(trimmedLine,retVal);
let options = trimmedLine.substring(trimmedLine.lastIndexOf('{'), trimmedLine.lastIndexOf('}') + 1).trim().toLowerCase()
this.getEntityOptions(trimmedLine, retVal);
continue;
}
} else if (trimmedLine.startsWith('export class')) {
@ -225,6 +225,18 @@ export class EntityFileToJson {
column.relationType = "OneToMany"
continue;
}
} else if (trimmedLine.startsWith('@ManyToMany')) {
if (this.isPartOfMultilineStatement(trimmedLine)) {
isMultilineStatement = true;
priorPartOfMultilineStatement = trimmedLine;
continue;
} else {
isMultilineStatement = false;
let column = new EntityColumn()
retVal.columns.push(column)
column.relationType = "ManyToMany"
continue;
}
} else if (trimmedLine.startsWith('@OneToOne')) {
if (this.isPartOfMultilineStatement(trimmedLine)) {
isMultilineStatement = true;
@ -247,6 +259,16 @@ export class EntityFileToJson {
retVal.columns[retVal.columns.length - 1].isOwnerOfRelation = true;
continue;
}
} else if (trimmedLine.startsWith('@JoinTable')) {
if (this.isPartOfMultilineStatement(trimmedLine)) {
isMultilineStatement = true;
priorPartOfMultilineStatement = trimmedLine;
continue;
} else {
isMultilineStatement = false;
retVal.columns[retVal.columns.length - 1].isOwnerOfRelation = true;
continue;
}
} else if (trimmedLine.startsWith('@Index')) {
if (this.isPartOfMultilineStatement(trimmedLine)) {
isMultilineStatement = true;
@ -332,7 +354,7 @@ class EntityColumn {
columnName: string
columnTypes: string[] = []
columnOptions: any = {}
relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "None" = "None"
relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany" | "None" = "None"
isOwnerOfRelation: boolean = false;
}
class EntityIndex {