implemented joinAndMap functionality + fixed bug with inserting many-many from inverse

This commit is contained in:
Umed Khudoiberdiev 2016-05-12 17:54:35 +05:00
parent 1b26c2f498
commit ae7b362560
7 changed files with 129 additions and 55 deletions

View File

@ -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",

View File

@ -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")

View File

@ -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;
}

View File

@ -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];

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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;
}