mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
implemented joinAndMap functionality + fixed bug with inserting many-many from inverse
This commit is contained in:
parent
1b26c2f498
commit
ae7b362560
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "typeorm",
|
||||
"private": true,
|
||||
"version": "0.0.2-alpha.16",
|
||||
"version": "0.0.2-alpha.17",
|
||||
"description": "Data-mapper ORM for Typescript",
|
||||
"license": "Apache-2.0",
|
||||
"readmeFilename": "README.md",
|
||||
|
||||
@ -42,30 +42,28 @@ createConnection(options).then(connection => {
|
||||
post.title = "hello";
|
||||
post.authorId = 1;
|
||||
// post.author = author;
|
||||
// post.categories = [category1, category2];
|
||||
post.categories = [category1, category2];
|
||||
|
||||
Promise.all<any>([
|
||||
postRepository.persist(post),
|
||||
authorRepository.persist(author),
|
||||
categoryRepository.persist(category1),
|
||||
categoryRepository.persist(category2),
|
||||
])
|
||||
.then(() => {
|
||||
return postRepository.persist(post);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Everything has been saved.");
|
||||
})
|
||||
.then(() => {
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoin(Author, "author", "ON", "author.id=post.authorId")
|
||||
.leftJoinAndMapMany("post.superCategories", "post.categories", "categories")
|
||||
.leftJoinAndMapOne("post.author", Author, "author", "ON", "author.id=post.authorId")
|
||||
.getResults();
|
||||
|
||||
// let secondPost = postRepository.create();
|
||||
// secondPost.text = "Second post";
|
||||
// secondPost.title = "About second post";
|
||||
// return authorRepository.persist(author);
|
||||
|
||||
}).then(post => {
|
||||
console.log("Loaded posts: ", post);
|
||||
}).then(posts => {
|
||||
console.log("Loaded posts: ", posts);
|
||||
|
||||
return entityManager
|
||||
.createQueryBuilder(Author, "author")
|
||||
|
||||
@ -2,6 +2,8 @@ import {PrimaryColumn, Column} from "../../../src/columns";
|
||||
import {Table} from "../../../src/tables";
|
||||
import {Author} from "./Author";
|
||||
import {Category} from "./Category";
|
||||
import {ManyToMany} from "../../../src/decorator/relations/ManyToMany";
|
||||
import {JoinTable} from "../../../src/decorator/relations/JoinTable";
|
||||
|
||||
@Table("sample20_post")
|
||||
export class Post {
|
||||
@ -18,4 +20,12 @@ export class Post {
|
||||
@Column("int")
|
||||
authorId: number;
|
||||
|
||||
@ManyToMany(type => Category)
|
||||
@JoinTable()
|
||||
categories: Category[];
|
||||
|
||||
superCategories: Category[];
|
||||
|
||||
author: Author;
|
||||
|
||||
}
|
||||
@ -447,7 +447,7 @@ export class PersistOperationExecutor {
|
||||
|
||||
let values: any[];
|
||||
// order may differ, find solution (column.table to compare with entity metadata table?)
|
||||
if (metadata1.table === junctionMetadata.foreignKeys[0].table) {
|
||||
if (metadata1.table === junctionMetadata.foreignKeys[0].referencedTable) {
|
||||
values = [id1, id2];
|
||||
} else {
|
||||
values = [id2, id1];
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import {Alias} from "./alias/Alias";
|
||||
import {AliasMap} from "./alias/AliasMap";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {RawSqlResultsToEntityTransformer} from "./transformer/RawSqlResultsToEntityTransformer";
|
||||
import {Broadcaster} from "../subscriber/Broadcaster";
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
|
||||
import {Driver} from "../driver/Driver";
|
||||
|
||||
@ -16,6 +14,15 @@ export interface Join {
|
||||
conditionType: "ON"|"WITH";
|
||||
condition: string;
|
||||
tableName: string;
|
||||
mapToProperty: string|undefined;
|
||||
isMappingMany: boolean;
|
||||
}
|
||||
|
||||
export interface JoinMapping {
|
||||
alias: Alias;
|
||||
parentName: string;
|
||||
propertyName: string;
|
||||
isMany: boolean;
|
||||
}
|
||||
|
||||
export class QueryBuilder<Entity> {
|
||||
@ -145,6 +152,20 @@ export class QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
innerJoinAndMapMany(mapToProperty: string, property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
innerJoinAndMapMany(mapToProperty: string, entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
innerJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }): this {
|
||||
this.addSelect(alias);
|
||||
return this.join("INNER", entityOrProperty, alias, conditionType, condition, parameters, mapToProperty, true);
|
||||
}
|
||||
|
||||
innerJoinAndMapOne(mapToProperty: string, property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
innerJoinAndMapOne(mapToProperty: string, entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
innerJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }): this {
|
||||
this.addSelect(alias);
|
||||
return this.join("INNER", entityOrProperty, alias, conditionType, condition, parameters, mapToProperty, false);
|
||||
}
|
||||
|
||||
innerJoinAndSelect(property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
innerJoinAndSelect(entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
innerJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }): this {
|
||||
@ -158,6 +179,20 @@ export class QueryBuilder<Entity> {
|
||||
return this.join("INNER", entityOrProperty, alias, conditionType, condition, parameters);
|
||||
}
|
||||
|
||||
leftJoinAndMapMany(mapToProperty: string, property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
leftJoinAndMapMany(mapToProperty: string, entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
leftJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }): this {
|
||||
this.addSelect(alias);
|
||||
return this.join("LEFT", entityOrProperty, alias, conditionType, condition, parameters, mapToProperty, true);
|
||||
}
|
||||
|
||||
leftJoinAndMapOne(mapToProperty: string, property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
leftJoinAndMapOne(mapToProperty: string, entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
leftJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }): this {
|
||||
this.addSelect(alias);
|
||||
return this.join("LEFT", entityOrProperty, alias, conditionType, condition, parameters, mapToProperty, false);
|
||||
}
|
||||
|
||||
leftJoinAndSelect(property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
leftJoinAndSelect(entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
leftJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }): this {
|
||||
@ -171,30 +206,6 @@ export class QueryBuilder<Entity> {
|
||||
return this.join("LEFT", entityOrProperty, alias, conditionType, condition, parameters);
|
||||
}
|
||||
|
||||
join(joinType: "INNER"|"LEFT", property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
join(joinType: "INNER"|"LEFT", entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }): this;
|
||||
join(joinType: "INNER"|"LEFT", entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH", condition: string, parameters?: { [key: string]: any }): this;
|
||||
join(joinType: "INNER"|"LEFT", entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }): this {
|
||||
|
||||
let tableName = "";
|
||||
const aliasObj = new Alias(alias);
|
||||
this.aliasMap.addAlias(aliasObj);
|
||||
if (entityOrProperty instanceof Function) {
|
||||
aliasObj.target = entityOrProperty;
|
||||
|
||||
} else if (typeof entityOrProperty === "string" && entityOrProperty.indexOf(".") !== -1) {
|
||||
aliasObj.parentAliasName = entityOrProperty.split(".")[0];
|
||||
aliasObj.parentPropertyName = entityOrProperty.split(".")[1];
|
||||
} else if (typeof entityOrProperty === "string") {
|
||||
tableName = entityOrProperty;
|
||||
}
|
||||
|
||||
const join: Join = { type: joinType, alias: aliasObj, tableName: tableName, conditionType: conditionType, condition: condition };
|
||||
this.joins.push(join);
|
||||
if (parameters) this.addParameters(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
where(where: string, parameters?: { [key: string]: any }): this {
|
||||
this.wheres.push({ type: "simple", condition: where });
|
||||
if (parameters) this.addParameters(parameters);
|
||||
@ -428,7 +439,7 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
this.joins.forEach(join => {
|
||||
const property = join.tableName || join.alias.target || (join.alias.parentAliasName + "." + join.alias.parentPropertyName);
|
||||
qb.join(join.type, property, join.alias.name, join.conditionType, join.condition);
|
||||
qb.join(join.type, property, join.alias.name, join.conditionType, join.condition, undefined, join.mapToProperty, join.isMappingMany);
|
||||
});
|
||||
|
||||
this.groupBys.forEach(groupBy => qb.addGroupBy(groupBy));
|
||||
@ -474,13 +485,12 @@ export class QueryBuilder<Entity> {
|
||||
return qb;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected rawResultsToEntities(results: any[]) {
|
||||
const transformer = new RawSqlResultsToEntityTransformer(this.driver, this.aliasMap);
|
||||
const transformer = new RawSqlResultsToEntityTransformer(this.driver, this.aliasMap, this.extractJoinMappings());
|
||||
return transformer.transform(results);
|
||||
}
|
||||
|
||||
@ -691,4 +701,45 @@ export class QueryBuilder<Entity> {
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected extractJoinMappings(): JoinMapping[] {
|
||||
return this.joins
|
||||
.filter(join => !!join.mapToProperty)
|
||||
.map(join => {
|
||||
const [parentName, propertyName] = (join.mapToProperty as string).split(".");
|
||||
return {
|
||||
alias: join.alias,
|
||||
parentName: parentName,
|
||||
propertyName: propertyName,
|
||||
isMany: join.isMappingMany
|
||||
} as JoinMapping;
|
||||
});
|
||||
}
|
||||
|
||||
protected join(joinType: "INNER"|"LEFT", property: string, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }, mapToProperty?: string, isMappingMany?: boolean): this;
|
||||
protected join(joinType: "INNER"|"LEFT", entity: Function, alias: string, conditionType?: "ON"|"WITH", condition?: string, parameters?: { [key: string]: any }, mapToProperty?: string, isMappingMany?: boolean): this;
|
||||
protected join(joinType: "INNER"|"LEFT", entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH", condition: string, parameters?: { [key: string]: any }, mapToProperty?: string, isMappingMany?: boolean): this;
|
||||
protected join(joinType: "INNER"|"LEFT", entityOrProperty: Function|string, alias: string, conditionType: "ON"|"WITH" = "ON", condition: string = "", parameters?: { [key: string]: any }, mapToProperty?: string, isMappingMany: boolean = false): this {
|
||||
|
||||
if (!mapToProperty && typeof entityOrProperty === "string")
|
||||
mapToProperty = entityOrProperty;
|
||||
|
||||
let tableName = "";
|
||||
const aliasObj = new Alias(alias);
|
||||
this.aliasMap.addAlias(aliasObj);
|
||||
if (entityOrProperty instanceof Function) {
|
||||
aliasObj.target = entityOrProperty;
|
||||
|
||||
} else if (typeof entityOrProperty === "string" && entityOrProperty.indexOf(".") !== -1) {
|
||||
aliasObj.parentAliasName = entityOrProperty.split(".")[0];
|
||||
aliasObj.parentPropertyName = entityOrProperty.split(".")[1];
|
||||
} else if (typeof entityOrProperty === "string") {
|
||||
tableName = entityOrProperty;
|
||||
}
|
||||
|
||||
const join: Join = { type: joinType, alias: aliasObj, tableName: tableName, conditionType: conditionType, condition: condition, mapToProperty: mapToProperty, isMappingMany: isMappingMany };
|
||||
this.joins.push(join);
|
||||
if (parameters) this.addParameters(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@ export class Alias {
|
||||
return result[this.name + "_" + primaryColumn.name];
|
||||
}
|
||||
|
||||
getColumnValue(result: any, column: ColumnMetadata) {
|
||||
return result[this.name + "_" + column.name];
|
||||
getColumnValue(result: any, columnName: string) {
|
||||
return result[this.name + "_" + columnName];
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,6 +3,7 @@ import {Alias} from "../alias/Alias";
|
||||
import {EntityMetadata} from "../../metadata/EntityMetadata";
|
||||
import {OrmUtils} from "../../util/OrmUtils";
|
||||
import {Driver} from "../../driver/Driver";
|
||||
import {JoinMapping} from "../QueryBuilder";
|
||||
|
||||
/**
|
||||
* Transforms raw sql results returned from the database into entity object.
|
||||
@ -17,7 +18,8 @@ export class RawSqlResultsToEntityTransformer {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private driver: Driver,
|
||||
private aliasMap: AliasMap) {
|
||||
private aliasMap: AliasMap,
|
||||
private joinMappings: JoinMapping[]) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -39,17 +41,10 @@ export class RawSqlResultsToEntityTransformer {
|
||||
private groupAndTransform(rawSqlResults: any[], alias: Alias) {
|
||||
|
||||
const metadata = this.aliasMap.getEntityMetadataByAlias(alias);
|
||||
if (!metadata.hasPrimaryKey)
|
||||
throw new Error("Metadata does not have primary key. You must have it to make convertation to object possible.");
|
||||
|
||||
const groupedResults = OrmUtils.groupBy(rawSqlResults, result => alias.getPrimaryKeyValue(result, metadata.primaryColumn));
|
||||
return groupedResults
|
||||
.map(group => this.transformIntoSingleResult(group.items, alias, metadata))
|
||||
.filter(res => !!res)
|
||||
.map(res => {
|
||||
// console.log("res: ", res);
|
||||
return res;
|
||||
});
|
||||
.filter(res => !!res);
|
||||
}
|
||||
|
||||
|
||||
@ -59,10 +54,23 @@ export class RawSqlResultsToEntityTransformer {
|
||||
private transformIntoSingleResult(rawSqlResults: any[], alias: Alias, metadata: EntityMetadata) {
|
||||
const entity: any = metadata.create();
|
||||
let hasData = false;
|
||||
|
||||
this.joinMappings
|
||||
.filter(joinMapping => joinMapping.parentName === alias.name && !joinMapping.alias.parentAliasName && joinMapping.alias.target)
|
||||
.map(joinMapping => {
|
||||
const relatedEntities = this.groupAndTransform(rawSqlResults, joinMapping.alias);
|
||||
const isResultArray = joinMapping.isMany;
|
||||
const result = !isResultArray ? relatedEntities[0] : relatedEntities;
|
||||
|
||||
if (result && (!isResultArray || result.length > 0)) {
|
||||
entity[joinMapping.propertyName] = result;
|
||||
hasData = true;
|
||||
}
|
||||
});
|
||||
|
||||
// get value from columns selections and put them into object
|
||||
metadata.columns.forEach(column => {
|
||||
const valueInObject = alias.getColumnValue(rawSqlResults[0], column); // we use zero index since its grouped data
|
||||
const valueInObject = alias.getColumnValue(rawSqlResults[0], column.name); // we use zero index since its grouped data
|
||||
if (valueInObject && column.propertyName && !column.isVirtual) {
|
||||
entity[column.propertyName] = this.driver.prepareHydratedValue(valueInObject, column);
|
||||
hasData = true;
|
||||
@ -73,14 +81,21 @@ export class RawSqlResultsToEntityTransformer {
|
||||
metadata.relations.forEach(relation => {
|
||||
const relationAlias = this.aliasMap.findAliasByParent(alias.name, relation.name);
|
||||
if (relationAlias) {
|
||||
const joinMapping = this.joinMappings.find(joinMapping => joinMapping.alias === relationAlias);
|
||||
const relatedEntities = this.groupAndTransform(rawSqlResults, relationAlias);
|
||||
const isResultArray = relation.isManyToMany || relation.isOneToMany;
|
||||
const result = !isResultArray ? relatedEntities[0] : relatedEntities;
|
||||
|
||||
if (result && (!isResultArray || result.length > 0)) {
|
||||
let propertyName = relation.propertyName;
|
||||
if (joinMapping) {
|
||||
propertyName = joinMapping.propertyName;
|
||||
}
|
||||
|
||||
if (relation.isLazy) {
|
||||
entity["__" + relation.propertyName + "__"] = result;
|
||||
entity["__" + propertyName + "__"] = result;
|
||||
} else {
|
||||
entity[relation.propertyName] = result;
|
||||
entity[propertyName] = result;
|
||||
}
|
||||
hasData = true;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user