mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
experementing with always left join option
This commit is contained in:
parent
4309b8d810
commit
7d34424eec
@ -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);
|
||||
|
||||
@ -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;*/
|
||||
|
||||
}
|
||||
21
sample/sample2-one-to-one/entity/ImageDetails.ts
Normal file
21
sample/sample2-one-to-one/entity/ImageDetails.ts
Normal 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;
|
||||
|
||||
}
|
||||
@ -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[];
|
||||
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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("\$\$", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user