import { DatabaseClient } from '../database/database-client.interface'; import { SelectQueryBuilder, ResolveCollectionResults } from './query-builder'; import { FieldRef, UnwrapSelection } from './conditions'; /** * Interface for queries that can be used in CTEs * Supports SelectQueryBuilder, EntitySelectQueryBuilder, GroupedJoinedQueryBuilder * The TSelection type is inferred from the query's type parameter */ interface CteCompatibleQuery { toList: () => Promise[] | TSelection[]>; } /** * Type helper to extract the underlying value type from a FieldRef or keep as-is */ type ExtractValueType = T extends FieldRef ? V : T; /** * Reference to a CTE for use inside sql`` template literals, created via DbCte.as(). * Interpolating the ref itself renders the FROM-clause identifier * (`"cte_name" AS "alias"`); interpolating a column property renders a * qualified column identifier (`"alias"."column"`). */ export type CteTableRef = { readonly __isCteTableRef: true; readonly __cteName: string; readonly __tableAlias: string; } & { [K in keyof TColumns & string]: FieldRef>; }; /** * Represents a Common Table Expression (CTE) with strong typing */ export declare class DbCte { readonly name: string; readonly query: string; readonly params: unknown[]; readonly columnDefs: TColumns; readonly selectionMetadata?: Record | undefined; /** * Set of column names that contain aggregated JSONB arrays. * These columns need COALESCE(..., '[]'::jsonb) when used in LEFT JOINs. */ readonly aggregationColumns: Set; constructor(name: string, query: string, params: unknown[], columnDefs: TColumns, selectionMetadata?: Record | undefined, aggregationColumns?: string[]); /** * Get a typed reference to a CTE column */ getColumn(columnName: K): TColumns[K]; /** * Create a typed reference to this CTE for use inside sql`` template literals, * mirroring SQL's `FROM cte AS alias`. Column properties render as qualified * identifiers, the ref itself renders as the FROM-clause table reference — * no hand-written string identifiers needed: * * @example * const stats = cteBuilder.with('post_stats', db.posts.select(p => ({ * postId: p.id, * views: p.views, * authorId: p.userId, * }))); * const ps = stats.cte.as('ps'); * * sql`(SELECT COALESCE(SUM(${ps.views}), 0) FROM ${ps} WHERE ${ps.authorId} = ${u.id})` * // -> (SELECT COALESCE(SUM("ps"."views"), 0) FROM "post_stats" AS "ps" WHERE "ps"."authorId" = "users"."id") * * Without an alias, columns are qualified by the CTE name and the table ref * renders as just `"post_stats"`. */ as(alias?: string): CteTableRef; /** * Check if a column is an aggregation column (JSONB array) */ isAggregationColumn(columnName: string): boolean; } /** * Builder for creating Common Table Expressions (CTEs) */ export declare class DbCteBuilder { private client?; private ctes; private paramOffset; /** * @param client - Optional. When provided, CTE bodies are built with the * driver's capabilities in mind — notably `supportsBinaryArrayResults()`: * array-producing aggregations (toNumberList/toStringList) inside CTE * definitions emit json_agg for drivers that cannot decode native arrays * (BunClient in binary mode). Without a client, CTE SQL is built * driver-agnostically (array_agg), matching previous behavior. */ constructor(client?: DatabaseClient | undefined); /** * Create a regular CTE from a query * * @example * const activeUsersCte = cteBuilder.with( * 'active_users', * db.users * .where(u => lt(u.id, 100)) * .select(u => ({ * userId: u.id, * createdAt: u.createdAt, * postCount: u.posts.count() * })) * ); */ with>(cteName: string, query: SelectQueryBuilder | { toList: () => Promise; }): { cte: DbCte; }; /** * Create an aggregation CTE that groups results into a JSONB array * * @example * const aggregatedCte = cteBuilder.withAggregation( * 'aggregated_users', * db.userAddress.select(ua => ({ * id: ua.id, * userId: ua.userId, * street: ua.address * })), * ua => ({ userId: ua.userId }), * 'items' * ); */ withAggregation, TKey extends Record, TAlias extends string = 'items'>(cteName: string, query: SelectQueryBuilder | CteCompatibleQuery, keySelector: (value: TSelection) => TKey, aggregationAlias?: TAlias): DbCte & { [K in TAlias]: Array>; }>; /** * Build inner query SQL with metadata - handles different query builder types * Returns both the SQL and the selection metadata for mapper preservation */ private buildInnerQuerySqlWithMetadata; /** * Build inner query SQL - handles different query builder types * - SelectQueryBuilder: uses _createMockRow() and selector() * - GroupedSelectQueryBuilder: uses buildCteQuery() * - GroupedJoinedQueryBuilder: uses buildCteQuery() * * This also extracts any CTEs referenced by the inner query and adds them to this builder * to avoid duplicate CTE definitions in nested queries. */ private buildInnerQuerySql; /** * Get all CTEs created by this builder */ getCtes(): DbCte[]; /** * Clear all CTEs from this builder */ clear(): void; /** * Infer column types from query selection */ private inferColumnTypes; /** * Create a mock item for extracting group by columns */ private createMockItem; } /** * Type helper to extract CTE column types */ export type InferCteColumns = T extends DbCte ? TColumns : never; /** * Type helper for aggregated items - removes the grouping keys from the selection * and unwraps DbColumn/SqlFragment types to their underlying values */ export type AggregatedItemType, TKey extends Record> = { [K in Exclude]: UnwrapSelection; }; /** * Check if a value is a CTE */ export declare function isCte(value: any): value is DbCte; export {}; //# sourceMappingURL=cte-builder.d.ts.map