import { type AnyColumn, Column, SQL, type SQLWrapper, type Table } from 'drizzle-orm';
import type { DrizzleDialect } from './operator-resolver.js';
import { type DrizzleSchemaMetadata, type ResolvedRelation } from './schema-metadata.js';
import type { DrizzleDatabase } from './types.js';
/**
 * The slice of a drizzle select builder the query uses. Every dialect's builder
 * has this shape at runtime; the structural type sidesteps the union of three
 * dialect-specific overload sets, which TypeScript cannot call through.
 */
interface SelectChain extends SQLWrapper {
    where(condition: SQL | undefined): SelectChain;
    orderBy(...columns: Array<SQL | SQL.Aliased | AnyColumn>): SelectChain;
    groupBy(...columns: Array<SQL | AnyColumn>): SelectChain;
    limit(limit: number): SelectChain;
    offset(offset: number): SelectChain;
    as(alias: string): Table;
    toSQL(): {
        sql: string;
        params: unknown[];
    };
    then<R>(onfulfilled: (rows: Record<string, unknown>[]) => R): Promise<R>;
}
interface QueryDatabase {
    select(fields?: Record<string, unknown>): {
        from(source: unknown): SelectChain;
    };
    selectDistinct(fields?: Record<string, unknown>): {
        from(source: unknown): SelectChain;
    };
}
/** A row of `TTable`, plus whatever includes / computed aliases were attached. */
export type DrizzleRow<TTable extends Table> = TTable['$inferSelect'] & Record<string, unknown>;
/** A projected field: a column, a raw expression, or an aliased expression. */
export type SelectionValue = AnyColumn | SQL | SQL.Aliased;
/**
 * State shared by a root query and every child it spawns (relation constraints,
 * nested `EXISTS`): the database, the dialect, the schema metadata, and ONE
 * alias counter — so two subqueries over the same table never collide, however
 * deeply they nest.
 */
export declare class DrizzleQueryContext {
    readonly db: DrizzleDatabase;
    readonly dialect: DrizzleDialect;
    readonly metadata: DrizzleSchemaMetadata;
    private aliasSeq;
    constructor(db: DrizzleDatabase, dialect: DrizzleDialect, metadata: DrizzleSchemaMetadata);
    /** A fresh, SQL-safe table alias (`posts_1`, `manager_2`, …). */
    nextAlias(base: string): string;
    /** @internal — the untyped select entry points. */
    get queryDb(): QueryDatabase;
}
/**
 * The query state the core hands to a Drizzle filter as `this.$query`.
 *
 * Drizzle's select builders are immutable-ish and single-shot (`.where()`
 * replaces, the builder is bound to one projection at creation time), which is
 * the opposite of what a filter pipeline needs: many independent methods each
 * contributing a condition. So filters write into this accumulator instead —
 * conditions, ordering, a page window, a projection, includes — and the
 * accumulated state is materialized into ONE `db.select().from(table)` at
 * execution time ({@link toSelect}, {@link execute}).
 *
 * ```ts
 * @FilterFor('minAge')
 * applyMinAge(value: number) {
 *   this.$query.where(gte(users.age, value));
 * }
 * ```
 *
 * Relations never become joins on the root query. A relation constraint is an
 * `EXISTS (…)` subquery and an include is a second, batched query — so the
 * root query always returns exactly one row per matching entity, and
 * `LIMIT`/`OFFSET`/`COUNT(*)` stay correct with any number of to-many
 * relations involved.
 */
