added first version of FindOptions for repository

This commit is contained in:
Umed Khudoiberdiev 2016-03-24 14:38:53 +05:00
parent 26a5112242
commit 72ada22675
2 changed files with 323 additions and 25 deletions

View File

@ -0,0 +1,227 @@
import {QueryBuilder} from "../query-builder/QueryBuilder";
/**
* Options to be passed to find methods.
*
* Example:
* const options: FindOptions = {
* alias: "photo",
* limit: 100,
* offset: 0,
* firstResult: 5,
* maxResults: 10,
* where: "photo.likesCount > 0 && photo.likesCount < 10",
* having: "photo.viewsCount > 0 && photo.viewsCount < 1000",
* whereConditions: {
* "photo.isPublished": true,
* "photo.name": "Me and Bears"
* },
* havingConditions: {
* "photo.filename": "bears.jpg"
* },
* orderBy: [
* { sort: "photo.id", order: "DESC" }
* ],
* groupBy: [
* "photo.name"
* ],
* leftJoin: {
* author: "photo.author",
* categories: "categories",
* user: "categories.user",
* profile: "user.profile"
* },
* innerJoin: [
* author: "photo.author",
* categories: "categories",
* user: "categories.user",
* profile: "user.profile"
* ],
* leftJoinAndSelect: {
* author: "photo.author",
* categories: "categories",
* user: "categories.user",
* profile: "user.profile"
* },
* innerJoinAndSelect: [
* author: "photo.author",
* categories: "categories",
* user: "categories.user",
* profile: "user.profile"
* ]
* };
*/
export interface FindOptions {
/**
* Alias of the selected entity.
*/
alias: string;
/**
* Selection limitation, e.g. LIMIT expression.
*/
limit?: number;
/**
* From what position to select, e.g. OFFSET expression.
*/
offset?: number;
/**
* First results to select (offset using pagination).
*/
firstResult?: number;
/**
* Maximum result to select (limit using pagination).
*/
maxResults?: number;
/**
* Regular WHERE expression.
*/
where?: string;
/**
* Regular HAVING expression.
*/
having?: string;
/**
* WHERE conditions. Key-value object pair, where each key is a column name and value is a column value.
* "AND" is applied between all parameters.
*/
whereConditions?: Object;
/**
* HAVING conditions. Key-value object pair, where each key is a column name and value is a column value.
* "AND" is applied between all parameters.
*/
havingConditions?: Object;
/**
* Array of ORDER BY expressions.
*/
orderBy?: { sort: string, order: "ASC"|"DESC" }[];
/**
* Array of column to GROUP BY.
*/
groupBy?: string[];
/**
* Array of columns to LEFT JOIN.
*/
leftJoinAndSelect?: { [key: string]: string };
/**
* Array of columns to INNER JOIN.
*/
innerJoinAndSelect?: { [key: string]: string };
/**
* Array of columns to LEFT JOIN.
*/
leftJoin?: { [key: string]: string };
/**
* Array of columns to INNER JOIN.
*/
innerJoin?: { [key: string]: string };
/**
* Parameters used in the WHERE and HAVING expressions.
*/
parameters?: Object;
}
/**
* Utilities to work with FindOptions.
*/
export class FindOptionsUtils {
/**
* Checks if given object is really instance of FindOptions interface.
*/
static isFindOptions(object: any): object is FindOptions {
const possibleOptions: FindOptions = object;
return possibleOptions.alias && typeof possibleOptions.alias === "string" && (
!!possibleOptions.limit ||
!!possibleOptions.offset ||
!!possibleOptions.firstResult ||
!!possibleOptions.maxResults ||
!!possibleOptions.where ||
!!possibleOptions.having ||
!!possibleOptions.whereConditions ||
!!possibleOptions.havingConditions ||
!!possibleOptions.orderBy ||
!!possibleOptions.groupBy ||
!!possibleOptions.leftJoinAndSelect ||
!!possibleOptions.innerJoinAndSelect ||
!!possibleOptions.leftJoin ||
!!possibleOptions.innerJoin ||
!!possibleOptions.parameters
);
}
/**
* Applies give find options to the given query builder.
*/
static applyOptionsToQueryBuilder(qb: QueryBuilder<any>, options: FindOptions): QueryBuilder<any> {
if (options.limit)
qb.setLimit(options.limit);
if (options.offset)
qb.setOffset(options.offset);
if (options.firstResult)
qb.setFirstResult(options.firstResult);
if (options.maxResults)
qb.setMaxResults(options.maxResults);
if (options.where)
qb.where(options.where);
if (options.having)
qb.having(options.having);
if (options.whereConditions) {
Object.keys(options.whereConditions).forEach(key => {
const name = key.indexOf(".") === -1 ? options.alias + "." + key : key;
qb.andWhere(name + "=:" + key);
});
qb.addParameters(options.whereConditions);
}
if (options.havingConditions) {
Object.keys(options.havingConditions).forEach(key => {
const name = key.indexOf(".") === -1 ? options.alias + "." + key : key;
qb.andHaving(name + "=:" + key);
});
qb.addParameters(options.havingConditions);
}
if (options.orderBy)
options.orderBy.forEach(orderBy => qb.addOrderBy(orderBy.sort, orderBy.order));
if (options.groupBy)
options.groupBy.forEach(groupBy => qb.addGroupBy(groupBy));
if (options.leftJoin)
Object.keys(options.leftJoin).forEach(key => qb.leftJoin(options.leftJoin[key], key));
if (options.innerJoin)
Object.keys(options.innerJoin).forEach(key => qb.innerJoin(options.innerJoin[key], key));
if (options.leftJoinAndSelect)
Object.keys(options.leftJoinAndSelect).forEach(key => qb.leftJoinAndSelect(options.leftJoinAndSelect[key], key));
if (options.innerJoinAndSelect)
Object.keys(options.innerJoinAndSelect).forEach(key => qb.innerJoinAndSelect(options.innerJoinAndSelect[key], key));
if (options.parameters)
qb.addParameters(options.parameters);
return qb;
}
}

