experementing with always left join option

This commit is contained in:
Umed Khudoiberdiev 2016-02-21 17:37:03 +05:00
parent 4309b8d810
commit 7d34424eec
11 changed files with 196 additions and 79 deletions

View File

@ -2,6 +2,7 @@ import {TypeORM} from "../../src/TypeORM";
import {Post} from "./entity/Post";
import {PostDetails} from "./entity/PostDetails";
import {Image} from "./entity/Image";
import {ImageDetails} from "./entity/ImageDetails";
// first create a connection
let options = {
@ -13,8 +14,19 @@ let options = {
autoSchemaCreate: true
};
TypeORM.createMysqlConnection(options, [Post, PostDetails, Image]).then(connection => {
TypeORM.createMysqlConnection(options, [Post, PostDetails, Image, ImageDetails]).then(connection => {
const postJson = {
id: 1,
text: "This is post about hello",
title: "hello",
details: {
id: 1,
comment: "This is post about hello",
meta: "about-hello"
}
};
let postRepository = connection.getRepository<Post>(Post);
return postRepository.findById(1).then(post => {
console.log(post);

View File

@ -1,6 +1,6 @@
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
import {Table} from "../../../src/decorator/Tables";
import {ManyToOne, OneToMany} from "../../../src/decorator/Relations";
import {ManyToOne, OneToMany, OneToOne} from "../../../src/decorator/Relations";
import {Post} from "./Post";
@Table("sample2_image")
@ -11,10 +11,22 @@ export class Image {
@Column()
name: string;
@ManyToOne<Post>(() => Post, post => post.images, {
isAlwaysLeftJoin: true
//isAlwaysLeftJoin: true
})
post: Post;
@ManyToOne<Post>(() => Post, post => post.secondaryImages, {
// isAlwaysLeftJoin: true
})
secondaryPost: Post;
/*
@OneToOne<ImageDetails>(true, () => ImageDetails, details => details.image, {
isAlwaysInnerJoin: true
})
details: ImageDetails;*/
}

View File

@ -0,0 +1,21 @@
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
import {Table} from "../../../src/decorator/Tables";
import {OneToOne} from "../../../src/decorator/Relations";
import {Image} from "./Image";
@Table("sample2_image_details")
export class ImageDetails {
@PrimaryColumn("int", { isAutoIncrement: true })
id: number;
@Column()
meta: string;
@Column()
comment: string;
@OneToOne<Image>(false, () => Image, image => image.details)
image: Image;
}

View File

@ -20,14 +20,19 @@ export class Post {
})
text: string;
@OneToOne<PostDetails>(true, () => PostDetails, details => details.post, {
isAlwaysInnerJoin: true
/* @OneToOne<PostDetails>(true, () => PostDetails, details => details.post, {
//isAlwaysInnerJoin: true
})
details: PostDetails;
details: PostDetails;*/
@OneToMany<Image>(() => Image, image => image.post, {
isAlwaysLeftJoin: true
})
images: Image[];
@OneToMany<Image>(() => Image, image => image.secondaryPost, {
isAlwaysLeftJoin: true
})
secondaryImages: Image[];
}

View File

@ -1,6 +1,7 @@
import {ConnectionOptions} from "../connection/ConnectionOptions";
import {SchemaBuilder} from "./schema-builder/SchemaBuilder";
import {QueryBuilder} from "./query-builder/QueryBuilder";
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
/**
* Driver communicates with specific database.
@ -20,7 +21,7 @@ export interface Driver {
/**
* Creates a query builder which can be used to build an sql queries.
*/
createQueryBuilder(): QueryBuilder;
createQueryBuilder(entityMetadatas: EntityMetadata[]): QueryBuilder;
/**
* Creates a schema builder which can be used to build database/table schemas.

View File

@ -3,6 +3,7 @@ import {ConnectionOptions} from "../connection/ConnectionOptions";
import {SchemaBuilder} from "./schema-builder/SchemaBuilder";
import {QueryBuilder} from "./query-builder/QueryBuilder";
import {MysqlSchemaBuilder} from "./schema-builder/MysqlSchemaBuilder";
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
/**
* This driver organizes work with mongodb database.
@ -44,8 +45,8 @@ export class MysqlDriver implements Driver {
/**
* Creates a query builder which can be used to build an sql queries.
*/
createQueryBuilder(): QueryBuilder {
return new QueryBuilder();
createQueryBuilder(entityMetadatas: EntityMetadata[]): QueryBuilder {
return new QueryBuilder(entityMetadatas);
}
/**

View File

@ -1,13 +1,16 @@
import {EntityMetadata} from "../../metadata-builder/metadata/EntityMetadata";
import {ColumnMetadata} from "../../metadata-builder/metadata/ColumnMetadata";
/**
* @author Umed Khudoiberdiev <info@zar.tj>
*/
export class QueryBuilder {
// -------------------------------------------------------------------------
// Public properties
// Constructor
// -------------------------------------------------------------------------
getTableNameFromEntityCallback: (entity: Function) => string;
constructor(private entityMetadatas: EntityMetadata[]) {
}
// -------------------------------------------------------------------------
// Pirvate properties
@ -15,9 +18,9 @@ export class QueryBuilder {
private type: "select"|"update"|"delete";
private selects: string[] = [];
private froms: { entityOrTableName: Function|string, alias: string };
private leftJoins: { join: string, alias: string, conditionType: string, condition: string }[] = [];
private innerJoins: { join: string, alias: string, conditionType: string, condition: string }[] = [];
private froms: { entity: Function, alias: string };
private leftJoins: { join: Function, alias: string, conditionType: string, condition: string }[] = [];
private innerJoins: { join: Function, alias: string, conditionType: string, condition: string }[] = [];
private groupBys: string[] = [];
private wheres: { type: "simple"|"and"|"or", condition: string }[] = [];
private havings: { type: "simple"|"and"|"or", condition: string }[] = [];
@ -48,11 +51,12 @@ export class QueryBuilder {
return this;
}
select(selection: string): this;
select(selection: string[]): this;
select(selection: string|string[]): this {
select(selection?: string): this;
select(selection?: string[]): this;
select(selection?: string|string[]): this {
this.type = "select";
this.addSelection(selection);
if (selection)
this.addSelection(selection);
return this;
}
@ -67,20 +71,20 @@ export class QueryBuilder {
return this;
}
from(tableName: string, alias: string): this;
from(entity: Function, alias: string): this;
from(entityOrTableName: Function|string, alias: string): this {
this.froms = { entityOrTableName: entityOrTableName, alias: alias };
//from(tableName: string, alias: string): this;
from(entity: Function, alias?: string): this {
//from(entityOrTableName: Function|string, alias: string): this {
this.froms = { entity: entity, alias: alias };
return this;
}
innerJoin(join: string, alias: string, conditionType: string, condition: string): this {
this.innerJoins.push({ join: join, alias: alias, conditionType: conditionType, condition: condition });
innerJoin(target: Function, alias: string, conditionType: string, condition: string): this {
this.innerJoins.push({ join: target, alias: alias, conditionType: conditionType, condition: condition });
return this;
}
leftJoin(join: string, alias: string, conditionType: string, condition: string): this {
this.leftJoins.push({ join: join, alias: alias, conditionType: conditionType, condition: condition });
leftJoin(target: Function, alias: string, conditionType: string, condition: string): this {
this.leftJoins.push({ join: target, alias: alias, conditionType: conditionType, condition: condition });
return this;
}
@ -156,9 +160,9 @@ export class QueryBuilder {
getSql(): string {
let sql = this.createSelectExpression();
sql += this.createWhereExpression();
sql += this.createLeftJoinExpression();
sql += this.createInnerJoinExpression();
sql += this.createWhereExpression();
sql += this.createGroupByExpression();
sql += this.createHavingExpression();
sql += this.createOrderByExpression();
@ -189,25 +193,45 @@ export class QueryBuilder {
}*/
}
protected getTableName() {
if (this.froms.entityOrTableName instanceof Function) {
return this.getTableNameFromEntityCallback(this.froms.entityOrTableName);
} else {
return <string> this.froms.entityOrTableName;
}
protected findMetadata(target: Function) {
const metadata = this.entityMetadatas.find(metadata => metadata.target === target);
if (!metadata)
throw new Error("Metadata for " + this.froms.entity + " was not found.");
return metadata;
}
protected createSelectExpression() {
// todo throw exception if selects or from missing
const tableName = this.getTableName();
// todo throw exception if selects or from is missing
const metadata = this.findMetadata(this.froms.entity);
const tableName = metadata.table.name;
const alias = this.froms.alias ? this.froms.alias : metadata.table.name;
const columns: string[] = [];
// add select from the main table
if (this.selects.indexOf(this.froms.alias) !== -1)
metadata.columns.forEach(column => {
columns.push(this.froms.alias + "." + column.name + " AS " + this.froms.alias + "_" + column.name);
});
// add selects from left and inner joins
this.leftJoins.concat(this.innerJoins)
.filter(join => this.selects.indexOf(join.alias) !== -1)
.forEach(join => {
const joinMetadata = this.findMetadata(join.join);
joinMetadata.columns.forEach(column => {
columns.push(join.alias + "." + column.name + " AS " + join.alias + "_" + column.name);
});
});
switch (this.type) {
case "select":
return "SELECT " + this.selects.join(", ") + " FROM " + tableName + " " + this.froms.alias;// + " ";
return "SELECT " + columns.join(", ") + " FROM " + tableName + " " + alias;
case "update":
return "UPDATE " + tableName + " " + this.froms.alias;// + " ";
return "UPDATE " + tableName + " " + alias;
case "delete":
return "DELETE " + tableName + " " + this.froms.alias;// + " ";
return "DELETE " + tableName + " " + alias;
}
return "";
}
@ -231,7 +255,9 @@ export class QueryBuilder {
if (!this.innerJoins || !this.innerJoins.length) return "";
return this.innerJoins.map(join => {
return " INNER JOIN " + join.join + " " + join.alias + " " + join.conditionType + " " + join.condition;
const joinMetadata = this.entityMetadatas.find(metadata => metadata.target === join.join); // todo: throw exception if not found
const relationTable = joinMetadata.table.name;
return " INNER JOIN " + relationTable + " " + join.alias + " " + join.conditionType + " " + join.condition;
}).join(" ");
}
@ -239,7 +265,9 @@ export class QueryBuilder {
if (!this.leftJoins || !this.leftJoins.length) return "";
return this.leftJoins.map(join => {
return " LEFT JOIN " + join.join + " " + join.alias + " " + join.conditionType + " " + join.condition;
const joinMetadata = this.findMetadata(join.join);
const relationTable = joinMetadata.table.name;
return " LEFT JOIN " + relationTable + " " + join.alias + " " + join.conditionType + " " + join.condition;
}).join(" ");
}
@ -283,5 +311,9 @@ export class QueryBuilder {
});
return sql;
}
protected replaceTableNames(sql: string) {
return sql.replace("\$\$", "");
}
}

View File

@ -91,9 +91,6 @@ export class EntityMetadataBuilder {
entityColumns.forEach(column => column.namingStrategy = this.namingStrategy);
entityRelations.forEach(relation => relation.namingStrategy = this.namingStrategy);
// set properties map for relations
entityRelations.forEach(relation => relation.ownerEntityPropertiesMap = entityMetadata.createPropertiesMap());
return entityMetadata;
});
@ -153,7 +150,7 @@ export class EntityMetadataBuilder {
});
const allEntityMetadatas = entityMetadatas.concat(junctionEntityMetadatas);
// set inverse side (related) entity metadatas for all relation metadatas
allEntityMetadatas.forEach(entityMetadata => {
entityMetadata.relations.forEach(relation => {

View File

@ -25,7 +25,7 @@ export class RelationMetadata extends PropertyMetadata {
// ---------------------------------------------------------------------
namingStrategy: NamingStrategy;
ownerEntityPropertiesMap: Object;
// ownerEntityPropertiesMap: Object;
// ---------------------------------------------------------------------
// Private Properties
@ -215,8 +215,9 @@ export class RelationMetadata extends PropertyMetadata {
// ---------------------------------------------------------------------
private computeInverseSide(inverseSide: PropertyTypeInFunction<any>): string {
const ownerEntityPropertiesMap = this.relatedEntityMetadata.createPropertiesMap();
if (typeof inverseSide === "function")
return (<Function> inverseSide)(this.ownerEntityPropertiesMap);
return (<Function> inverseSide)(ownerEntityPropertiesMap);
if (typeof inverseSide === "string")
return <string> inverseSide;

View File

@ -88,10 +88,9 @@ export class Repository<Entity> {
* Creates a new query builder that can be used to build an sql query.
*/
createQueryBuilder(alias?: string): QueryBuilder {
const queryBuilder = this.connection.driver.createQueryBuilder();
queryBuilder.getTableNameFromEntityCallback = entity => this.getTableNameFromEntityCallback(entity);
const queryBuilder = this.connection.driver.createQueryBuilder(this.connection.metadatas);
if (alias)
queryBuilder.select("*").from(this.metadata.target, alias);
queryBuilder.select(alias).from(this.metadata.target, alias);
return queryBuilder;
}
@ -274,10 +273,4 @@ export class Repository<Entity> {
return hydrator.hydrate(this.metadata, dbObject, joinFields);
}*/
private getTableNameFromEntityCallback(entity: Function) {
const metadata = this.connection.getMetadata(entity);
// todo throw exception if metadata is missing
return metadata.table.name;
}
}

View File

@ -1,5 +1,6 @@
import {Connection} from "../../connection/Connection";
import {EntityMetadata} from "../../metadata-builder/metadata/EntityMetadata";
import {RelationMetadata} from "../../metadata-builder/metadata/RelationMetadata";
export class EntityCreator {
@ -32,35 +33,76 @@ export class EntityCreator {
// -------------------------------------------------------------------------
getLoadMap(metadata: EntityMetadata) {
// let tableUsageIndices = 0;
const columns = metadata.columns.map(column => {
return metadata.table.name + "." + column.name + " as " + metadata.table.name + "_" + column.name;
});
const qb = this.connection.driver
.createQueryBuilder()
.select(columns)
.from(metadata.table.name, metadata.table.name);
metadata.relations.filter(relation => relation.isAlwaysLeftJoin || relation.isAlwaysInnerJoin).map(relation => {
const table = metadata.table.name;
const column = metadata.primaryColumn.name;
const relationTable = relation.relatedEntityMetadata.table.name;
const relationColumn = relation.relatedEntityMetadata.primaryColumn.name;
const condition = table + "." + column + "=" + relationTable + "." + relationColumn;
const selectedColumns = relation.relatedEntityMetadata.columns.map(column => {
return relationTable + "." + column.name + " as " + relationTable + "_" + column.name;
});
if (relation.isAlwaysLeftJoin) {
return qb.addSelect(selectedColumns).leftJoin(relationTable, relationTable, "ON", condition);
} else { // else can be only always inner join
return qb.addSelect(selectedColumns).innerJoin(relationTable, relationTable, "ON", condition);
const postId = 1;
const postJson = {
id: 1,
text: "This is post about hello",
title: "hello",
details: {
id: 1,
comment: "This is post about hello",
meta: "about-hello"
}
};
// let tableUsageIndices = 0;
const mainTableAlias = metadata.table.name + "_1";
const visitedMetadatas: EntityMetadata[] = [];
const qb = this.connection.driver
.createQueryBuilder(this.connection.metadatas)
.select(mainTableAlias)
.from(metadata.target, mainTableAlias);
const aliasesCounter: { [type: string]: number } = { [mainTableAlias]: 0 };
const joinRelations = (parentTableAlias: string, entityMetadata: EntityMetadata) => {
if (visitedMetadatas.find(metadata => metadata === entityMetadata))
return;
visitedMetadatas.push(metadata);
entityMetadata.relations.filter(relation => relation.isAlwaysLeftJoin || relation.isAlwaysInnerJoin).forEach(relation => {
let relationAlias = relation.relatedEntityMetadata.table.name;
if (!aliasesCounter[relationAlias]) aliasesCounter[relationAlias] = 0;
aliasesCounter[relationAlias] += 1;
relationAlias += "_" + aliasesCounter[relationAlias];
let condition = "";
const relationColumn = relation.relatedEntityMetadata.primaryColumn.name;
if (relation.isOwning) {
condition = relationAlias + "." + relationColumn + "=" + parentTableAlias + "." + relation.inverseSideProperty;
} else {
condition = relationAlias + "." + relation.inverseSideProperty + "=" + parentTableAlias + "." + relationColumn;
}
if (relation.isAlwaysLeftJoin) {
qb.addSelect(relationAlias).leftJoin(relation.type, relationAlias, "ON", condition);
} else { // else can be only always inner join
qb.addSelect(relationAlias).innerJoin(relation.type, relationAlias, "ON", condition);
}
// now recursively go throw its relations
joinRelations(relationAlias, relation.relatedEntityMetadata);
});
};
joinRelations(mainTableAlias, metadata);
Object.keys(postJson).forEach(key => {
});
qb.where(mainTableAlias + "." + metadata.primaryColumn.name + "='" + postId + "'");
console.log(qb.getSql());
}
private convertTableResultToJsonTree() {
}
private objectToEntity(object: any, metadata: EntityMetadata, doFetchProperties?: boolean): Promise<any>;
private objectToEntity(object: any, metadata: EntityMetadata, fetchConditions?: Object): Promise<any>;
private objectToEntity(object: any, metadata: EntityMetadata, fetchOption?: boolean|Object): Promise<any> {