mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
added first version of FindOptions for repository
This commit is contained in:
parent
26a5112242
commit
72ada22675
227
src/repository/FindOptions.ts
Normal file
227
src/repository/FindOptions.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user