export declare class DrizzleQuery<TTable extends Table = Table> {
    readonly context: DrizzleQueryContext;
    readonly baseTable: TTable;
    private readonly conditions;
    private readonly orderings;
    private limitValue;
    private offsetValue;
    private projection;
    private readonly extras;
    private distinctFlag;
    private readonly includePaths;
    /**
     * @param context - Shared db/dialect/metadata/alias state.
     * @param baseTable - The ORIGINAL table object (metadata is keyed by it).
     * @param aliased - When this query targets an aliased copy of the table (a
     *   relation constraint's subquery), that alias; columns are read off it.
     */
    constructor(context: DrizzleQueryContext, baseTable: TTable, aliased?: TTable);
    /** The table the query reads — the original, or its alias inside a subquery. */
    readonly table: TTable;
    /** The drizzle database the query executes against. */
    get db(): DrizzleDatabase;
    /** `'postgres' | 'mysql' | 'sqlite'`. */
    get dialect(): DrizzleDialect;
    /** The table's columns keyed by property name — `this.$query.columns.email`. */
    get columns(): TTable['_']['columns'];
    /**
     * ANDs one or more conditions onto the query. `undefined` entries are
     * ignored, so drizzle's `and()`/`or()` (which return `undefined` for an empty
     * list) compose without guards.
     */
    where(...conditions: Array<SQL | undefined>): this;
    /**
     * Like {@link where}, but also accepts an equality map keyed by column
     * property — `andWhere({ status: 'active', role: ['a', 'b'] })` — which is
     * the shape the core's `related()` helper and `@TenantScoped` emit. Array
     * values become `IN`, `null` becomes `IS NULL`; keys that are not columns of
     * the table are ignored.
     */
    andWhere(condition: SQL | Record<string, unknown> | undefined): this;
    /**
     * Constrains the query to rows that HAVE a related row — `EXISTS (SELECT 1
     * FROM <relation> WHERE <correlation> AND <build(target)>)`. The callback
     * receives the related table (an alias — reference its columns through the
     * argument, not the imported table object). A dotted path follows several
     * relations (`'posts.comments'`).
     *
     * ```ts
     * this.$query.whereHas('posts', (posts) => eq(posts.status, 'published'));
     * ```
     *
     * An unknown relation throws — silently ignoring a constraint would return
     * rows the caller asked to exclude.
     */
    whereHas(relationPath: string, build?: (target: Table) => SQL | undefined): this;
    /** The negation of {@link whereHas}: rows with NO matching related row. */
    whereDoesntHave(relationPath: string, build?: (target: Table) => SQL | undefined): this;
    /** The accumulated WHERE — every condition ANDed — or `undefined` when empty. */
    getWhere(): SQL | undefined;
    /**
     * Appends ORDER BY terms. A bare column sorts ascending; use drizzle's
     * `asc()`/`desc()` for an explicit direction.
     */
    orderBy(...terms: Array<SQL | SQL.Aliased | AnyColumn>): this;
    /** Drops every ORDER BY term accumulated so far. */
    clearOrderBy(): this;
    limit(limit: number | undefined): this;
    offset(offset: number | undefined): this;
    /**
     * Replaces the projection with the given fields (keyed by output name). The
     * default projection is every column of the table.
     */
    select(fields: Record<string, SelectionValue>): this;
    /**
     * Adds fields to the projection without replacing it — computed values that
     * should come back on each row next to the table's own columns.
     */
    addSelect(fields: Record<string, SelectionValue>): this;
    /** Marks the projection `SELECT DISTINCT`. */
    distinct(on?: boolean): this;
    /**
     * Relations to load onto the fetched rows (dotted for nesting:
     * `'posts.comments'`). Loaded by {@link execute} in separate batched
     * queries after the page is fetched, never joined — see the class doc.
     */
    include(...relationPaths: string[]): this;
    isDistinct(): boolean;
    getIncludes(): readonly string[];
    getOrderBy(): ReadonlyArray<SQL | SQL.Aliased | AnyColumn>;
    getLimit(): number | undefined;
    getOffset(): number | undefined;
    /** The projection currently in effect (explicit, or all columns), plus additive fields. */
    getSelection(): Record<string, SelectionValue>;
    /** True when the projection was narrowed (`select`/`distinct`) rather than all columns. */
    hasExplicitProjection(): boolean;
    /**
     * Materializes the accumulated state as a drizzle select builder:
     * `db.select(<projection>).from(table).where(…).orderBy(…).limit(…).offset(…)`.
     * Use it to extend the query with anything the accumulator does not model,
     * or to hand it to drizzle APIs that take a builder.
     */
    toSelect(opts?: {
        withWindow?: boolean;
        withOrder?: boolean;
    }): SelectChain;
    /** The SQL + bound params the query would run. */
    toSQL(): {
        sql: string;
        params: unknown[];
    };
    /** Runs the query and loads every {@link include}d relation onto the rows. */
    execute(): Promise<DrizzleRow<TTable>[]>;
    /**
     * `COUNT(*)` over the same WHERE, ignoring ordering and the page window. For
     * a DISTINCT projection, counts distinct TUPLES of the projection (via a
     * derived table, the one form every dialect accepts for several columns).
     */
    count(): Promise<number>;
    /** {@link execute} + {@link count} — the page and the total it was cut from. */
    executeAndCount(): Promise<{
        rows: DrizzleRow<TTable>[];
        total: number;
    }>;
    /**
     * The projection sent to the database. Includes need the key they join on:
     * when a narrowed projection dropped it, it is added back so the include can
     * still be resolved.
     */
    private selectionForExecution;
    /**
     * A column of this query's table by property key — the aliased column when
     * the query runs over an alias.
     */
    column(key: string): Column | undefined;
    /**
     * `EXISTS` over a relation chain starting at this query's table. The last
     * hop's (aliased) table is handed to `build` for the inner condition.
     * Returns `undefined` when any segment is not a relation.
     */
    relationExists(segments: string[], build?: (target: Table) => SQL | undefined): SQL | undefined;
    /**
     * A scalar expression for a to-one relation path's column
     * (`manager.name` → `(SELECT m.name FROM users m WHERE m.id = users.manager_id)`),
     * used for ORDER BY / DISTINCT on relation fields without joining. Returns
     * `undefined` when the path crosses a to-many relation (it has no single
     * value) or does not resolve.
     */
    relationScalar(segments: string[]): SQL | undefined;
    /**
     * Creates the child query a relation constraint's filter writes into: it
     * targets a fresh alias of the related table, and {@link relationExistsFor}
     * later folds its WHERE into an `EXISTS` on this query.
     */
    childFor(relation: ResolvedRelation): DrizzleQuery<Table>;
    /**
     * The predicate correlating `targetView` (an alias of `relation.target`) to
     * this query's table — the WHERE of a correlated subquery over the relation.
     */
    correlate(relation: ResolvedRelation, targetView: Table): SQL | undefined;
    /** `EXISTS` correlating `child` (made by {@link childFor}) to this query's table. */
    relationExistsFor(relation: ResolvedRelation, child: DrizzleQuery<Table>): SQL;
}
/**
 * Loads relation paths onto already-fetched rows with one batched
 * `SELECT … WHERE <key> IN (…)` per relation (and per nesting level), then
 * grafts the results back: a to-many relation becomes an array (empty when
 * nothing matched), a to-one relation the row or `null`. The shape matches
 * drizzle's relational queries (`with: { posts: true }`).
 *
 * Unknown relations and composite-key relations are skipped.
 */
export declare function loadRelations(context: DrizzleQueryContext, table: Table, rows: Record<string, unknown>[], paths: readonly string[]): Promise<void>;
export {};
//# sourceMappingURL=drizzle-query.d.ts.map