diff --git a/package.json b/package.json index 9512848..c886aa3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "safe-typeorm", - "version": "1.0.15", + "version": "1.0.16", "description": "Make TypeORM much safer", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/src/builders/JoinQueryBuilder.ts b/src/builders/JoinQueryBuilder.ts index ecfebfd..3ea5465 100644 --- a/src/builders/JoinQueryBuilder.ts +++ b/src/builders/JoinQueryBuilder.ts @@ -1,3 +1,4 @@ +import { DomainError } from "tstl/exception/DomainError"; import * as orm from "typeorm"; import { Belongs } from "../decorators/Belongs"; @@ -50,6 +51,14 @@ export class JoinQueryBuilder { private readonly mine_: Creator; private readonly alias_: string; + private readonly joined_: Map< + SpecialFields>, + IJoined + >; + + /* ----------------------------------------------------------- + CONNSTRUCTOR + ----------------------------------------------------------- */ /** * Default Constructor. * @@ -65,6 +74,58 @@ export class JoinQueryBuilder { this.stmt_ = stmt; this.mine_ = mine; this.alias_ = alias === undefined ? mine.name : alias; + this.joined_ = new Map(); + } + + private _Take_join< + Field extends SpecialFields>, + >( + method: IJoined.Method, + field: SpecialFields>, + alias: string | undefined, + closure: + | (( + builder: JoinQueryBuilder< + Relationship.Joinable.TargetType + >, + ) => void) + | undefined, + joiner: (asset: IAsset) => void, + ): JoinQueryBuilder> { + // PREPARE ASSET + const asset: IAsset = prepare_asset( + this.mine_, + field, + alias, + ); + + // CHECK OLDBIE + const oldbie: IJoined | undefined = this.joined_.get(field); + if (oldbie !== undefined) { + if (method !== oldbie.method) + throw new DomainError( + `Error on safe.JoinQueryBuilder.${method}(): ${this.mine_.name}.${field} already has been joined by ${oldbie.method}().`, + ); + else if (asset.alias !== oldbie.alias) + throw new DomainError( + `Error on safe.JoinQueryBuilder.${method}(): ${this.mine_.name}.${field} already has been joined with another alias "${oldbie.alias}".`, + ); + return oldbie.builder; + } + + // DO JOIN + joiner(asset); + + // BUILDER + const builder = new JoinQueryBuilder( + this.stmt_, + asset.metadata.target(), + asset.alias, + ); + this.joined_.set(field, { method, alias: asset.alias, builder }); + + if (closure) closure(builder); + return builder; } /* ----------------------------------------------------------- @@ -146,8 +207,7 @@ export class JoinQueryBuilder { ) => void, ): JoinQueryBuilder> { return this._Join_atomic( - (target, alias, condition) => - this.stmt_.innerJoin(target, alias, condition), + "innerJoin", field, ...get_parametric_tuple(alias, closure), ); @@ -229,8 +289,7 @@ export class JoinQueryBuilder { ) => void, ): JoinQueryBuilder> { return this._Join_atomic( - (target, alias, condition) => - this.stmt_.leftJoin(target, alias, condition), + "leftJoin", field, ...get_parametric_tuple(alias, closure), ); @@ -239,11 +298,7 @@ export class JoinQueryBuilder { private _Join_atomic< Field extends SpecialFields>, >( - joiner: ( - target: Creator>, - alias: string, - condition: string, - ) => orm.SelectQueryBuilder, + method: "innerJoin" | "leftJoin", field: Field, alias: string | undefined, closure: @@ -254,44 +309,31 @@ export class JoinQueryBuilder { ) => void) | undefined, ): JoinQueryBuilder> { - // PREPRAE ASSET - const asset: IAsset = prepare_asset( - this.mine_, - field, - alias, - ); + return this._Take_join(method, field, alias, closure, (asset) => { + // LIST UP EACH FIELDS + const [myField, targetField] = (() => { + if (asset.belongs === true) + return [ + asset.metadata.foreign_key_field, + get_primary_column(asset.metadata.target()), + ]; + + const inverseMetadata: Belongs.ManyToOne.IMetadata = + ReflectAdaptor.get( + asset.metadata.target().prototype, + asset.metadata.inverse, + ) as Belongs.ManyToOne.IMetadata; - // LIST UP EACH FIELDS - const [myField, targetField] = (() => { - if (asset.belongs === true) return [ - asset.metadata.foreign_key_field, - get_primary_column(asset.metadata.target()), + get_primary_column(this.mine_), + inverseMetadata.foreign_key_field, ]; + })(); - const inverseMetadata: Belongs.ManyToOne.IMetadata = - ReflectAdaptor.get( - asset.metadata.target().prototype, - asset.metadata.inverse, - ) as Belongs.ManyToOne.IMetadata; - - return [ - inverseMetadata.foreign_key_field, - get_primary_column(this.mine_), - ]; - })(); - - // DO JOIN - const condition: string = `${this.alias_}.${myField} = ${asset.alias}.${targetField}`; - joiner(asset.metadata.target(), asset.alias, condition); - - // CALL-BACK - return call_back( - this.stmt_, - asset.metadata.target(), - asset.alias, - closure, - ); + // DO JOIN + const condition: string = `${this.alias_}.${myField} = ${asset.alias}.${targetField}`; + this.stmt_[method](asset.metadata.target(), asset.alias, condition); + }); } /* ----------------------------------------------------------- @@ -383,7 +425,7 @@ export class JoinQueryBuilder { ) => void, ): JoinQueryBuilder> { return this._Join_and_select( - (field, alias) => this.stmt_.innerJoinAndSelect(field, alias), + "innerJoinAndSelect", field, ...get_parametric_tuple(alias, closure), ); @@ -475,7 +517,7 @@ export class JoinQueryBuilder { ) => void, ): JoinQueryBuilder> { return this._Join_and_select( - (field, alias) => this.stmt_.leftJoinAndSelect(field, alias), + "leftJoinAndSelect", field, ...get_parametric_tuple(alias, closure), ); @@ -484,7 +526,7 @@ export class JoinQueryBuilder { private _Join_and_select< Field extends SpecialFields>, >( - joiner: (field: string, alias: string) => orm.SelectQueryBuilder, + method: "innerJoinAndSelect" | "leftJoinAndSelect", field: Field, alias: string | undefined, closure: @@ -495,33 +537,16 @@ export class JoinQueryBuilder { ) => void) | undefined, ) { - // PREPARE ASSET - const asset: IAsset = prepare_asset( - this.mine_, - field, - alias, - ); - const index: string = RelationshipVariable.getter( - asset.belongs ? "belongs" : "has", - field, - ); - - // DO JOIN - joiner(`${this.alias_}.${index}`, asset.alias); - - // CALL-BACK - return call_back( - this.stmt_, - asset.metadata.target(), - asset.alias, - closure, - ); + return this._Take_join(method, field, alias, closure, (asset) => { + const index: string = RelationshipVariable.getter( + asset.belongs ? "belongs" : "has", + field, + ); + this.stmt_[method](`${this.alias_}.${index}`, asset.alias); + }); } } -/* ----------------------------------------------------------- - BACKGROUND ------------------------------------------------------------ */ type IAsset< Mine extends object, Field extends SpecialFields>, @@ -567,21 +592,6 @@ function prepare_asset< }; } -function call_back( - stmt: orm.SelectQueryBuilder, - target: Creator, - alias: string, - closure: ((builder: JoinQueryBuilder) => void) | undefined, -): JoinQueryBuilder { - const ret: JoinQueryBuilder = new JoinQueryBuilder( - stmt, - target, - alias, - ); - if (closure !== undefined) closure(ret); - return ret; -} - function get_parametric_tuple( x?: string | Func, y?: Func, @@ -592,3 +602,16 @@ function get_parametric_tuple( function get_primary_column(creator: Creator): string { return ITableInfo.get(creator).primaryColumn; } + +interface IJoined { + method: IJoined.Method; + alias: string; + builder: JoinQueryBuilder; +} +namespace IJoined { + export type Method = + | "innerJoin" + | "leftJoin" + | "innerJoinAndSelect" + | "leftJoinAndSelect"; +}