added subselects

This commit is contained in:
Umed Khudoiberdiev 2017-06-28 12:50:48 +05:00
parent d9e5b45113
commit aeadb6f9e0
7 changed files with 398 additions and 24 deletions

View File

@ -50,8 +50,24 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "simple", condition: where });
where(where: string, parameters?: ObjectLiteral): this;
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres = [{ type: "simple", condition: typeof where === "string" ? where : where(this) }];
if (parameters) this.setParameters(parameters);
return this;
}
@ -60,8 +76,20 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: where });
andWhere(where: string, parameters?: ObjectLiteral): this;
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: typeof where === "string" ? where : where(this) });
if (parameters) this.setParameters(parameters);
return this;
}
@ -70,8 +98,20 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: where });
orWhere(where: string, parameters?: ObjectLiteral): this;
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: typeof where === "string" ? where : where(this) });
if (parameters) this.setParameters(parameters);
return this;
}
@ -81,7 +121,7 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
*/
whereInIds(ids: any[]): this {
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.andWhere(whereExpression, parameters);
this.where(whereExpression, parameters);
return this;
}

View File

@ -380,7 +380,7 @@ export abstract class QueryBuilder<Entity> {
* Specifies FROM which entity's table select/update/delete will be executed.
* Also sets a main string alias of the selection data.
*/
protected setMainAlias(entityTarget: Function|string, aliasName?: string): this {
protected setMainAlias(entityTarget: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName?: string): this {
// if table has a metadata then find it to properly escape its properties
// const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
@ -391,8 +391,16 @@ export abstract class QueryBuilder<Entity> {
});
} else {
let subQuery: string = "";
if (entityTarget instanceof Function) {
const subQueryBuilder: SelectQueryBuilder<any> = (entityTarget as any)(((this as any) as SelectQueryBuilder<any>).subQuery());
this.setParameters(subQueryBuilder.getParameters());
subQuery = subQueryBuilder.getQuery();
} else {
subQuery = entityTarget;
}
const isSubQuery = entityTarget instanceof Function || entityTarget.substr(0, 1) === "(" && entityTarget.substr(-1) === ")";
const subQuery = entityTarget instanceof Function ? entityTarget(((this as any) as SelectQueryBuilder<any>).subQuery()).getQuery() : entityTarget;
this.expressionMap.createMainAlias({
name: aliasName,
tableName: isSubQuery === false ? entityTarget as string : undefined,

View File

@ -100,7 +100,19 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Specifies FROM which entity's table select/update/delete will be executed.
* Also sets a main string alias of the selection data.
*/
from<T>(entityTarget: ObjectType<T>|string, aliasName: string): SelectQueryBuilder<T> {
from<T>(entityTarget: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string): SelectQueryBuilder<T>;
/**
* Specifies FROM which entity's table select/update/delete will be executed.
* Also sets a main string alias of the selection data.
*/
from<T>(entityTarget: ObjectType<T>|string, aliasName: string): SelectQueryBuilder<T>;
/**
* Specifies FROM which entity's table select/update/delete will be executed.
* Also sets a main string alias of the selection data.
*/
from<T>(entityTarget: ObjectType<T>|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string): SelectQueryBuilder<T> {
this.setMainAlias(entityTarget, aliasName);
return (this as any) as SelectQueryBuilder<T>;
}
@ -491,8 +503,24 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "simple", condition: where });
where(where: string, parameters?: ObjectLiteral): this;
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres = [{ type: "simple", condition: typeof where === "string" ? where : where(this) }];
if (parameters) this.setParameters(parameters);
return this;
}
@ -501,8 +529,20 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: where });
andWhere(where: string, parameters?: ObjectLiteral): this;
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: typeof where === "string" ? where : where(this) });
if (parameters) this.setParameters(parameters);
return this;
}
@ -511,8 +551,20 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: where });
orWhere(where: string, parameters?: ObjectLiteral): this;
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: typeof where === "string" ? where : where(this) });
if (parameters) this.setParameters(parameters);
return this;
}
@ -522,7 +574,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
*/
whereInIds(ids: any[]): this {
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.andWhere(whereExpression, parameters);
this.where(whereExpression, parameters);
return this;
}

View File

@ -48,8 +48,24 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "simple", condition: where });
where(where: string, parameters?: ObjectLiteral): this;
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres = [{ type: "simple", condition: typeof where === "string" ? where : where(this) }];
if (parameters) this.setParameters(parameters);
return this;
}
@ -58,8 +74,20 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: where });
andWhere(where: string, parameters?: ObjectLiteral): this;
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: typeof where === "string" ? where : where(this) });
if (parameters) this.setParameters(parameters);
return this;
}
@ -68,8 +96,20 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: where });
orWhere(where: string, parameters?: ObjectLiteral): this;
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: (qb: this) => string, parameters?: ObjectLiteral): this;
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string|((qb: this) => string), parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: typeof where === "string" ? where : where(this) });
if (parameters) this.setParameters(parameters);
return this;
}
@ -79,7 +119,7 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
*/
whereInIds(ids: any[]): this {
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.andWhere(whereExpression, parameters);
this.where(whereExpression, parameters);
return this;
}