View File

@ -6,8 +6,7 @@ import {PlainObjectToDatabaseEntityTransformer} from "../query-builder/transform
import {EntityPersistOperationBuilder} from "../persistment/EntityPersistOperationsBuilder";
import {PersistOperationExecutor} from "../persistment/PersistOperationExecutor";
import {EntityWithId} from "../persistment/operation/PersistOperation";
// todo: make extended functionality for findOne, find and findAndCount queries, so no need for user to use query builder
import {FindOptions, FindOptionsUtils} from "./FindOptions";
/**
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
@ -118,45 +117,93 @@ export class Repository<Entity> {
/**
* Finds entities that match given conditions.
*/
find(conditions?: Object): Promise<Entity[]> {
const alias = this.metadata.table.name;
const builder = this.createQueryBuilder(alias);
Object.keys(conditions).forEach(key => builder.where(alias + "." + key + "=:" + key));
return builder.setParameters(conditions).getResults();
find(): Promise<Entity[]>;
/**
* Finds entities that match given conditions.
*/
find(conditions: Object): Promise<Entity[]>;
/**
* Finds entities that match given conditions.
*/
find(options: FindOptions): Promise<Entity[]>;
/**
* Finds entities that match given conditions.
*/
find(conditions: Object, options: FindOptions): Promise<Entity[]>;
/**
* Finds entities that match given conditions.
*/
find(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Promise<Entity[]> {
return this.createFindQueryBuilder(conditionsOrFindOptions, options)
.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() ])
findAndCount(): Promise<{ items: Entity[], count: number }>;
/**
* Finds entities that match given conditions.
*/
findAndCount(conditions: Object): Promise<{ items: Entity[], count: number }>;
/**
* Finds entities that match given conditions.
*/
findAndCount(options: FindOptions): Promise<{ items: Entity[], count: number }>;
/**
* Finds entities that match given conditions.
*/
findAndCount(conditions: Object, options: FindOptions): Promise<{ items: Entity[], count: number }>;
/**
* Finds entities that match given conditions.
*/
findAndCount(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Promise<{ items: Entity[], count: number }> {
const qb = this.createFindQueryBuilder(conditionsOrFindOptions, options);
return Promise.all<any>([ qb.getResults(), qb.getCount() ])
.then(([entities, count]: [Entity[], number]) => ({ items: entities, count: count }));
}
/**
* Finds first entity that matches given conditions.
*/
findOne(conditions: Object): Promise<Entity> {
const alias = this.metadata.table.name;
const builder = this.createQueryBuilder(alias);
Object.keys(conditions).forEach(key => builder.where(alias + "." + key + "=:" + key));
return builder.setParameters(conditions).getSingleResult();
findOne(): Promise<Entity>;
/**
* Finds first entity that matches given conditions.
*/
findOne(conditions: Object): Promise<Entity>;
/**
* Finds first entity that matches given conditions.
*/
findOne(options: FindOptions): Promise<Entity>;
/**
* Finds first entity that matches given conditions.
*/
findOne(conditions: Object, options: FindOptions): Promise<Entity>;
/**
* Finds first entity that matches given conditions.
*/
findOne(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Promise<Entity> {
return this.createFindQueryBuilder(conditionsOrFindOptions, options)
.getSingleResult();
}
/**
* Finds entity with given id.
*/
findById(id: any): Promise<Entity> {
const alias = this.metadata.table.name;
return this.createQueryBuilder(alias)
.where(alias + "." + this.metadata.primaryColumn.name + "=:id")
.setParameter("id", id)
findById(id: any, options?: FindOptions): Promise<Entity> {
return this.createFindQueryBuilder({ [this.metadata.primaryColumn.name]: id }, options)
.getSingleResult();
}
@ -186,6 +233,30 @@ export class Repository<Entity> {
// Private Methods
// -------------------------------------------------------------------------
private createFindQueryBuilder(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions) {
let findOptions: FindOptions, conditions: Object;
if (FindOptionsUtils.isFindOptions(conditionsOrFindOptions)) {
findOptions = conditionsOrFindOptions;
} else {
findOptions = options;
conditions = conditionsOrFindOptions;
}
const alias = findOptions ? options.alias : this.metadata.table.name;
const qb = this.createQueryBuilder(alias);
if (findOptions) {
FindOptionsUtils.applyOptionsToQueryBuilder(qb, findOptions);
}
if (conditions) {
Object.keys(conditions).forEach(key => {
const name = key.indexOf(".") === -1 ? alias + "." + key : key;
qb.andWhere(name + "=:" + key);
});
qb.addParameters(conditions);
}
return qb;
}
/**
* When ORM loads dbEntity it uses joins to load all entity dependencies. However when dbEntity is newly persisted
* to the db, but uses already exist in the db relational entities, those entities cannot be loaded, and will