rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
226 lines (225 loc) • 9.11 kB
TypeScript
import { SelectQuery } from "../models/SelectQuery";
import { SortConditions } from "./SqlSortInjector";
import { PaginationOptions } from "./SqlPaginationInjector";
import { SqlParameterValue } from "../models/ValueComponent";
import { SchemaInfo } from "./OptimizeUnusedLeftJoins";
import { OptionalConditionParameterStates, OptionalConditionPruningParameters } from "./PruneOptionalConditionBranches";
import type { ExistsSubqueryDefinition } from "./ExistsPredicateInjector";
export type { ExistsSubqueryDefinition };
/**
* Object-form filter condition supporting scalar operators, logical grouping,
* and column-anchored EXISTS/NOT EXISTS predicates.
*
* @example
* const filter: FilterConditionObject = {
* min: 10,
* max: 100,
* exists: { sql: 'SELECT 1 FROM orders WHERE user_id = $c0' }
* };
* Related tests: packages/core/tests/transformers/DynamicQueryBuilder.test.ts
*/
export interface FilterConditionObject {
min?: SqlParameterValue;
max?: SqlParameterValue;
like?: string;
ilike?: string;
in?: SqlParameterValue[];
any?: SqlParameterValue[];
'='?: SqlParameterValue;
'>'?: SqlParameterValue;
'<'?: SqlParameterValue;
'>='?: SqlParameterValue;
'<='?: SqlParameterValue;
'!='?: SqlParameterValue;
'<>'?: SqlParameterValue;
or?: {
column: string;
[operator: string]: SqlParameterValue | string;
}[];
and?: {
column: string;
[operator: string]: SqlParameterValue | string;
}[];
column?: string;
exists?: ExistsSubqueryDefinition;
notExists?: ExistsSubqueryDefinition;
}
export interface MultiColumnExistsDefinition extends ExistsSubqueryDefinition {
on: string[];
}
export type FilterConditionValue = SqlParameterValue | SqlParameterValue[] | FilterConditionObject | MultiColumnExistsDefinition[];
/**
* Filter conditions for dynamic query building.
*
* Supports both unqualified and qualified column names:
* - Unqualified: `{ name: 'Alice' }` - applies to all columns named 'name'
* - Qualified: `{ 'users.name': 'Bob' }` - applies only to the 'name' column in the 'users' table/alias
* - Hybrid: `{ name: 'Default', 'users.name': 'Override' }` - qualified names take priority over unqualified
*
* @example
* ```typescript
* // Basic usage (backward compatible)
* const filter: FilterConditions = {
* name: 'Alice',
* status: 'active'
* };
*
* // Qualified names for disambiguation in JOINs
* const filter: FilterConditions = {
* 'users.name': 'Alice', // Only applies to users.name
* 'profiles.name': 'Bob' // Only applies to profiles.name
* };
*
* // Hybrid approach
* const filter: FilterConditions = {
* status: 'active', // Applies to all 'status' columns
* 'users.name': 'Alice', // Overrides for users.name specifically
* 'profiles.name': 'Bob' // Overrides for profiles.name specifically
* };
* ```
* Related tests: packages/core/tests/transformers/DynamicQueryBuilder.test.ts
*/
export type FilterConditions = Record<string, FilterConditionValue>;
/**
* Options for dynamic query building
*/
export interface QueryBuildOptions {
/**
* Legacy filter input for named-parameter binding only.
* Dynamic predicate injection is no longer supported and will fail fast.
*/
filter?: FilterConditions;
/** Sort conditions to inject into ORDER BY clause */
sort?: SortConditions;
/** Pagination options to inject LIMIT/OFFSET clauses */
paging?: PaginationOptions;
/**
* Columns that should remain in the SELECT clause.
* When specified, every other column is removed so the output matches this whitelist.
*/
includeColumns?: string[];
/**
* Columns that should be removed from the SELECT clause.
* Filters apply subtractively and only drop columns that exist in the original output.
*/
excludeColumns?: string[];
/**
* Throw when column-anchored EXISTS filters fail to resolve.
* Defaults to false so invalid definitions are skipped silently.
*/
existsStrict?: boolean;
/**
* Schema metadata used when removing unused LEFT JOINs; overrides builder defaults.
*/
schemaInfo?: SchemaInfo;
/**
* Remove unused LEFT JOINs before further processing when schema info is available.
*/
removeUnusedLeftJoins?: boolean;
/**
* Remove unused Common Table Expressions (CTEs) when they can be safely pruned.
* Defaults to false to preserve original WITH definitions.
*/
removeUnusedCtes?: boolean;
/**
* Explicit opt-in values for truthful optional condition branches in source SQL.
* Only listed parameter names are eligible for pruning, and `null`/`undefined` are treated as absent-equivalent.
*/
optionalConditionParameters?: OptionalConditionPruningParameters;
/**
* Legacy state-map form for optional branch pruning.
* Prefer `optionalConditionParameters` for new code so SQL-facing null semantics stay intuitive.
*/
optionalConditionParameterStates?: OptionalConditionParameterStates;
}
/**
* Builder-level configuration that can be reused across multiple build calls.
*/
export interface DynamicQueryBuilderOptions {
/** Optional resolver for table column names (retains backward compatibility). */
tableColumnResolver?: (tableName: string) => string[];
/**
* Schema metadata that may be applied by default when the optimizer is enabled.
* Schema info provided via QueryBuildOptions takes precedence.
*/
schemaInfo?: SchemaInfo;
}
/**
* DynamicQueryBuilder combines SQL parsing with dynamic condition injection (filters, sorts, paging).
*
* Key behaviours verified in packages/core/tests/transformers/DynamicQueryBuilder.test.ts:
* - Preserves the input SQL when no options are supplied.
* - Applies filter, sort, and pagination in a deterministic order.
* - Fails fast for removed SQL-result JSON shaping.
*/
export declare class DynamicQueryBuilder {
private tableColumnResolver?;
private defaultSchemaInfo?;
/**
* Creates a new DynamicQueryBuilder instance.
* Accepts either the legacy table resolver or an options object that can provide schema metadata.
*
* @param resolverOrOptions Optional resolver or configuration object
*/
constructor(resolverOrOptions?: ((tableName: string) => string[]) | DynamicQueryBuilderOptions);
/**
* Builds a SelectQuery from SQL content with dynamic conditions.
* This is a pure function that does not perform any I/O operations.
* @param sqlContent Raw SQL string to parse and modify
* @param options Dynamic conditions to apply (filter, sort, paging)
* @returns Modified SelectQuery with all dynamic conditions applied
* @example
* ```typescript
* const builder = new DynamicQueryBuilder();
* const query = builder.buildQuery(
* 'SELECT id, name FROM users WHERE active = true',
* {
* filter: { status: 'premium' },
* sort: { created_at: { desc: true } },
* paging: { page: 2, pageSize: 10 }
* }
* );
* ```
*/
buildQuery(sqlContent: string, options?: QueryBuildOptions): SelectQuery;
private resolveOptionalConditionPruningParameters;
private applyColumnFilters;
private normalizeColumnList;
private normalizeColumnIdentifier;
private getSelectItemName;
/**
* Legacy helper for binding existing named parameters without adding new runtime predicates.
* Dynamic WHERE-condition injection is no longer supported; use SSSQL scaffold/refresh instead.
*
* @param sqlContent Raw SQL string to parse and modify
* @param filter Named parameters to bind when they already exist in the SQL
* @returns Modified SelectQuery after binding existing named parameters
*/
buildFilteredQuery(sqlContent: string, filter: FilterConditions): SelectQuery;
/**
* Builds a SelectQuery with only sorting applied.
* Convenience method for when you only need dynamic ORDER BY clauses.
*
* @param sqlContent Raw SQL string to parse and modify
* @param sort Sort conditions to apply
* @returns Modified SelectQuery with sort conditions applied
*/
buildSortedQuery(sqlContent: string, sort: SortConditions): SelectQuery; /**
* Builds a SelectQuery with only pagination applied.
* Convenience method for when you only need LIMIT/OFFSET clauses.
*
* @param sqlContent Raw SQL string to parse and modify
* @param paging Pagination options to apply
* @returns Modified SelectQuery with pagination applied
*/
buildPaginatedQuery(sqlContent: string, paging: PaginationOptions): SelectQuery;
/**
* Validates SQL content by attempting to parse it.
* Useful for testing SQL validity without applying any modifications.
*
* @param sqlContent Raw SQL string to validate
* @returns true if SQL is valid, throws error if invalid
* @throws Error if SQL cannot be parsed
*/
validateSql(sqlContent: string): boolean;
}