mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
added embedded decorator and metadata
This commit is contained in:
parent
0264d1fc10
commit
249b30bc91
63
sample/sample26-embedded-tables/app.ts
Normal file
63
sample/sample26-embedded-tables/app.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import "reflect-metadata";
|
||||
import {createConnection, CreateConnectionOptions} from "../../src/index";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Question} from "./entity/Question";
|
||||
import {Counters} from "./entity/Counters";
|
||||
|
||||
const options: CreateConnectionOptions = {
|
||||
driver: "mysql",
|
||||
connection: {
|
||||
host: "192.168.99.100",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "admin",
|
||||
database: "test",
|
||||
autoSchemaCreate: true,
|
||||
logging: {
|
||||
logOnlyFailedQueries: true,
|
||||
logFailedQueryError: true
|
||||
}
|
||||
},
|
||||
entities: [Post, Question, Counters]
|
||||
};
|
||||
|
||||
createConnection(options).then(connection => {
|
||||
|
||||
let postRepository = connection.getRepository(Post);
|
||||
let questionRepository = connection.getRepository(Question);
|
||||
|
||||
const questionPromise = questionRepository.findOneById(1).then(question => {
|
||||
if (!question) {
|
||||
question = new Question();
|
||||
question.title = "Umed";
|
||||
return questionRepository.persist(question).then(savedAuthor => {
|
||||
return questionRepository.findOneById(1);
|
||||
});
|
||||
}
|
||||
return question;
|
||||
});
|
||||
|
||||
const postPromise = postRepository.findOneById(1).then(post => {
|
||||
if (!post) {
|
||||
post = new Post();
|
||||
post.title = "Hello post";
|
||||
post.text = "This is post contents";
|
||||
return postRepository.persist(post).then(savedPost => {
|
||||
return postRepository.findOneById(1);
|
||||
});
|
||||
}
|
||||
return post;
|
||||
});
|
||||
|
||||
return Promise.all<any>([questionPromise, postPromise])
|
||||
.then(results => {
|
||||
const [question, post] = results;
|
||||
question.posts = [post];
|
||||
return questionRepository.persist(question);
|
||||
})
|
||||
.then(savedAuthor => {
|
||||
console.log("Question has been saved: ", savedAuthor);
|
||||
})
|
||||
.catch(error => console.log(error.stack));
|
||||
|
||||
}, error => console.log("Cannot connect: ", error));
|
||||
19
sample/sample26-embedded-tables/entity/Counters.ts
Normal file
19
sample/sample26-embedded-tables/entity/Counters.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {Column} from "../../../src/columns";
|
||||
import {EmbeddableTable} from "../../../src/decorator/tables/EmbeddableTable";
|
||||
|
||||
@EmbeddableTable()
|
||||
export class Counters {
|
||||
|
||||
@Column()
|
||||
raiting: number;
|
||||
|
||||
@Column()
|
||||
stars: number;
|
||||
|
||||
@Column()
|
||||
commentCount: number;
|
||||
|
||||
@Column()
|
||||
metadata: string;
|
||||
|
||||
}
|
||||
21
sample/sample26-embedded-tables/entity/Post.ts
Normal file
21
sample/sample26-embedded-tables/entity/Post.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/columns";
|
||||
import {Table} from "../../../src/tables";
|
||||
import {Counters} from "./Counters";
|
||||
import {Embedded} from "../../../src/decorator/Embedded";
|
||||
|
||||
@Table("sample26_post")
|
||||
export class Post {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column()
|
||||
text: string;
|
||||
|
||||
@Embedded(type => Counters)
|
||||
counters: Counters;
|
||||
|
||||
}
|
||||
18
sample/sample26-embedded-tables/entity/Question.ts
Normal file
18
sample/sample26-embedded-tables/entity/Question.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/columns";
|
||||
import {Table} from "../../../src/tables";
|
||||
import {Counters} from "./Counters";
|
||||
import {Embedded} from "../../../src/decorator/Embedded";
|
||||
|
||||
@Table("sample26_question")
|
||||
export class Question {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Embedded(type => Counters)
|
||||
counters: Counters;
|
||||
|
||||
}
|
||||
22
src/decorator/Embedded.ts
Normal file
22
src/decorator/Embedded.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {ConstructorFunction} from "../common/ConstructorFunction";
|
||||
import {getMetadataArgsStorage} from "../index";
|
||||
import {EmbeddedMetadataArgs} from "../metadata-args/EmbeddedMetadataArgs";
|
||||
|
||||
/**
|
||||
* Property in entity can be marked as Embedded, and on persist all columns from the embedded are mapped to the
|
||||
* single table of the entity where Embedded is used. And on hydration all columns which supposed to be in the
|
||||
* embedded will be mapped to it from the single table.
|
||||
*/
|
||||
export function Embedded<T>(typeFunction: (type?: any) => ConstructorFunction<T>) {
|
||||
return function (object: Object, propertyName: string) {
|
||||
// const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
|
||||
|
||||
const args: EmbeddedMetadataArgs = {
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
// propertyType: reflectedType,
|
||||
type: typeFunction
|
||||
};
|
||||
getMetadataArgsStorage().embeddeds.add(args);
|
||||
};
|
||||
}
|
||||
16
src/decorator/tables/EmbeddableTable.ts
Normal file
16
src/decorator/tables/EmbeddableTable.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {getMetadataArgsStorage} from "../../index";
|
||||
import {TableMetadataArgs} from "../../metadata-args/TableMetadataArgs";
|
||||
|
||||
/**
|
||||
* This decorators is used on the entities that must be embedded into the tables.
|
||||
*/
|
||||
export function EmbeddableTable(): Function {
|
||||
return function (target: Function) {
|
||||
const args: TableMetadataArgs = {
|
||||
target: target,
|
||||
name: name,
|
||||
type: "embeddable"
|
||||
};
|
||||
getMetadataArgsStorage().tables.add(args);
|
||||
};
|
||||
}
|
||||
21
src/metadata-args/EmbeddedMetadataArgs.ts
Normal file
21
src/metadata-args/EmbeddedMetadataArgs.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Arguments for EmbeddedMetadata class.
|
||||
*/
|
||||
export interface EmbeddedMetadataArgs {
|
||||
|
||||
/**
|
||||
* Class to which this column is applied.
|
||||
*/
|
||||
readonly target: Function;
|
||||
|
||||
/**
|
||||
* Class's property name to which this column is applied.
|
||||
*/
|
||||
readonly propertyName: string;
|
||||
|
||||
/**
|
||||
* Type of the class to be embedded.
|
||||
*/
|
||||
readonly type: ((type?: any) => Function);
|
||||
|
||||
}
|
||||
@ -4,6 +4,7 @@ import {TableMetadata} from "../metadata/TableMetadata";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {IndexMetadata} from "../metadata/IndexMetadata";
|
||||
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
|
||||
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
|
||||
|
||||
/**
|
||||
* Arguments for EntityMetadata class.
|
||||
@ -16,5 +17,6 @@ export interface EntityMetadataArgs {
|
||||
readonly relationMetadatas?: RelationMetadata[];
|
||||
readonly indexMetadatas?: IndexMetadata[];
|
||||
readonly foreignKeyMetadatas?: ForeignKeyMetadata[];
|
||||
readonly embeddedMetadatas?: EmbeddedMetadata[];
|
||||
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import {NamingStrategyMetadataArgs} from "./NamingStrategyMetadataArgs";
|
||||
import {EventSubscriberMetadataArgs} from "./EventSubscriberMetadataArgs";
|
||||
import {JoinTableMetadataArgs} from "./JoinTableMetadataArgs";
|
||||
import {JoinColumnMetadataArgs} from "./JoinColumnMetadataArgs";
|
||||
import {EmbeddedMetadataArgs} from "./EmbeddedMetadataArgs";
|
||||
|
||||
/**
|
||||
* Storage all metadatas of all available types: tables, fields, subscribers, relations, etc.
|
||||
@ -36,6 +37,7 @@ export class MetadataArgsStorage {
|
||||
readonly joinTables = new PropertyMetadataArgsCollection<JoinTableMetadataArgs>();
|
||||
readonly entityListeners = new PropertyMetadataArgsCollection<EntityListenerMetadataArgs>();
|
||||
readonly relationCounts = new PropertyMetadataArgsCollection<RelationsCountMetadataArgs>();
|
||||
readonly embeddeds = new PropertyMetadataArgsCollection<EmbeddedMetadataArgs>();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
@ -46,20 +48,29 @@ export class MetadataArgsStorage {
|
||||
*/
|
||||
getMergedTableMetadatas(classes: Function[]) {
|
||||
const allTableMetadataArgs = this.tables.filterByClasses(classes);
|
||||
const tableMetadatas = this.tables.filterByClasses(classes).filter(table => table.type !== "abstract");
|
||||
const tableMetadatas = this.tables.filterByClasses(classes).filter(table => table.type === "regular" || table.type === "closure");
|
||||
|
||||
return tableMetadatas.map(tableMetadata => {
|
||||
return this.mergeWithAbstract(allTableMetadataArgs, tableMetadata);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets merged (with all abstract classes) embeddable table metadatas for the given classes.
|
||||
*/
|
||||
getMergedEmbeddableTableMetadatas(classes: Function[]) {
|
||||
const embeddableTableMetadatas = this.tables.filterByClasses(classes).filter(table => table.type === "embeddable");
|
||||
|
||||
return embeddableTableMetadatas.map(embeddableTableMetadata => {
|
||||
return this.mergeWithEmbeddable(embeddableTableMetadatas, embeddableTableMetadata);
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a new copy of the MetadataStorage with same metadatas as in current metadata storage, but filtered
|
||||
* by classes.
|
||||
*/
|
||||
private mergeWithAbstract(allTableMetadatas: TargetMetadataArgsCollection<TableMetadataArgs>,
|
||||
tableMetadata: TableMetadataArgs) {
|
||||
@ -71,6 +82,7 @@ export class MetadataArgsStorage {
|
||||
const joinTables = this.joinTables.filterByClass(tableMetadata.target);
|
||||
const entityListeners = this.entityListeners.filterByClass(tableMetadata.target);
|
||||
const relationCounts = this.relationCounts.filterByClass(tableMetadata.target);
|
||||
const embeddeds = this.embeddeds.filterByClass(tableMetadata.target);
|
||||
|
||||
allTableMetadatas
|
||||
.filter(metadata => {
|
||||
@ -103,6 +115,10 @@ export class MetadataArgsStorage {
|
||||
metadatasFromAbstract.relationCounts
|
||||
.filterRepeatedMetadatas(relationCounts)
|
||||
.forEach(metadata => relationCounts.push(metadata));
|
||||
|
||||
metadatasFromAbstract.embeddeds
|
||||
.filterRepeatedMetadatas(embeddeds)
|
||||
.forEach(metadata => embeddeds.push(metadata));
|
||||
});
|
||||
|
||||
return {
|
||||
@ -113,7 +129,33 @@ export class MetadataArgsStorage {
|
||||
joinColumns: joinColumns,
|
||||
joinTables: joinTables,
|
||||
entityListeners: entityListeners,
|
||||
relationCounts: relationCounts
|
||||
relationCounts: relationCounts,
|
||||
embeddeds: embeddeds
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
private mergeWithEmbeddable(allTableMetadatas: TargetMetadataArgsCollection<TableMetadataArgs>,
|
||||
tableMetadata: TableMetadataArgs) {
|
||||
const columns = this.columns.filterByClass(tableMetadata.target);
|
||||
|
||||
allTableMetadatas
|
||||
.filter(metadata => {
|
||||
if (!tableMetadata.target || !metadata.target) return false;
|
||||
return this.isInherited(tableMetadata.target, metadata.target);
|
||||
})
|
||||
.forEach(parentMetadata => {
|
||||
const metadatasFromParents = this.mergeWithEmbeddable(allTableMetadatas, parentMetadata);
|
||||
|
||||
metadatasFromParents.columns
|
||||
.filterRepeatedMetadatas(columns)
|
||||
.forEach(metadata => columns.push(metadata));
|
||||
});
|
||||
|
||||
return {
|
||||
table: tableMetadata,
|
||||
columns: columns
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -7,14 +7,9 @@ export class PropertyMetadataArgsCollection<T extends { target?: Function, prope
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
filterRepeatedMetadatas(existsMetadatas: T[]): this {
|
||||
const collection = new (<any> this.constructor)();
|
||||
this
|
||||
.filter(metadata => {
|
||||
return !existsMetadatas.find(fieldFromDocument => fieldFromDocument.propertyName === metadata.propertyName);
|
||||
})
|
||||
.forEach(metadata => collection.add(metadata));
|
||||
|
||||
return collection;
|
||||
return this.filter(metadata => {
|
||||
return !existsMetadatas.find(fieldFromDocument => fieldFromDocument.propertyName === metadata.propertyName);
|
||||
});
|
||||
}
|
||||
|
||||
findByProperty(propertyName: string) {
|
||||
|
||||
@ -6,6 +6,13 @@ export class TargetMetadataArgsCollection<T extends { target?: Function }> exten
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): this {
|
||||
const collection = new (<any> this.constructor)();
|
||||
super.filter(callbackfn)
|
||||
.forEach(metadata => collection.add(metadata));
|
||||
return collection;
|
||||
}
|
||||
|
||||
filterByClass(cls?: Function): this {
|
||||
|
||||
// if no class specified then simply return empty collection
|
||||
@ -16,14 +23,10 @@ export class TargetMetadataArgsCollection<T extends { target?: Function }> exten
|
||||
}
|
||||
|
||||
filterByClasses(classes: Function[]): this {
|
||||
const collection = new (<any> this.constructor)();
|
||||
this
|
||||
.filter(metadata => {
|
||||
if (!metadata.target) return false;
|
||||
return classes.indexOf(metadata.target) !== -1;
|
||||
})
|
||||
.forEach(metadata => collection.add(metadata));
|
||||
return collection;
|
||||
return this.filter(metadata => {
|
||||
if (!metadata.target) return false;
|
||||
return classes.indexOf(metadata.target) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
add(metadata: T, checkForDuplicateTargets = false) {
|
||||
|
||||
@ -12,6 +12,7 @@ import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {JoinTableMetadata} from "../metadata/JoinTableMetadata";
|
||||
import {JunctionEntityMetadataBuilder} from "./JunctionEntityMetadataBuilder";
|
||||
import {ClosureJunctionEntityMetadataBuilder} from "./ClosureJunctionEntityMetadataBuilder";
|
||||
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
|
||||
|
||||
/**
|
||||
* Aggregates all metadata: table, column, relation into one collection grouped by tables for a given set of classes.
|
||||
@ -36,8 +37,20 @@ export class EntityMetadataBuilder {
|
||||
*/
|
||||
build(namingStrategy: NamingStrategyInterface, entityClasses: Function[]): EntityMetadata[] {
|
||||
|
||||
const embeddableMergedArgs = getMetadataArgsStorage().getMergedEmbeddableTableMetadatas(entityClasses);
|
||||
const entityMetadatas = getMetadataArgsStorage().getMergedTableMetadatas(entityClasses).map(mergedArgs => {
|
||||
|
||||
|
||||
// find embeddable tables for embeddeds registered in this table and create EmbeddedMetadatas from them
|
||||
const embeddeds: EmbeddedMetadata[] = [];
|
||||
mergedArgs.embeddeds.forEach(embedded => {
|
||||
const embeddableTable = embeddableMergedArgs.find(mergedArs => mergedArgs.table.target === embedded.type);
|
||||
if (embeddableTable) {
|
||||
const table = new TableMetadata(mergedArgs.table);
|
||||
const columns = mergedArgs.columns.map(args => new ColumnMetadata(args));
|
||||
embeddeds.push(new EmbeddedMetadata(table, columns));
|
||||
}
|
||||
});
|
||||
|
||||
// create metadatas from args
|
||||
const table = new TableMetadata(mergedArgs.table);
|
||||
const columns = mergedArgs.columns.map(args => new ColumnMetadata(args));
|
||||
@ -50,7 +63,8 @@ export class EntityMetadataBuilder {
|
||||
tableMetadata: table,
|
||||
columnMetadatas: columns,
|
||||
relationMetadatas: relations,
|
||||
indexMetadatas: indices
|
||||
indexMetadatas: indices,
|
||||
embeddedMetadatas: embeddeds
|
||||
});
|
||||
|
||||
// create entity's relations join tables
|
||||
|
||||
42
src/metadata/EmbeddedMetadata.ts
Normal file
42
src/metadata/EmbeddedMetadata.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {EntityMetadata} from "./EntityMetadata";
|
||||
import {TableMetadata} from "./TableMetadata";
|
||||
import {ColumnMetadata} from "./ColumnMetadata";
|
||||
|
||||
/**
|
||||
* Contains all information about entity's embedded property.
|
||||
*/
|
||||
export class EmbeddedMetadata {
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public Properties
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Its own entity metadata.
|
||||
*/
|
||||
entityMetadata: EntityMetadata;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Private Properties
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Embeddable table.
|
||||
*/
|
||||
readonly table: TableMetadata;
|
||||
|
||||
/**
|
||||
* Embeddable table's columns.
|
||||
*/
|
||||
readonly columns: ColumnMetadata[];
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
constructor(table: TableMetadata, columns: ColumnMetadata[]) {
|
||||
this.table = table;
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,6 +6,7 @@ import {RelationTypes} from "./types/RelationTypes";
|
||||
import {ForeignKeyMetadata} from "./ForeignKeyMetadata";
|
||||
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
|
||||
import {EntityMetadataArgs} from "../metadata-args/EntityMetadataArgs";
|
||||
import {EmbeddedMetadata} from "./EmbeddedMetadata";
|
||||
|
||||
/**
|
||||
* Contains all entity metadata.
|
||||
@ -55,6 +56,11 @@ export class EntityMetadata {
|
||||
*/
|
||||
readonly foreignKeys: ForeignKeyMetadata[] = [];
|
||||
|
||||
/**
|
||||
* Entity's embedded metadatas.
|
||||
*/
|
||||
readonly embeddeds: EmbeddedMetadata[];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
@ -66,12 +72,14 @@ export class EntityMetadata {
|
||||
this.relations = args.relationMetadatas || [];
|
||||
this.indices = args.indexMetadatas || [];
|
||||
this.foreignKeys = args.foreignKeyMetadatas || [];
|
||||
this.embeddeds = args.embeddedMetadatas || [];
|
||||
|
||||
this.table.entityMetadata = this;
|
||||
this.columns.forEach(column => column.entityMetadata = this);
|
||||
this.relations.forEach(relation => relation.entityMetadata = this);
|
||||
this.foreignKeys.forEach(foreignKey => foreignKey.entityMetadata = this);
|
||||
this.indices.forEach(index => index.entityMetadata = this);
|
||||
this.embeddeds.forEach(embedded => embedded.entityMetadata = this);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -5,7 +5,7 @@ import {TableMetadataArgs} from "../metadata-args/TableMetadataArgs";
|
||||
/**
|
||||
* Table type.
|
||||
*/
|
||||
export type TableType = "regular"|"abstract"|"junction"|"closure"|"closureJunction";
|
||||
export type TableType = "regular"|"abstract"|"junction"|"closure"|"closureJunction"|"embeddable";
|
||||
|
||||
/**
|
||||
* This metadata interface contains all information about specific table.
|
||||
@ -98,5 +98,12 @@ export class TableMetadata extends TargetMetadata {
|
||||
get isClosure() {
|
||||
return this.tableType === "closure";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this table is an embeddable table.
|
||||
*/
|
||||
get isEmbeddable() {
|
||||
return this.tableType === "embeddable";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user