import { DataTable } from '../database/DataTable'; import { PaginationResult } from '../database/PaginationResult'; import { Field } from '../database/query'; import { Condition, ConditionGroup, ExistsSubquery } from '../database/query/conditions'; import { OrderByDirection } from '../database/query/features/HasOrderByFields'; import { EntityRepository } from './EntityRepository'; /** A single eager-load entry: the dot-notation path and an optional constraint callback. */ export type WithEntry = { path: string; callback?: (query: EntityQuery) => void; }; /** * Chainable query builder for Entities. Wraps DataTable and hydrates rows * into typed entity instances when a terminal method is called. * * Constructed by EntityRepository.query() — do not instantiate directly. */ export declare class EntityQuery { private _repository; private _table; private _withs; private _scopesApplied; private _excludedScopes; private _skipAllScopes; /** * Subqueries built by whereHas/orWhereHas, waiting for their own entity's * global scopes. They cannot be resolved when the EXISTS is built, because * whereHas is a synchronous chainable and a scope may be asynchronous — so * they are held here and resolved by _applyGlobalScopes, which already runs * (once, awaited) right before the statement is compiled. See _applyWhereHas. */ private _pendingRelationQueries; constructor(repository: EntityRepository, table: DataTable); /** * Excludes one or more named global scopes from being applied to this query. * * Product.withoutGlobalScope('active').get() */ withoutGlobalScope(...names: string[]): this; /** * Disables all global scopes for this query. * * Product.withoutGlobalScopes().get() */ withoutGlobalScopes(): this; /** Applies all registered global scopes that have not been excluded. Called lazily before terminal methods. */ private _applyGlobalScopes; /** * Applies the pending whereHas subqueries' own global scopes and folds their * conditions into the EXISTS that is already part of this statement. * * This is what makes `whereHas('posts')` mean "has posts that are visible to * you" rather than "has a row in the posts table": a global scope states an * invariant about the entity, so a relation constraint has to honour it the * same way a direct query does — and the same way `with()` already does when * it eager-loads that relation. A callback that wants the raw table opts out * from inside, with the sub-query's own `withoutGlobalScope(s)`. * * Resolution is recursive: applying a sub-query's scopes resolves ITS pending * subqueries in turn, so a nested whereHas is scoped at every level. */ private _resolvePendingRelationQueries; when(condition: any, callback: (query: this) => void): this; with(relations: Record) => void>): this; with(relations: string | string[], ...rest: string[]): this; joinRelationship(relationName: string): this; innerJoinRelationship(relationName: string): this; leftJoinRelationship(relationName: string): this; whereHas(relationName: string, callback?: (query: EntityQuery) => void): this; orWhereHas(relationName: string, callback?: (query: EntityQuery) => void): this; private _applyWhereHas; private _applyJoin; get(): Promise; first(): Promise; count(column?: Field): Promise; /** * SUM of a column, with this entity's global scopes applied — so it answers * "the total of the rows you are allowed to see", not "the total in the * table". Sums to 0 over an empty result set. * * await Order.where('paid', true).sum('amount') */ sum(column: Field): Promise; /** AVG of a column with the global scopes applied, or null when nothing matched. */ avg(column: Field): Promise; /** * MIN of a column with the global scopes applied, or null when nothing * matched. Returned as the driver produced it, uncast — see `DataTable.min`. */ min(column: Field): Promise; /** MAX of a column with the global scopes applied, or null when nothing matched. */ max(column: Field): Promise; /** * Drops down to the underlying DataTable with this entity's global scopes * already folded in, so the rows come back raw instead of hydrated into * entities. The escape hatch for queries whose result is not a row of the * table — grouped aggregates above all. * * const totals = await (await Ranking.where('userId', id).toBase()) * .select('categoryId', 'SUM(points) AS points') * .groupBy('categoryId') * .get() * * Needed because `get()` hydrates every row through the entity's declared * columns, which is the right thing for a row of the table and drops anything * a projection added: `Ranking.select('SUM(points) AS total').get()` hands * back a Ranking with no `total` on it (and no points either). Reach for this * instead, and the scopes still apply — the whole point of going through the * entity rather than `DB.table(...)`. * * It is `async` because a global scope may be asynchronous (it might read the * current session, for instance), so the scopes can only be applied by * awaiting. The DataTable returned is the query's own, already scoped: keep * chaining on it, and do not reuse the EntityQuery afterwards. */ toBase(): Promise; paginate(perPage?: number, page?: number): Promise>; find(id: any): Promise; private _loadRelations; private _resolveFieldName; private _resolveField; private _resolveCondition; private _resolveConditionGroup; where(callback: (group: ConditionGroup) => void): this; where(condition: Condition): this; where(field: Field, value: any): this; where(field: Field, operator: string, value: any): this; whereIn(field: Field, values: any[]): this; whereNotIn(field: Field, values: any[]): this; whereBetween(field: Field, range: [any, any]): this; whereNotBetween(field: Field, range: [any, any]): this; whereNull(field: Field): this; whereNotNull(field: Field): this; whereLike(field: Field, pattern: string, caseSensitive?: boolean): this; whereNotLike(field: Field, pattern: string, caseSensitive?: boolean): this; whereColumn(field: Field, column: Field): this; whereColumn(field: Field, operator: string, column: Field): this; whereExists(subquery: ExistsSubquery): this; whereNotExists(subquery: ExistsSubquery): this; whereArrayContains(field: Field, value: any): this; orWhere(...args: any[]): this; orWhereIn(field: Field, values: any[]): this; orWhereNotIn(field: Field, values: any[]): this; orWhereBetween(field: Field, range: [any, any]): this; orWhereNotBetween(field: Field, range: [any, any]): this; orWhereNull(field: Field): this; orWhereNotNull(field: Field): this; orWhereLike(field: Field, pattern: string, caseSensitive?: boolean): this; orWhereNotLike(field: Field, pattern: string, caseSensitive?: boolean): this; orWhereExists(subquery: ExistsSubquery): this; orWhereNotExists(subquery: ExistsSubquery): this; orWhereArrayContains(field: Field, value: any): this; /** * HAVING condition on a grouped query. Same three shapes as `where`, and the * field goes through the same property → column resolution, so both a * declared property and a raw aggregate expression work: * * Ranking.groupBy('userId').having('SUM(points)', '>', 100) */ having(condition: Condition): this; having(field: Field, value: any): this; having(field: Field, operator: string, value: any): this; /** OR-connected HAVING condition. See `having`. */ orHaving(condition: Condition): this; orHaving(field: Field, value: any): this; orHaving(field: Field, operator: string, value: any): this; select(...fields: (Field | Field[])[]): this; orderBy(field: Field, direction?: OrderByDirection): this; orderByDesc(field: Field): this; groupBy(...fields: Field[]): this; limit(value: number): this; offset(value: number): this; distinct(): this; }