UNPKG

rawsql-ts

Version:

[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

425 lines (424 loc) 18.7 kB
import { SqlComponent } from "./SqlComponent"; import { ForClause, FromClause, GroupByClause, HavingClause, LimitClause, OrderByClause, SelectClause, SourceExpression, WhereClause, WindowsClause as WindowClause, WithClause, CommonTable, OffsetClause, FetchClause } from "./Clause"; import { ValueComponent, SqlParameterValue } from "./ValueComponent"; import { BinarySelectQuery } from "./BinarySelectQuery"; import type { SelectQuery, CTEOptions, CTEManagement, InsertQueryConversionOptions, UpdateQueryConversionOptions, DeleteQueryConversionOptions, MergeQueryConversionOptions } from "./SelectQuery"; import { TableColumnResolver } from "../transformers/TableColumnResolver"; import type { InsertQuery } from "./InsertQuery"; import type { UpdateQuery } from "./UpdateQuery"; import type { DeleteQuery } from "./DeleteQuery"; import type { MergeQuery } from "./MergeQuery"; /** * Represents a single SELECT statement with full clause support (WITH, JOIN, GROUP BY, etc.). * Provides the fluent CTE management API used throughout packages/core/tests/models/SelectQuery.cte-management.test.ts. * * @example * ```typescript * const query = SelectQueryParser.parse('SELECT id, email FROM users').toSimpleQuery(); * const active = SelectQueryParser.parse('SELECT id FROM users WHERE active = true'); * * query * .addCTE('active_users', active) * .toUnionAll(SelectQueryParser.parse('SELECT id, email FROM legacy_users')); * ``` */ export declare class SimpleSelectQuery extends SqlComponent implements SelectQuery, CTEManagement { static kind: symbol; readonly __selectQueryType: 'SelectQuery'; headerComments: string[] | null; withClause: WithClause | null; selectClause: SelectClause; fromClause: FromClause | null; whereClause: WhereClause | null; groupByClause: GroupByClause | null; havingClause: HavingClause | null; orderByClause: OrderByClause | null; windowClause: WindowClause | null; limitClause: LimitClause | null; offsetClause: OffsetClause | null; fetchClause: FetchClause | null; forClause: ForClause | null; private cteNameCache; constructor(params: { selectClause: SelectClause; fromClause?: FromClause | null; whereClause?: WhereClause | null; groupByClause?: GroupByClause | null; havingClause?: HavingClause | null; orderByClause?: OrderByClause | null; windowClause?: WindowClause | null; limitClause?: LimitClause | null; offsetClause?: OffsetClause | null; fetchClause?: FetchClause | null; forClause?: ForClause | null; withClause?: WithClause | null; }); /** * Initializes the CTE name cache from existing withClause. * Called during construction and when withClause is modified externally. * @private */ private initializeCTECache; /** * Creates a new BinarySelectQuery with this query as the left side and the provided query as the right side, * using UNION as the operator. * * @param rightQuery The right side of the UNION * @returns A new BinarySelectQuery representing "this UNION rightQuery" */ toUnion(rightQuery: SelectQuery): BinarySelectQuery; /** * Creates a new BinarySelectQuery with this query as the left side and the provided query as the right side, * using UNION ALL as the operator. * * @param rightQuery The right side of the UNION ALL * @returns A new BinarySelectQuery representing "this UNION ALL rightQuery" */ toUnionAll(rightQuery: SelectQuery): BinarySelectQuery; /** * Converts this query into an INSERT statement definition. * @remarks * Calling this method may reorder the current SELECT clause to match the requested column order. */ toInsertQuery(options: InsertQueryConversionOptions): InsertQuery; /** * Converts this query into an UPDATE statement definition. * @remarks * The conversion may reorder the SELECT list so that primary keys and updated columns align with the target table. */ toUpdateQuery(options: UpdateQueryConversionOptions): UpdateQuery; /** * Converts this query into a DELETE statement definition. * @remarks * The SELECT clause may be reordered to ensure primary keys and comparison columns appear first. */ toDeleteQuery(options: DeleteQueryConversionOptions): DeleteQuery; /** * Converts this query into a MERGE statement definition. * @remarks * This method may reorder the SELECT clause to align with the specified MERGE column lists. */ toMergeQuery(options: MergeQueryConversionOptions): MergeQuery; /** * Creates a new BinarySelectQuery with this query as the left side and the provided query as the right side, * using INTERSECT as the operator. * * @param rightQuery The right side of the INTERSECT * @returns A new BinarySelectQuery representing "this INTERSECT rightQuery" */ toIntersect(rightQuery: SelectQuery): BinarySelectQuery; /** * Creates a new BinarySelectQuery with this query as the left side and the provided query as the right side, * using INTERSECT ALL as the operator. * * @param rightQuery The right side of the INTERSECT ALL * @returns A new BinarySelectQuery representing "this INTERSECT ALL rightQuery" */ toIntersectAll(rightQuery: SelectQuery): BinarySelectQuery; /** * Creates a new BinarySelectQuery with this query as the left side and the provided query as the right side, * using EXCEPT as the operator. * * @param rightQuery The right side of the EXCEPT * @returns A new BinarySelectQuery representing "this EXCEPT rightQuery" */ toExcept(rightQuery: SelectQuery): BinarySelectQuery; /** * Creates a new BinarySelectQuery with this query as the left side and the provided query as the right side, * using EXCEPT ALL as the operator. * * @param rightQuery The right side of the EXCEPT ALL * @returns A new BinarySelectQuery representing "this EXCEPT ALL rightQuery" */ toExceptAll(rightQuery: SelectQuery): BinarySelectQuery; /** * Creates a new BinarySelectQuery with this query as the left side and the provided query as the right side, * using the specified operator. * * @param operator SQL operator to use (e.g. 'union', 'union all', 'intersect', 'except') * @param rightQuery The right side of the binary operation * @returns A new BinarySelectQuery representing "this [operator] rightQuery" */ toBinaryQuery(operator: string, rightQuery: SelectQuery): BinarySelectQuery; /** * Appends a new condition to the query's WHERE clause using AND logic. * The condition is provided as a raw SQL string which is parsed internally. * * @param rawCondition Raw SQL string representing the condition (e.g. "status = 'active'") */ appendWhereRaw(rawCondition: string): void; /** * Appends a new condition to the query's WHERE clause using AND logic. * The condition is provided as a ValueComponent object. * * @param condition ValueComponent representing the condition */ appendWhere(condition: ValueComponent): void; /** * Appends a new condition to the query's HAVING clause using AND logic. * The condition is provided as a raw SQL string which is parsed internally. * * @param rawCondition Raw SQL string representing the condition (e.g. "count(*) > 5") */ appendHavingRaw(rawCondition: string): void; /** * Appends a new condition to the query's HAVING clause using AND logic. * The condition is provided as a ValueComponent object. * * @param condition ValueComponent representing the condition */ appendHaving(condition: ValueComponent): void; /** * Appends an INNER JOIN clause to the query. * @param joinSourceRawText The table source text to join (e.g., "my_table", "schema.my_table") * @param alias The alias for the joined table * @param columns The columns to use for the join condition (e.g. ["user_id"] or "user_id") */ innerJoinRaw(joinSourceRawText: string, alias: string, columns: string | string[], resolver?: TableColumnResolver | null): void; /** * Appends a LEFT JOIN clause to the query. * @param joinSourceRawText The table source text to join * @param alias The alias for the joined table * @param columns The columns to use for the join condition */ leftJoinRaw(joinSourceRawText: string, alias: string, columns: string | string[], resolver?: TableColumnResolver | null): void; /** * Appends a RIGHT JOIN clause to the query. * @param joinSourceRawText The table source text to join * @param alias The alias for the joined table * @param columns The columns to use for the join condition */ rightJoinRaw(joinSourceRawText: string, alias: string, columns: string | string[], resolver?: TableColumnResolver | null): void; /** * Appends an INNER JOIN clause to the query using a SourceExpression. * @param sourceExpr The source expression to join * @param columns The columns to use for the join condition */ innerJoin(sourceExpr: SourceExpression, columns: string | string[], resolver?: TableColumnResolver | null): void; /** * Appends a LEFT JOIN clause to the query using a SourceExpression. * @param sourceExpr The source expression to join * @param columns The columns to use for the join condition */ leftJoin(sourceExpr: SourceExpression, columns: string | string[], resolver?: TableColumnResolver | null): void; /** * Appends a RIGHT JOIN clause to the query using a SourceExpression. * @param sourceExpr The source expression to join * @param columns The columns to use for the join condition */ rightJoin(sourceExpr: SourceExpression, columns: string | string[], resolver?: TableColumnResolver | null): void; /** * Internal helper to append a JOIN clause. * Parses the table source, finds the corresponding columns in the existing query context, * and builds the JOIN condition. * @param joinType Type of join (e.g., 'inner join', 'left join') * @param joinSourceRawText Raw text for the table/source to join (e.g., "my_table", "schema.another_table") * @param alias Alias for the table/source being joined * @param columns Array or string of column names to join on */ private joinSourceRaw; /** * Internal helper to append a JOIN clause using a SourceExpression. * @param joinType Type of join (e.g., 'inner join', 'left join') * @param sourceExpr The source expression to join * @param columns Array or string of column names to join on */ private joinSource; toSource(alias: string): SourceExpression; appendWith(commonTable: CommonTable | CommonTable[]): void; /** * Appends a CommonTable (CTE) to the WITH clause from raw SQL text and alias. * If alias is provided, it will be used as the CTE name. * * @param rawText Raw SQL string representing the CTE body (e.g. '(SELECT ...)') * @param alias Optional alias for the CTE (e.g. 'cte_name') */ appendWithRaw(rawText: string, alias: string): void; /** * Overrides a select item using a template literal function. * The callback receives the SQL string of the original expression and must return a new SQL string. * The result is parsed and set as the new select item value. * * Example usage: * query.overrideSelectItemRaw("journal_date", expr => `greatest(${expr}, DATE '2025-01-01')`) * * @param columnName The name of the column to override * @param fn Callback that receives the SQL string of the original expression and returns a new SQL string */ overrideSelectItemExpr(columnName: string, fn: (expr: string) => string): void; /** * Appends a WHERE clause using the expression for the specified column. * If `options.upstream` is true, applies to all upstream queries containing the column. * If false or omitted, applies only to the current query. * * @param columnName The name of the column to target. * @param exprBuilder Function that receives the column expression as a string and returns the WHERE condition string. * @param options Optional settings. If `upstream` is true, applies to upstream queries. */ appendWhereExpr(columnName: string, exprBuilder: (expr: string) => string, options?: { upstream?: boolean; }): void; /** * Sets the value of a parameter by name in this query. * @param name Parameter name * @param value Value to set */ setParameter(name: string, value: SqlParameterValue): this; /** * Returns this SimpleSelectQuery instance (identity function). * @returns This SimpleSelectQuery instance */ toSimpleQuery(): SimpleSelectQuery; /** * Adds a CTE (Common Table Expression) to the query. * * @param name CTE name/alias (must be non-empty) * @param query SelectQuery to use as CTE * @param options Optional configuration * @param options.materialized PostgreSQL-specific: true = MATERIALIZED, false = NOT MATERIALIZED, null/undefined = no hint * * @throws {InvalidCTENameError} When name is empty or whitespace-only * @throws {DuplicateCTEError} When CTE with same name already exists * * @example * ```typescript * // Basic CTE * query.addCTE('active_users', * SelectQueryParser.parse('SELECT * FROM users WHERE active = true') * ); * * // PostgreSQL MATERIALIZED CTE (forces materialization) * query.addCTE('expensive_calc', expensiveQuery, { materialized: true }); * * // PostgreSQL NOT MATERIALIZED CTE (prevents materialization) * query.addCTE('simple_view', simpleQuery, { materialized: false }); * ``` * * @remarks * - MATERIALIZED/NOT MATERIALIZED is PostgreSQL-specific syntax * - Other databases will ignore the materialized hint * - CTE names must be unique within the query * - Method supports fluent chaining */ addCTE(name: string, query: SelectQuery, options?: CTEOptions): this; /** * Removes a CTE by name from the query. * * @param name CTE name to remove * * @throws {CTENotFoundError} When CTE with specified name doesn't exist * * @example * ```typescript * query.addCTE('temp_data', tempQuery); * query.removeCTE('temp_data'); // Removes the CTE * * // Throws CTENotFoundError * query.removeCTE('non_existent'); * ``` * * @remarks * - Throws error if CTE doesn't exist (strict mode for safety) * - Use hasCTE() to check existence before removal if needed * - Method supports fluent chaining */ removeCTE(name: string): this; /** * Checks if a CTE with the given name exists in the query. * Optimized with O(1) lookup using internal cache. * * @param name CTE name to check * @returns true if CTE exists, false otherwise * * @example * ```typescript * query.addCTE('user_stats', statsQuery); * * if (query.hasCTE('user_stats')) { * console.log('CTE exists'); * } * * query.removeCTE('user_stats'); * console.log(query.hasCTE('user_stats')); // false * ``` * * @remarks * - Performs case-sensitive name matching * - Returns false for queries without any CTEs * - Useful for conditional CTE operations * - O(1) performance using internal cache */ hasCTE(name: string): boolean; /** * Returns an array of all CTE names in the query. * * @returns Array of CTE names in the order they were defined * * @example * ```typescript * const query = SelectQueryParser.parse('SELECT * FROM data').toSimpleQuery(); * * // Empty query * console.log(query.getCTENames()); // [] * * // Add CTEs * query.addCTE('users', userQuery); * query.addCTE('orders', orderQuery); * * console.log(query.getCTENames()); // ['users', 'orders'] * * // Use for validation * const expectedCTEs = ['users', 'orders', 'products']; * const actualCTEs = query.getCTENames(); * const missingCTEs = expectedCTEs.filter(name => !actualCTEs.includes(name)); * ``` * * @remarks * - Returns empty array for queries without CTEs * - Names are returned in definition order * - Useful for debugging and validation * - Names reflect actual CTE aliases, not table references * - Performance: O(n) but avoids redundant array mapping */ getCTENames(): string[]; /** * Replaces an existing CTE or adds a new one with the given name. * * @param name CTE name to replace/add (must be non-empty) * @param query SelectQuery to use as CTE * @param options Optional configuration * @param options.materialized PostgreSQL-specific: true = MATERIALIZED, false = NOT MATERIALIZED, null/undefined = no hint * * @throws {InvalidCTENameError} When name is empty or whitespace-only * * @example * ```typescript * const query = SelectQueryParser.parse('SELECT * FROM final_data').toSimpleQuery(); * const oldQuery = SelectQueryParser.parse('SELECT id FROM old_table'); * const newQuery = SelectQueryParser.parse('SELECT id, status FROM new_table WHERE active = true'); * * // Add initial CTE * query.addCTE('data_source', oldQuery); * * // Replace with improved version * query.replaceCTE('data_source', newQuery, { materialized: true }); * * // Safe replacement - adds if doesn't exist * query.replaceCTE('new_cte', newQuery); // Won't throw error * * // Chaining replacements * query * .replaceCTE('cte1', query1, { materialized: false }) * .replaceCTE('cte2', query2, { materialized: true }); * ``` * * @remarks * - Unlike addCTE(), this method won't throw error if CTE already exists * - Unlike removeCTE(), this method won't throw error if CTE doesn't exist * - Useful for upsert-style CTE operations * - MATERIALIZED/NOT MATERIALIZED is PostgreSQL-specific * - Method supports fluent chaining * - Maintains CTE order when replacing existing CTEs */ replaceCTE(name: string, query: SelectQuery, options?: CTEOptions): this; }