import { Condition, SqlFragment, SqlBuildContext, FieldRef, UnwrapSelection } from './conditions'; import { PreparedQuery } from './prepared-query'; import { TableSchema } from '../schema/table-builder'; import type { CollectionStrategyType, OrderDirection, FluentDelete, FluentQueryUpdate } from '../entity/db-context'; import { QueryExecutor } from '../entity/db-context'; import type { DatabaseClient } from '../database/database-client.interface'; import { Subquery } from './subquery'; import { GroupedQueryBuilder } from './grouped-query'; import { DbCte } from './cte-builder'; import type { SelectedField, NavigationJoin } from './collection-strategy.interface'; import { UnionQueryBuilder } from './union-builder'; import { FutureQuery, FutureSingleQuery, FutureCountQuery } from './future-query'; /** * Performance utility: Get column name map from schema, using cached version if available */ export declare function getColumnNameMapForSchema(schema: TableSchema): Map; /** * Performance utility: Get relation entries array from schema, using cached version if available */ export declare function getRelationEntriesForSchema(schema: TableSchema): Array<[string, any]>; /** * Performance utility: Get target schema for a relation, using cached version if available */ export declare function getTargetSchemaForRelation(schema: TableSchema, relName: string, relConfig: { targetTableBuilder?: { build(): TableSchema; }; }): TableSchema | undefined; /** * Creates a nested proxy that supports accessing properties at any depth. * This allows patterns like `p.product.priceMode` to work even without full schema information. * Each property access returns an object that is both a FieldRef and can be further accessed. * * @param tableAlias The table alias to use for the FieldRef * @returns A proxy that creates FieldRefs for any property access */ export declare function createNestedFieldRefProxy(tableAlias: string): any; /** * Join type */ export type JoinType = 'INNER' | 'LEFT'; /** * Manual join definition */ export interface ManualJoinDefinition { type: JoinType; table: string; alias: string; schema: TableSchema; condition: Condition; cte?: DbCte; } /** * Query context for tracking CTEs and aliases */ export interface QueryContext { ctes: Map; cteCounter: number; paramCounter: number; allParams: any[]; collectionStrategy?: CollectionStrategyType; executor?: QueryExecutor; /** Map of placeholder names to their parameter indices (for prepared statements) */ placeholders?: Map; /** * Map of original table names to their LATERAL aliases. * Used by nested LATERALs to reference parent collection tables correctly. * Example: { "posts": "lateral_0_posts" } */ lateralTableAliasMap?: Map; /** * True when the driver cannot decode native ARRAY result columns * (BunClient) — array-producing aggregations must use json_agg. */ useJsonArrayAggregation?: boolean; } export declare class QueryBuilder { /** @internal Chain identity — see chainIdSeq. */ chainId: number; private schema; private client; private whereCond?; private selection?; private limitValue?; private offsetValue?; private orderByFields; private executor?; private manualJoins; private joinCounter; private collectionStrategy?; private schemaRegistry?; private _cachedMockRow?; constructor(schema: TSchema, client: DatabaseClient, whereCond?: Condition, limit?: number, offset?: number, orderBy?: Array<{ field: string; direction: 'ASC' | 'DESC'; }>, executor?: QueryExecutor, manualJoins?: ManualJoinDefinition[], joinCounter?: number, collectionStrategy?: CollectionStrategyType, schemaRegistry?: Map); /** * Override the timeout for this single query (in milliseconds). Only this query * is wrapped (`SET LOCAL statement_timeout` inside a short transaction) — other * queries are unaffected. Overrides the connection-level default; pass `0` to * disable the timeout for this query. On timeout a `QueryTimeoutError` is thrown. * * @example * await db.users.where(u => gt(u.id, 0)).withTimeout(5000).select(...).toList(); */ withTimeout(timeoutMs: number): this; /** * Mark this query as expected to finish within `expectedMs` (ms). If it runs * longer, the context's `onQueryTakingTooLong` callback fires — the query is * NOT cancelled (use `.withTimeout()` for that). Overrides the context's * `longRunningQueryThreshold` for this query. */ expectedExecutionTime(expectedMs: number): this; /** * Get qualified table name with schema prefix if specified */ private getQualifiedTableName; /** * Define the selection with support for nested queries * UnwrapSelection extracts the value types from SqlFragment expressions */ select(selector: (row: TRow) => TSelection): SelectQueryBuilder>; /** * Add WHERE condition * Multiple where() calls are chained with AND logic */ where(condition: (row: TRow) => Condition): this; /** * Add CTEs (Common Table Expressions) to the query */ with(...ctes: DbCte[]): SelectQueryBuilder; /** * Create mock row for analysis * @internal - Also used by DbEntityTable.props() to avoid code duplication */ _createMockRow(): any; /** * Add a LEFT JOIN to the query with a selector (supports both tables and subqueries) * UnwrapSelection extracts the value types from SqlFragment expressions */ leftJoin(rightTable: { _getSchema: () => TableSchema; } | Subquery, condition: (left: TRow, right: TRight) => Condition, selector: (left: TRow, right: TRight) => TSelection, alias?: string): SelectQueryBuilder>; /** * Add an INNER JOIN to the query with a selector (supports both tables and subqueries) * UnwrapSelection extracts the value types from SqlFragment expressions */ innerJoin(rightTable: { _getSchema: () => TableSchema; } | Subquery, condition: (left: TRow, right: TRight) => Condition, selector: (left: TRow, right: TRight) => TSelection, alias?: string): SelectQueryBuilder>; /** * Create mock row for a specific table/alias (for joins) */ private createMockRowForTable; /** * Limit results */ limit(count: number): this; /** * Offset results */ offset(count: number): this; /** * Order by field(s) * @example * .orderBy(p => p.colName) * .orderBy(p => [p.colName, p.otherCol]) * .orderBy(p => [[p.colName, 'ASC'], [p.otherCol, 'DESC']]) */ orderBy(selector: (row: TRow) => T): this; orderBy(selector: (row: TRow) => T[]): this; orderBy(selector: (row: TRow) => Array<[T, OrderDirection]>): this; } /** * Select query builder with nested collection support */ export declare class SelectQueryBuilder { /** @internal Chain identity — see chainIdSeq. */ chainId: number; private schema; private client; private selector; private whereCond?; private limitValue?; private offsetValue?; private orderByFields; private executor?; private manualJoins; private joinCounter; private isDistinct; private _includeCountOver; private schemaRegistry?; private ctes; private collectionStrategy?; /** * Get qualified table name with schema prefix if specified */ private getQualifiedTableName; constructor(schema: TableSchema, client: DatabaseClient, selector: (row: any) => TSelection, whereCond?: Condition, limit?: number, offset?: number, orderBy?: Array<{ field: string; direction: 'ASC' | 'DESC'; }>, executor?: QueryExecutor, manualJoins?: ManualJoinDefinition[], joinCounter?: number, isDistinct?: boolean, schemaRegistry?: Map, ctes?: DbCte[], collectionStrategy?: CollectionStrategyType, chainId?: number); /** * Override the timeout for this single query (in milliseconds). Only this query * is wrapped (`SET LOCAL statement_timeout` inside a short transaction). * Overrides the connection-level default; pass `0` to disable. On timeout a * `QueryTimeoutError` is thrown. */ withTimeout(timeoutMs: number): this; /** * Mark this query as expected to finish within `expectedMs` (ms). If it runs * longer, the context's `onQueryTakingTooLong` callback fires — the query is * NOT cancelled (use `.withTimeout()` for that). Overrides the context's * `longRunningQueryThreshold` for this query. */ expectedExecutionTime(expectedMs: number): this; /** * Transform the selection with a new selector * UnwrapSelection extracts the value types from SqlFragment expressions */ select(selector: (row: TSelection) => TNewSelection): SelectQueryBuilder>; /** * Add WHERE condition * Multiple where() calls are chained with AND logic * Note: The row parameter represents the selected shape (after select()) */ /** * INNER JOIN used purely as a row FILTER — the selection shape is preserved * (unlike innerJoin, no selector is required and the builder stays chainable * as the same entity/selection type). The ON condition receives (left, right) * mocks; an optional third callback contributes an extra WHERE predicate with * the same (left, right) arguments. * * Intended for scope-style predicates that hop across an N:1 FK (e.g. * order_item → invoicing_partner_data): a JOIN against the "one" side never * duplicates left rows. Joining a 1:N side WILL duplicate rows — callers own * that trade-off (use exists()/inSubquery for semi-join semantics instead). */ joinFilter(rightTable: { _getSchema: () => TableSchema; }, on: (left: any, right: TRight) => Condition, filter?: (left: any, right: TRight) => Condition): this; /** * LEFT JOIN used purely as a row FILTER — see joinFilter. Useful with an * IS NULL predicate in the filter callback for anti-join shapes * ("rows with NO matching right row"). */ leftJoinFilter(rightTable: { _getSchema: () => TableSchema; }, on: (left: any, right: TRight) => Condition, filter?: (left: any, right: TRight) => Condition): this; private addFilterJoin; where(condition: (row: any) => Condition): this; /** * Attach one or more CTEs to this query * * @example * const result = await db.users * .where(u => eq(u.id, 1)) * .with(activeUsersCte.cte) * .leftJoin(activeUsersCte.cte, ...) * .toList(); */ with(...ctes: DbCte[]): this; /** * Limit results */ limit(count: number): this; /** * Offset results */ offset(count: number): this; /** * Order by field(s) * @example * .orderBy(p => p.colName) * .orderBy(p => [p.colName, p.otherCol]) * .orderBy(p => [[p.colName, 'ASC'], [p.otherCol, 'DESC']]) */ orderBy(selector: (row: TSelection) => T): this; orderBy(selector: (row: TSelection) => T[]): this; orderBy(selector: (row: TSelection) => Array<[T, OrderDirection]>): this; /** * Group by fields - returns a GroupedQueryBuilder for type-safe aggregations * @param selector Function that selects the grouping key from the current selection * @example * db.users * .select(u => ({ id: u.id, street: u.address.street, name: u.name })) * .groupBy(p => ({ street: p.street })) * .select(g => ({ street: g.key.street, count: g.count() })) */ groupBy(selector: (row: TSelection) => TGroupingKey): GroupedQueryBuilder; /** * Add a LEFT JOIN with a subquery * @param subquery The subquery to join (must be 'table' mode) * @param alias Alias for the subquery in the FROM clause * @param condition Join condition * @param selector Result selector */ leftJoinSubquery(subquery: Subquery, alias: string, condition: (left: TSelection, right: TSubqueryResult) => Condition, selector: (left: TSelection, right: TSubqueryResult) => TNewSelection): SelectQueryBuilder>; /** * Add a LEFT JOIN to the query with a selector * Note: After select(), the left parameter in the join will be the selected shape (TSelection) * UnwrapSelection extracts the value types from SqlFragment expressions */ leftJoin, TNewSelection>(rightTable: DbCte, condition: (left: TSelection, right: TRight) => Condition, selector: (left: TSelection, right: TRight) => TNewSelection): SelectQueryBuilder>; leftJoin, TNewSelection>(rightTable: Subquery, condition: (left: TSelection, right: TRight) => Condition, selector: (left: TSelection, right: TRight) => TNewSelection, alias: string): SelectQueryBuilder>; leftJoin, TNewSelection>(rightTable: { _getSchema: () => TableSchema; }, condition: (left: TSelection, right: TRight) => Condition, selector: (left: TSelection, right: TRight) => TNewSelection, alias?: string): SelectQueryBuilder>; /** * Add a LEFT JOIN with a CTE */ private leftJoinCte; /** * Add an INNER JOIN with a subquery * @param subquery The subquery to join (must be 'table' mode) * @param alias Alias for the subquery in the FROM clause * @param condition Join condition * @param selector Result selector */ innerJoinSubquery(subquery: Subquery, alias: string, condition: (left: TSelection, right: TSubqueryResult) => Condition, selector: (left: TSelection, right: TSubqueryResult) => TNewSelection): SelectQueryBuilder>; /** * Add an INNER JOIN to the query with a selector * Note: After select(), the left parameter in the join will be the selected shape (TSelection) * UnwrapSelection extracts the value types from SqlFragment expressions */ innerJoin(rightTable: { _getSchema: () => TableSchema; } | Subquery, condition: (left: TSelection, right: TRight) => Condition, selector: (left: TSelection, right: TRight) => TNewSelection, alias?: string): SelectQueryBuilder>; /** * Create mock row for a specific table/alias (for joins) */ private createMockRowForTable; /** * Create mock row for a subquery result (for subquery joins) * The subquery result type defines the shape - we create FieldRefs for each property */ private createMockRowForSubquery; /** * Create a mock row for CTE columns */ private createMockRowForCte; /** * Select distinct rows */ selectDistinct(selector: (row: TSelection) => TNewSelection): SelectQueryBuilder; /** * Get minimum value from the query */ min(selector?: (row: TSelection) => TResult): Promise; /** * Get maximum value from the query */ max(selector?: (row: TSelection) => TResult): Promise; /** * Get sum of values from the query */ sum(selector?: (row: TSelection) => TResult): Promise; /** * Get count of rows from the query */ count(): Promise; /** * Execute query and return results with total count using COUNT(*) OVER(). * Useful for pagination - gets data and total count in a single query. */ countOver(): Promise<{ data: ResolveCollectionResults[]; totalCount: number; }>; /** * Check if any rows match the query * More efficient than count() > 0 as it can stop after finding one row */ exists(): Promise; /** * Combine this query with another using UNION (removes duplicates) * * @param query Another SelectQueryBuilder with compatible selection type * @returns A UnionQueryBuilder for further chaining * * @example * ```typescript * const result = await db.users * .select(u => ({ id: u.id, name: u.name })) * .union(db.customers.select(c => ({ id: c.id, name: c.name }))) * .orderBy(r => r.name) * .toList(); * ``` */ union(query: SelectQueryBuilder): UnionQueryBuilder; /** * Combine this query with another using UNION ALL (keeps all rows including duplicates) * * @param query Another SelectQueryBuilder with compatible selection type * @returns A UnionQueryBuilder for further chaining * * @example * ```typescript * // UNION ALL is faster than UNION as it doesn't need to remove duplicates * const allLogs = await db.errorLogs * .select(l => ({ timestamp: l.createdAt, message: l.message })) * .unionAll(db.infoLogs.select(l => ({ timestamp: l.createdAt, message: l.message }))) * .orderBy(r => r.timestamp) * .toList(); * ``` */ unionAll(query: SelectQueryBuilder): UnionQueryBuilder; /** * Build SQL for use in UNION queries (without ORDER BY, LIMIT, OFFSET) * @internal Used by UnionQueryBuilder */ buildUnionSql(context: SqlBuildContext): string; /** * Metadata captured by the most recent `buildUnionSql` invocation. Used by * `UnionQueryBuilder.toList()` to drive `reconstructNestedObjects` and * `transformResults` on the union result. Cleared after each consume. * @internal */ private _lastUnionMetadata?; /** * Read (and consume) the metadata captured by the most recent * `buildUnionSql` call. Internal contract with `UnionQueryBuilder`. * @internal */ _consumeUnionMetadata(): { nestedPaths: Set; selectionResult: any; } | undefined; /** * Apply the standard post-fetch result transformation to the raw rows * returned by a UNION execution. Combines `reconstructNestedObjects` (for * `__nested__` columns produced by nested-object projections) with * `transformResults` (for collection projections, scalar mappers, FieldRef * mappers, etc.). Used exclusively by `UnionQueryBuilder.toList()`. * @internal */ _applyUnionPostProcessing(rows: any[], meta: { nestedPaths: Set; selectionResult: any; }): TSelection[]; /** * Create a future query that will be executed later. * Use with FutureQueryRunner.runAsync() for batch execution. * * @returns A FutureQuery that can be executed individually or in a batch * * @example * ```typescript * const q1 = db.users.select(u => ({ id: u.id, name: u.username })).future(); * const q2 = db.posts.select(p => ({ title: p.title })).future(); * * // Execute in a single roundtrip * const [users, posts] = await FutureQueryRunner.runAsync([q1, q2]); * ``` */ future(): FutureQuery>; /** * Create a future query that returns a single result or null. * Use with FutureQueryRunner.runAsync() for batch execution. * * @returns A FutureSingleQuery that resolves to a single result or null * * @example * ```typescript * const q1 = db.users.where(u => eq(u.id, 1)).select(u => u).futureFirstOrDefault(); * const q2 = db.posts.where(p => eq(p.id, 5)).select(p => p).futureFirstOrDefault(); * * const [user, post] = await FutureQueryRunner.runAsync([q1, q2]); * // user: User | null * // post: Post | null * ``` */ futureFirstOrDefault(): FutureSingleQuery>; /** * Create a future query that returns a count. * Use with FutureQueryRunner.runAsync() for batch execution. * * @returns A FutureCountQuery that resolves to a number * * @example * ```typescript * const q1 = db.users.futureCount(); * const q2 = db.posts.futureCount(); * const q3 = db.comments.where(c => eq(c.isPublished, true)).futureCount(); * * const [userCount, postCount, commentCount] = await FutureQueryRunner.runAsync([q1, q2, q3]); * ``` */ futureCount(): FutureCountQuery; /** * Execute query and return results as array * Collection results are automatically resolved to arrays */ toList(): Promise[]>; /** * Execute query using single-phase approach (JSONB/CTE strategy) */ private executeSinglePhase; /** * Execute query using two-phase approach (temp table strategy) */ private executeWithTempTables; /** * Execute using fully optimized single-query approach (PostgresClient only) * Combines base query + all collections into ONE multi-statement query */ /** * Whether a collection can be served by the naive per-collection SQL emitted * in {@link executeFullyOptimized} (`SELECT fk AS parent_id, * FROM target WHERE fk IN (SELECT __pk_id FROM tmp_base)`). That SQL knows * nothing about collection filters, pagination, aggregations, constant-FK * predicates, navigation correlation, or mapper transforms — so any of those * features disqualifies the fast path and the query falls back to the * two-phase execution whose strategy builders implement them correctly. */ private isNaiveCollectionFastPathSafe; private executeFullyOptimized; /** * Detect collections in the selection result, including nested objects. * Returns collections with path information for proper result reconstruction. */ private detectCollections; /** * Recursively detect collections in nested objects */ private detectCollectionsRecursive; /** * Build base selection excluding collections but including necessary foreign keys. * Handles nested collections by removing them from nested structures. */ private buildBaseSelection; /** * Recursively build base selection for nested objects, excluding collections */ private buildBaseSelectionRecursive; /** * Build collection aggregation config from CollectionQueryBuilder */ private buildCollectionConfig; /** * Extract array field from collection builder for array aggregations */ private extractArrayField; /** * Get default value string for aggregation type */ private getDefaultValueString; /** * Get default value for collection based on aggregation type */ private getDefaultValueForCollection; /** * Execute query and return first result or null */ first(): Promise | null>; /** * Execute query and return first result or null (alias for first) */ firstOrDefault(): Promise | null>; /** * Execute query and return first result or throw */ firstOrThrow(): Promise>; /** * Create a prepared query for efficient reusable parameterized execution. * * Prepared queries build the SQL once and allow multiple executions * with different parameter values. This is useful for: * 1. Query building optimization - Build SQL once, execute many times * 2. Type-safe placeholders - Named parameters with validation * 3. Developer ergonomics - Cleaner API for reusable queries * * @param name - A name for the prepared query (for debugging) * @returns PreparedQuery that can be executed multiple times with different parameters * * @example * ```typescript * // Create a prepared query with a placeholder * const getUserById = db.users * .where(u => eq(u.id, sql.placeholder('userId'))) * .prepare('getUserById'); * * // Execute multiple times with different values * const user1 = await getUserById.execute({ userId: 10 }); * const user2 = await getUserById.execute({ userId: 20 }); * ``` * * @example * ```typescript * // Multiple placeholders * const searchUsers = db.users * .where(u => and( * gt(u.age, sql.placeholder('minAge')), * like(u.name, sql.placeholder('namePattern')) * )) * .prepare('searchUsers'); * * await searchUsers.execute({ minAge: 18, namePattern: '%john%' }); * ``` */ prepare = Record>(name: string): PreparedQuery, TParams>; /** * Delete records matching the current WHERE condition * Returns a fluent builder that can be awaited directly or chained with .returning() * * @example * ```typescript * // No returning (default) - returns void * await db.users.where(u => eq(u.id, 1)).delete(); * * // With returning() - returns full entities * const deleted = await db.users.where(u => eq(u.id, 1)).delete().returning(); * * // With returning(selector) - returns selected columns * const results = await db.users.where(u => eq(u.isActive, false)).delete() * .returning(u => ({ id: u.id, username: u.username })); * ``` */ delete(): FluentDelete; /** * Update records matching the current WHERE condition * Returns a fluent builder that can be awaited directly or chained with .returning() * * @param data Partial data to update * * @example * ```typescript * // No returning (default) - returns void * await db.users.where(u => eq(u.id, 1)).update({ age: 30 }); * * // With returning() - returns full entities * const updated = await db.users.where(u => eq(u.id, 1)).update({ age: 30 }).returning(); * * // With returning(selector) - returns selected columns * const results = await db.users.where(u => eq(u.isActive, true)).update({ lastLogin: new Date() }) * .returning(u => ({ id: u.id, lastLogin: u.lastLogin })); * ``` */ update(data: Partial> | ((row: TSelection) => Partial>)): FluentQueryUpdate; /** * Build RETURNING clause for delete/update operations * @param returning - The returning configuration * @param qualifyWithTable - If true, qualify column names with the main table name (needed for DELETE with USING) * @internal */ private buildUpdateDeleteReturningClause; /** * Map row results for delete/update RETURNING clause * @internal */ private mapDeleteReturningResults; /** * Detect if a RETURNING selector uses navigation properties * Returns navigation info if found, null otherwise * @internal */ private detectNavigationInReturning; /** * Build RETURNING clause with navigation property support using CTE * @internal */ private buildReturningWithNavigation; /** * Rewrite table references in a collection subquery to use the correct aliases * from the main query's JOINs. This handles multi-level navigation where the * collection is accessed through intermediate joined tables. * * @param expression - The SQL expression (join clause or select expression) to rewrite * @param tableToAlias - Map of table names to their aliases in the main query * @returns The rewritten expression with all table references updated * @internal */ private rewriteCollectionTableReferences; /** * Map RETURNING results with navigation properties * Applies type mappers based on source table schemas * Reconstructs nested objects from flat column paths * @internal */ private mapReturningResultsWithNavigation; /** * Create mock row for analysis * @internal */ _createMockRow(): any; /** * Create a proxy that wraps selected values and returns FieldRefs for property access * This enables orderBy and other operations to work with chained selects * @param preserveOriginal - If true (for WHERE), preserve original column names; if false (for ORDER BY), use alias names */ private createFieldRefProxy; /** * Detect navigation property references in selection and add necessary JOINs * Supports multi-level navigation like task.level.createdBy */ /** * Try to build flat SQL SELECT parts for a nested object containing FieldRefs. * Uses path-encoded aliases (e.g., __nested__address__street) for JS-side reconstruction. * This is more performant than json_build_object() as it avoids JSON serialization overhead. * * @param obj The nested object from the selector * @param context Query context for parameter tracking * @param joins Array to add necessary JOINs * @param selectParts Array to add SELECT parts to * @param pathPrefix Current path prefix for alias encoding (e.g., "__nested__address") * @param nestedPaths Set to track all nested paths for reconstruction * @returns true if handled as nested object, false if not a valid nested object */ private tryBuildFlatNestedSelect; /** * Reconstruct nested objects from flat row data with path-encoded column names. * Transforms { "__nested__address__street": "Main St", "__nested__address__city": "NYC" } * into { address: { street: "Main St", city: "NYC" } } * Also handles nested collections with paths like "__nested__content__posts" * Converts numeric strings to numbers for scalar aggregation results. */ private reconstructNestedObjects; /** * Check if an object contains any CollectionQueryBuilder instances (recursively) */ private hasNestedCollections; /** * Build flat SQL SELECT parts for a nested object, excluding collections. * Collections are added to collectionFields for separate handling. */ private tryBuildFlatNestedSelectExcludingCollections; private detectAndAddJoinsFromSelection; /** * Collect all table aliases from a selection */ private collectTableAliasesFromSelection; /** * Resolve all navigation joins by finding the correct path through the schema graph * This handles multi-level navigation like task.level.createdBy */ private resolveJoinsForTableAliases; /** * Add a JOIN for a FieldRef if it references a related table * @deprecated Use detectAndAddJoinsFromSelection with multi-level resolution instead */ private addJoinForFieldRef; /** * Detect navigation property references in a WHERE condition and add necessary JOINs */ private detectAndAddJoinsFromCondition; /** * Build SQL query */ private buildQuery; /** * Build the core SQL query, optionally excluding ORDER BY, LIMIT, and OFFSET * Used by buildUnionSql to build component queries for UNION * @internal */ private buildQueryCore; /** * Transform database results */ private transformResults; /** * Convert database values: null to undefined, numeric strings to numbers */ private convertValue; /** * Transform collection items applying fromDriver mappers */ private transformCollectionItems; /** * Transform a nested collection value (from firstOrDefault or toList inside another collection) * Applies custom mappers to fields within the nested collection result. */ private transformNestedCollectionValue; /** * Transform CTE aggregation items applying fromDriver mappers from selection metadata */ private transformCteAggregationItems; /** * Build aggregation query (MIN, MAX, SUM) */ private buildAggregationQuery; /** * Build aggregate query (count or exists) */ private buildAggregateQuery; /** * Convert this query to a subquery that can be used in WHERE, SELECT, JOIN, or FROM clauses * * @template TMode - 'scalar' for single value, 'array' for column list, 'table' for full rows * @returns Subquery that maintains type safety * * @example * // Scalar subquery (returns single value) * const avgAge = db.users.select(u => u.age).asSubquery('scalar'); * * // Array subquery (returns list of values for IN clause) * const activeUserIds = db.users * .where(u => eq(u.isActive, true)) * .select(u => u.id) * .asSubquery('array'); * * // Table subquery (returns rows for FROM or JOIN) * const activeUsers = db.users * .where(u => eq(u.isActive, true)) * .select(u => ({ id: u.id, name: u.username })) * .asSubquery('table'); */ asSubquery(mode?: TMode): Subquery : TMode extends 'array' ? ResolveFieldRefs[] : ResolveCollectionResults, TMode>; /** * Extract field refs from the WHERE condition that reference outer queries. * These are field refs with a __tableAlias that doesn't match this query's schema. */ private extractOuterFieldRefs; } /** * Marker interface for collection results - signals that this will be an array at runtime */ export interface CollectionResult { readonly __collectionResult: true; readonly __itemType: TItem; } /** * Type helper to detect if a type is a class instance (has prototype methods) * vs a plain data object. See conditions.ts for detailed explanation. * Excludes DbColumn and SqlFragment which have valueOf but are not value types. */ type IsClassInstance = T extends { __isDbColumn: true; } ? false : T extends SqlFragment ? false : T extends { valueOf(): infer V; } ? V extends T ? true : V extends number | string | boolean | bigint | symbol ? true : false : false; /** * Check for types with known class method signatures */ type HasClassMethods = T extends { getTime(): number; } ? true : T extends { size: number; has(value: any): boolean; } ? true : T extends { byteLength: number; } ? true : T extends { then(onfulfilled?: any): any; } ? true : T extends { message: string; name: string; } ? true : T extends { exec(string: string): any; } ? true : false; /** * Combined check for value types that should not be recursively processed */ type IsValueType = IsClassInstance extends true ? true : HasClassMethods extends true ? true : false; /** * Type helper to resolve all FieldRef and SqlFragment types to their value types * Recursively processes nested objects and arrays * Preserves class instances (Date, Map, Set, Temporal, etc.) as-is */ export type ResolveFieldRefs = T extends FieldRef ? V : T extends SqlFragment ? V : T extends CollectionResult ? T : T extends Array ? Array> : T extends (...args: any[]) => any ? T : T extends object ? IsValueType extends true ? T : { [K in keyof T]: ResolveFieldRefs; } : T; /** * Type helper to resolve collection results to arrays * Transforms CollectionResult to T[] and resolves FieldRef to their value types */ export type ResolveCollectionResults = { [K in keyof T]: T[K] extends CollectionResult ? ResolveFieldRefs[] : ResolveFieldRefs; }; /** * Reference query builder for single navigation (many-to-one, one-to-one) */ export declare class ReferenceQueryBuilder { private relationName; private targetTable; private targetTableSchema?; private foreignKeys; private matches; private isMandatory; private schemaRegistry?; private navigationPath; private sourceAlias; constructor(relationName: string, targetTable: string, foreignKeys: string[], matches: string[], isMandatory: boolean, targetTableSchema?: TableSchema, schemaRegistry?: Map, navigationPath?: NavigationJoin[], sourceAlias?: string); /** * Get the alias to use for this reference in the query */ getAlias(): string; /** * Get target table name */ getTargetTable(): string; /** * Get foreign keys */ getForeignKeys(): string[]; /** * Get matches */ getMatches(): string[]; /** * Is this a mandatory relation (INNER JOIN vs LEFT JOIN) */ getIsMandatory(): boolean; /** * Get target table schema */ getTargetTableSchema(): TableSchema | undefined; /** * Create a mock object that exposes the target table's columns * This allows accessing related fields like: p.user.username */ createMockTargetRow(): any; } /** * Collection query builder for nested queries */ export declare class CollectionQueryBuilder { private relationName; private targetTable; private targetTableSchema?; private foreignKey; private foreignKeys; private matches; private sourceTable; private selector?; private whereCond?; private limitValue?; private offsetValue?; private orderByFields; private asName?; private isMarkedAsList; private isDistinct; private aggregationType?; private flattenResultType?; private schemaRegistry?; private navigationPath; private selectManyJoins; private foreignKeyTableAlias?; private _cachedMockItem?; private _selectedFieldConfigs?; constructor(relationName: string, targetTable: string, foreignKey: string, sourceTable: string, targetTableSchema?: TableSchema, schemaRegistry?: Map, navigationPath?: NavigationJoin[], foreignKeys?: string[], matches?: string[]); /** * Select specific fields from collection items */ select(selector: (item: TItem) => TSelection): CollectionQueryBuilder; /** * Select distinct fields from collection items */ selectDistinct(selector: (item: TItem) => TSelection): CollectionQueryBuilder; /** * Filter collection items * Multiple where() calls are chained with AND logic */ where(condition: (item: TItem) => Condition): this; /** * Create a mock item for the target table with proper typing */ private createMockItem; /** * Limit collection items */ limit(count: number): this; /** * Offset collection items */ offset(count: number): this; /** * Order collection items * @example * .orderBy(p => p.colName) * .orderBy(p => [p.colName, p.otherCol]) * .orderBy(p => [[p.colName, 'ASC'], [p.otherCol, 'DESC']]) */ orderBy(selector: (item: TItem) => T): this; orderBy(selector: (item: TItem) => T[]): this; orderBy(selector: (item: TItem) => Array<[T, OrderDirection]>): this; /** * Get minimum value (supports magic SQL in selector) */ /** * Get minimum value (supports magic SQL in selector) * Returns SqlFragment for automatic type resolution in selectors */ min(selector?: (item: TItem) => TSelection): SqlFragment; /** * Get maximum value (supports magic SQL in selector) * Returns SqlFragment for automatic type resolution in selectors */ max(selector?: (item: TItem) => TSelection): SqlFragment; /** * Get sum value (supports magic SQL in selector) * Returns SqlFragment for automatic type resolution in selectors */ sum(selector?: (item: TItem) => TSelection): SqlFragment; /** * Get count of items * Returns SqlFragment for automatic type resolution in selectors */ count(): SqlFragment; /** * Check if any items exist in the collection * Returns SqlFragment for automatic type resolution in selectors */ exists(): SqlFragment; /** * Flatten a nested collection through this collection. * Similar to LINQ's SelectMany - projects each item to a collection and flattens. * * Example: product.productPrices!.selectMany(pp => pp.capacityGroups!).exists() * SQL: SELECT EXISTS(SELECT 1 FROM capacity_groups JOIN product_prices ON ... WHERE ...) */ selectMany(selector: (item: TItem) => CollectionQueryBuilder): CollectionQueryBuilder; /** * Flatten result to number array (for single-column selections) */ toNumberList(name?: string): CollectionResult; /** * Flatten result to string array (for single-column selections) */ toStringList(name?: string): CollectionResult; /** * Specify the property name for the collection in the result * Marks this collection to be resolved as an array in the final result */ toList(name?: string): CollectionResult; /** * Get first item from collection or null if empty * Automatically applies LIMIT 1 and returns a single item instead of array */ firstOrDefault(name?: string): CollectionResult; /** * Get target table schema */ getTargetTableSchema(): TableSchema | undefined; /** * Get target table name */ getTargetTable(): string; /** * Get selected field configs (for mapper lookup during transformation) */ getSelectedFieldConfigs(): SelectedField[] | undefined; /** * Get schema registry (for mapper lookup during transformation of navigation fields) */ getSchemaRegistry(): Map | undefined; /** * Get the navigation path leading to this collection. * Non-empty when this collection was reached through a reference chain * (e.g. `cdc.discountCode!.discount!.discountProducts`). The outer collection * builder needs these joins emitted in its own FROM so the nested aggregation's * correlation key (e.g. `discount.id`) resolves. */ getNavigationPath(): NavigationJoin[]; /** * Check if this collection uses array aggregation (for flattened results) */ isArrayAggregation(): boolean; /** * Check if this is a scalar aggregation (count, sum, max, min) */ isScalarAggregation(): boolean; /** * Check if this is a single item result (firstOrDefault) */ isSingleResult(): boolean; /** * Get the aggregation type */ getAggregationType(): 'MIN' | 'MAX' | 'SUM' | 'COUNT' | 'EXISTS' | undefined; /** * Get the flatten result type (for determining PostgreSQL array type) */ getFlattenResultType(): 'number' | 'string' | undefined; /** * Get field references from this condition. * Returns empty since EXISTS subqueries are self-contained correlated subqueries. * Required for duck-typing compatibility with Condition interface when used in WHERE clauses. */ getFieldRefs(): FieldRef[]; /** * Build SQL for this collection as a correlated EXISTS subquery. * Produces: EXISTS (SELECT 1 FROM "table" [JOINs] WHERE correlation AND conditions) * Required for duck-typing compatibility with Condition interface when used in WHERE clauses. */ buildSql(context: SqlBuildContext): string; /** * Detect navigation property references in the selected fields and add necessary JOINs * This supports multi-level navigation like p.task.level.createdBy.username */ private detectNavigationJoins; /** * Add a navigation JOIN for a FieldRef if it references a related table * Handles multi-level navigation by recursively resolving the join chain */ private addNavigationJoinForFieldRef; /** * Add a navigation join and return the target schema */ private addNavigationJoin; /** * Resolve all navigation joins by finding the correct path through the schema graph * This handles multi-level navigation like task.level.createdBy */ private resolveNavigationJoins; /** * Find a path from already-joined schemas to the target alias * Uses BFS to find the shortest path through the schema graph */ private findNavigationPath; /** * Build CTE for this collection query * Now delegates to collection strategy pattern * Returns full CollectionAggregationResult for strategies that need special handling (like LATERAL) */ buildCTE(context: QueryContext, client?: DatabaseClient, parentIds?: any[]): { sql: string; params: any[]; isCTE?: boolean; joinClause?: string; selectExpression?: string; tableName?: string; }; } export {}; //# sourceMappingURL=query-builder.d.ts.map