View File

@ -0,0 +1,14 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
}

View File

@ -0,0 +1,17 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
registered: boolean;
}

View File

@ -0,0 +1,203 @@
import "reflect-metadata";
import * as chai from "chai";
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {User} from "./entity/User";
import {Post} from "./entity/Post";
const should = chai.should();
describe.only("query builder > sub-query", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
async function prepare(connection: Connection) {
const user1 = new User();
user1.name = "Alex Messer";
user1.registered = true;
await connection.manager.save(user1);
const user2 = new User();
user2.name = "Dima Zotov";
user2.registered = true;
await connection.manager.save(user2);
const user3 = new User();
user3.name = "Umed Khudoiberdiev";
user3.registered = false;
await connection.manager.save(user3);
const post1 = new Post();
post1.title = "Alex Messer";
await connection.manager.save(post1);
const post2 = new Post();
post2.title = "Dima Zotov";
await connection.manager.save(post2);
const post3 = new Post();
post3.title = "Umed Khudoiberdiev";
await connection.manager.save(post3);
}
it("should execute sub query in where string using subQuery method", () => Promise.all(connections.map(async connection => {
await prepare(connection);
const qb = await connection.getRepository(Post).createQueryBuilder("post");
const posts = await qb
.where("post.title IN " + qb.subQuery().select("user.name").from(User, "user").where("user.registered = :registered").getQuery())
.setParameter("registered", true)
.getMany();
posts.should.be.eql([
{ id: 1, title: "Alex Messer" },
{ id: 2, title: "Dima Zotov" },
]);
})));
it("should execute sub query in where function using subQuery method", () => Promise.all(connections.map(async connection => {
await prepare(connection);
const posts = await connection.getRepository(Post)
.createQueryBuilder("post")
.where(qb => {
const subQuery = qb.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery();
return "post.title IN " + subQuery;
})
.setParameter("registered", true)
.getMany();
posts.should.be.eql([
{ id: 1, title: "Alex Messer" },
{ id: 2, title: "Dima Zotov" },
]);
})));
it("should execute sub query in where function using subQuery method", () => Promise.all(connections.map(async connection => {
await prepare(connection);
const posts = await connection.getRepository(Post)
.createQueryBuilder("post")
.where(qb => {
const subQuery = qb.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery();
return "post.title IN " + subQuery;
})
.setParameter("registered", true)
.getMany();
posts.should.be.eql([
{ id: 1, title: "Alex Messer" },
{ id: 2, title: "Dima Zotov" },
]);
})));
it("should execute sub query using different query builder", () => Promise.all(connections.map(async connection => {
await prepare(connection);
const userQb = await connection.getRepository(User)
.createQueryBuilder("user")
.select("user.name")
.where("user.registered = :registered", { registered: true });
const posts = await connection.getRepository(Post)
.createQueryBuilder("post")
.where("post.title IN (" + userQb.getQuery() + ")")
.setParameters(userQb.getParameters())
.getMany();
posts.should.be.eql([
{ id: 1, title: "Alex Messer" },
{ id: 2, title: "Dima Zotov" },
]);
})));
it("should execute sub query in from expression (using different query builder)", () => Promise.all(connections.map(async connection => {
await prepare(connection);
const userQb = await connection.getRepository(User)
.createQueryBuilder("user")
.select("user.name", "name")
.where("user.registered = :registered", { registered: true });
const posts = await connection
.createQueryBuilder()
.select("user.name", "name")
.from("(" + userQb.getQuery() + ")", "user")
.setParameters(userQb.getParameters())
.getRawMany();
posts.should.be.eql([
{ name: "Alex Messer" },
{ name: "Dima Zotov" },
]);
})));
it("should execute sub query in from expression (using from's query builder)", () => Promise.all(connections.map(async connection => {
await prepare(connection);
const userQb = await connection.getRepository(User)
.createQueryBuilder("user")
.select("user.name", "name")
.where("user.registered = :registered", { registered: true });
const posts = await connection
.createQueryBuilder()
.select("user.name", "name")
.from(subQuery => {
return subQuery
.select("user.name", "name")
.from(User, "user")
.where("user.registered = :registered", { registered: true });
}, "user")
.setParameters(userQb.getParameters())
.getRawMany();
posts.should.be.eql([
{ name: "Alex Messer" },
{ name: "Dima Zotov" },
]);
})));
it.only("should execute sub query in from expression (using from's query builder)", () => Promise.all(connections.map(async connection => {
await prepare(connection);
const userQb = await connection.getRepository(User)
.createQueryBuilder("user")
.select("user.name", "name")
.where("user.registered = :registered", { registered: true });
const posts = await connection
.createQueryBuilder()
.select("user.name", "name")
.from(subQuery => {
return subQuery
.select("user.name", "name")
.from(User, "user")
.where("user.registered = :registered", { registered: true });
}, "user")
.setParameters(userQb.getParameters())
.getRawMany();
posts.should.be.eql([
{ name: "Alex Messer" },
{ name: "Dima Zotov" },
]);
})));
});