mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
added pagination support
This commit is contained in:
parent
9ebb128979
commit
3951852bbf
71
sample/sample7-pagination/app.ts
Normal file
71
sample/sample7-pagination/app.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import {createMysqlConnection} from "../../src/typeorm";
|
||||
import {Post} from "./entity/Post";
|
||||
import {PostCategory} from "./entity/PostCategory";
|
||||
import {PostAuthor} from "./entity/PostAuthor";
|
||||
|
||||
// first create a connection
|
||||
let options = {
|
||||
host: "192.168.99.100",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "admin",
|
||||
database: "test",
|
||||
autoSchemaCreate: true
|
||||
};
|
||||
|
||||
createMysqlConnection(options, [__dirname + "/entity"]).then(connection => {
|
||||
|
||||
let postRepository = connection.getRepository(Post);
|
||||
const posts: Post[] = [];
|
||||
|
||||
let author = new PostAuthor();
|
||||
author.name = "Umed";
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
|
||||
let category1 = new PostCategory();
|
||||
category1.name = "post category #1";
|
||||
|
||||
let category2 = new PostCategory();
|
||||
category2.name = "post category #2";
|
||||
|
||||
let post = new Post();
|
||||
post.text = "Hello how are you?";
|
||||
post.title = "hello";
|
||||
post.categories.push(category1, category2);
|
||||
post.author = author;
|
||||
|
||||
posts.push(post);
|
||||
}
|
||||
|
||||
const qb = postRepository
|
||||
.createQueryBuilder("p")
|
||||
.leftJoinAndSelect("p.author", "author")
|
||||
.leftJoinAndSelect("p.categories", "categories")
|
||||
.setFirstResult(5)
|
||||
.setMaxResults(10);
|
||||
|
||||
Promise.all(posts.map(post => postRepository.persist(post)))
|
||||
.then(savedPosts => {
|
||||
console.log("Posts has been saved. Lets try to load some posts");
|
||||
return qb.getResults();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log("post loaded: ", loadedPost);
|
||||
console.log("now lets get total post count: ");
|
||||
return qb.getCount();
|
||||
})
|
||||
.then(totalCount => {
|
||||
console.log("total post count: ", totalCount);
|
||||
console.log("now lets try to load it with same repository method:");
|
||||
|
||||
return postRepository.findAndCount();
|
||||
})
|
||||
.then(entitiesWithCount => {
|
||||
console.log("items: ", entitiesWithCount.items);
|
||||
console.log("count: ", entitiesWithCount.count);
|
||||
|
||||
})
|
||||
.catch(error => console.log("Cannot save. Error: ", error.stack ? error.stack : error));
|
||||
|
||||
}, error => console.log("Cannot connect: ", error.stack ? error.stack : error));
|
||||
34
sample/sample7-pagination/entity/Post.ts
Normal file
34
sample/sample7-pagination/entity/Post.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {ManyToMany} from "../../../src/decorator/Relations";
|
||||
import {PostCategory} from "./PostCategory";
|
||||
import {PostAuthor} from "./PostAuthor";
|
||||
import {ManyToOne} from "../../../src/decorator/relations/ManyToOne";
|
||||
|
||||
@Table("sample7_post")
|
||||
export class Post {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column()
|
||||
text: string;
|
||||
|
||||
@ManyToOne(type => PostAuthor, post => post.posts, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
author: PostAuthor;
|
||||
|
||||
@ManyToMany(type => PostCategory, category => category.posts, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
categories: PostCategory[] = [];
|
||||
|
||||
}
|
||||
18
sample/sample7-pagination/entity/PostAuthor.ts
Normal file
18
sample/sample7-pagination/entity/PostAuthor.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {Post} from "./Post";
|
||||
import {OneToMany} from "../../../src/decorator/relations/OneToMany";
|
||||
|
||||
@Table("sample7_post_author")
|
||||
export class PostAuthor {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@OneToMany(type => Post, post => post.author)
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
22
sample/sample7-pagination/entity/PostCategory.ts
Normal file
22
sample/sample7-pagination/entity/PostCategory.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {Post} from "./Post";
|
||||
import {ManyToManyInverse} from "../../../src/decorator/relations/ManyToManyInverse";
|
||||
|
||||
@Table("sample7_post_category")
|
||||
export class PostCategory {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@ManyToManyInverse(type => Post, post => post.categories, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
posts: Post[] = [];
|
||||
|
||||
}
|
||||
@ -17,11 +17,13 @@ export class QueryBuilder<Entity> {
|
||||
// Private properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private broadcaster: OrmBroadcaster;
|
||||
private _aliasMap: AliasMap;
|
||||
private type: "select"|"update"|"delete";
|
||||
private selects: string[] = [];
|
||||
private fromEntity: { alias: Alias };
|
||||
private fromTableName: string;
|
||||
private fromTableAlias: string;
|
||||
private updateQuerySet: Object;
|
||||
private joins: Join[] = [];
|
||||
private groupBys: string[] = [];
|
||||
@ -31,13 +33,16 @@ export class QueryBuilder<Entity> {
|
||||
private parameters: { [key: string]: any } = {};
|
||||
private limit: number;
|
||||
private offset: number;
|
||||
|
||||
private firstResult: number;
|
||||
private maxResults: number;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private connection: Connection) {
|
||||
this._aliasMap = new AliasMap(connection.metadatas);
|
||||
this.broadcaster = new OrmBroadcaster(connection);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -129,6 +134,7 @@ export class QueryBuilder<Entity> {
|
||||
this.fromEntity = { alias: aliasObj };
|
||||
} else {
|
||||
this.fromTableName = <string> entityOrTableName;
|
||||
this.fromTableAlias = alias;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -242,6 +248,16 @@ export class QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
setFirstResult(firstResult: number): this {
|
||||
this.firstResult = firstResult;
|
||||
return this;
|
||||
}
|
||||
|
||||
setMaxResults(maxResults: number): this {
|
||||
this.maxResults = maxResults;
|
||||
return this;
|
||||
}
|
||||
|
||||
setParameter(key: string, value: any): this {
|
||||
this.parameters[key] = value;
|
||||
return this;
|
||||
@ -283,20 +299,123 @@ export class QueryBuilder<Entity> {
|
||||
}
|
||||
|
||||
getResults(): Promise<Entity[]> {
|
||||
const broadcaster = new OrmBroadcaster(this.connection);
|
||||
return this.connection.driver
|
||||
.query<any[]>(this.getSql())
|
||||
.then(results => this.rawResultsToEntities(results))
|
||||
.then(results => {
|
||||
broadcaster.broadcastLoadEventsForAll(results);
|
||||
return results;
|
||||
});
|
||||
const mainAlias = this.aliasMap.mainAlias.name;
|
||||
if (this.firstResult || this.maxResults) {
|
||||
const metadata = this.connection.getMetadata(this.fromEntity.alias.target);
|
||||
const idsQuery = this.clone()
|
||||
.select(`DISTINCT(${mainAlias}.${metadata.primaryColumn.name}) as ids`)
|
||||
.setOffset(this.firstResult)
|
||||
.setLimit(this.maxResults)
|
||||
.getSql();
|
||||
return this.connection.driver
|
||||
.query<any[]>(idsQuery)
|
||||
.then((results: any[]) => {
|
||||
const ids = results.map(result => result["ids"]).join(", ");
|
||||
const queryWithIds = this.clone()
|
||||
.andWhere(mainAlias + "." + metadata.primaryColumn.name + " IN (" + ids + ")")
|
||||
.getSql();
|
||||
return this.connection.driver.query<any[]>(queryWithIds);
|
||||
})
|
||||
.then(results => this.rawResultsToEntities(results))
|
||||
.then(results => {
|
||||
this.broadcaster.broadcastLoadEventsForAll(results);
|
||||
return results;
|
||||
});
|
||||
|
||||
} else {
|
||||
return this.connection.driver
|
||||
.query<any[]>(this.getSql())
|
||||
.then(results => this.rawResultsToEntities(results))
|
||||
.then(results => {
|
||||
this.broadcaster.broadcastLoadEventsForAll(results);
|
||||
return results;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSingleResult(): Promise<Entity> {
|
||||
return this.getResults().then(entities => entities[0]);
|
||||
}
|
||||
|
||||
getCount(): Promise<number> {
|
||||
const mainAlias = this.aliasMap.mainAlias.name;
|
||||
const metadata = this.connection.getMetadata(this.fromEntity.alias.target);
|
||||
const countQuery = this.clone()
|
||||
.select(`COUNT(DISTINCT(${mainAlias}.${metadata.primaryColumn.name})) as cnt`)
|
||||
.getSql();
|
||||
return this.connection.driver
|
||||
.query<any[]>(countQuery)
|
||||
.then(results => parseInt(results[0]["cnt"]));
|
||||
}
|
||||
|
||||
clone() {
|
||||
const qb = new QueryBuilder(this.connection);
|
||||
|
||||
switch (this.type) {
|
||||
case "select":
|
||||
qb.select(this.selects);
|
||||
break;
|
||||
case "update":
|
||||
qb.update(this.updateQuerySet);
|
||||
break;
|
||||
case "delete":
|
||||
qb.delete();
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.fromEntity && this.fromEntity.alias && this.fromEntity.alias.target) {
|
||||
qb.from(this.fromEntity.alias.target, this.fromEntity.alias.name);
|
||||
} else if (this.fromTableName) {
|
||||
qb.from(this.fromTableName, this.fromTableAlias);
|
||||
}
|
||||
|
||||
this.joins.forEach(join => {
|
||||
const property = join.alias.target || (join.alias.parentAliasName + "." + join.alias.parentPropertyName);
|
||||
qb.join(join.type, property, join.alias.name, join.conditionType, join.condition);
|
||||
});
|
||||
|
||||
this.groupBys.forEach(groupBy => qb.addGroupBy(groupBy));
|
||||
|
||||
this.wheres.forEach(where => {
|
||||
switch (where.type) {
|
||||
case "simple":
|
||||
qb.where(where.condition);
|
||||
break;
|
||||
case "and":
|
||||
qb.andWhere(where.condition);
|
||||
break;
|
||||
case "or":
|
||||
qb.orWhere(where.condition);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.havings.forEach(having => {
|
||||
switch (having.type) {
|
||||
case "simple":
|
||||
qb.where(having.condition);
|
||||
break;
|
||||
case "and":
|
||||
qb.andWhere(having.condition);
|
||||
break;
|
||||
case "or":
|
||||
qb.orWhere(having.condition);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.orderBys.forEach(orderBy => qb.addOrderBy(orderBy.sort, orderBy.order));
|
||||
Object.keys(this.parameters).forEach(key => qb.setParameter(key, this.parameters[key]))
|
||||
|
||||
qb.setLimit(this.limit)
|
||||
.setOffset(this.offset)
|
||||
.setFirstResult(this.firstResult)
|
||||
.setMaxResults(this.maxResults);
|
||||
|
||||
return qb;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
@ -365,7 +484,8 @@ export class QueryBuilder<Entity> {
|
||||
this.addParameters(params);
|
||||
return "UPDATE " + tableName + " " + (alias ? alias : "") + " SET " + updateSet;
|
||||
}
|
||||
return "";
|
||||
|
||||
throw new Error("No query builder type is specified.");
|
||||
}
|
||||
|
||||
protected createWhereExpression() {
|
||||
|
||||
@ -7,7 +7,7 @@ import {EntityPersistOperationBuilder} from "../persistment/EntityPersistOperati
|
||||
import {PersistOperationExecutor} from "../persistment/PersistOperationExecutor";
|
||||
import {EntityWithId} from "../persistment/operation/PersistOperation";
|
||||
|
||||
// todo: think how we can implement queryCount, queryManyAndCount
|
||||
// todo: make extended functionality for findOne, find and findAndCount queries, so no need for user to use query builder
|
||||
|
||||
/**
|
||||
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
|
||||
@ -125,6 +125,20 @@ export class Repository<Entity> {
|
||||
return builder.setParameters(conditions).getResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds entities that match given conditions.
|
||||
*/
|
||||
findAndCount(conditions?: Object): Promise<{ items: Entity[], count: number }> {
|
||||
const alias = this.metadata.table.name;
|
||||
const builder = this.createQueryBuilder(alias);
|
||||
if (conditions) {
|
||||
Object.keys(conditions).forEach(key => builder.where(alias + "." + key + "=:" + key));
|
||||
builder.setParameters(conditions);
|
||||
}
|
||||
return Promise.all<any>([ builder.getResults(), builder.getCount() ])
|
||||
.then(([entities, count]: [Entity[], number]) => ({ items: entities, count: count }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first entity that matches given conditions.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user