UNPKG

rawsql-ts

Version:

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

166 lines 7.89 kB
import { SelectQueryParser } from "../parsers/SelectQueryParser"; import { SqlParamInjector } from "./SqlParamInjector"; import { SqlSortInjector } from "./SqlSortInjector"; import { SqlPaginationInjector } from "./SqlPaginationInjector"; import { PostgresJsonQueryBuilder } from "./PostgresJsonQueryBuilder"; import { QueryBuilder } from "./QueryBuilder"; import { SqlParameterBinder } from "./SqlParameterBinder"; import { ParameterDetector } from "../utils/ParameterDetector"; /** * DynamicQueryBuilder provides pure JavaScript SQL query building capabilities. * It combines SQL parsing with dynamic condition injection (filtering, sorting, pagination, serialization). * * This class is framework-agnostic and does not perform any file I/O operations. * It only works with SQL content provided as strings. * * Key features: * - Pure JavaScript/TypeScript - no file system dependencies * - Framework-agnostic - can be used with any database framework * - Composable - combines multiple injectors in the correct order * - Type-safe - provides TypeScript types for all options * - Testable - easy to unit test without mocking file system */ export class DynamicQueryBuilder { /** * Creates a new DynamicQueryBuilder instance * @param tableColumnResolver Optional function to resolve table columns for wildcard queries */ constructor(tableColumnResolver) { this.tableColumnResolver = tableColumnResolver; } /** * 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, serialize) * @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 }, * serialize: { rootName: 'user', rootEntity: { id: 'user', name: 'User', columns: { id: 'id', name: 'name' } }, nestedEntities: [] } * } * ); * ``` */ buildQuery(sqlContent, options = {}) { // Parse the base SQL let parsedQuery; try { parsedQuery = SelectQueryParser.parse(sqlContent); } catch (error) { throw new Error(`Failed to parse SQL: ${error instanceof Error ? error.message : 'Unknown error'}`); } // Apply dynamic modifications in the correct order let modifiedQuery = parsedQuery; // 1. Bind hardcoded parameters first (before any other transformations) if (options.filter && Object.keys(options.filter).length > 0) { const { hardcodedParams, dynamicFilters } = ParameterDetector.separateFilters(modifiedQuery, options.filter); // Bind hardcoded parameters if any exist if (Object.keys(hardcodedParams).length > 0) { const parameterBinder = new SqlParameterBinder({ requireAllParameters: false }); modifiedQuery = parameterBinder.bind(modifiedQuery, hardcodedParams); } // Apply dynamic filtering only if there are non-hardcoded filters if (Object.keys(dynamicFilters).length > 0) { const paramInjector = new SqlParamInjector(this.tableColumnResolver); // Ensure we have a SimpleSelectQuery for the injector const simpleQuery = QueryBuilder.buildSimpleQuery(modifiedQuery); modifiedQuery = paramInjector.inject(simpleQuery, dynamicFilters); } } // 2. Apply sorting second (after filtering to sort smaller dataset) if (options.sort && Object.keys(options.sort).length > 0) { const sortInjector = new SqlSortInjector(this.tableColumnResolver); // Ensure we have a SimpleSelectQuery for the injector const simpleQuery = QueryBuilder.buildSimpleQuery(modifiedQuery); modifiedQuery = sortInjector.inject(simpleQuery, options.sort); } // 3. Apply pagination third (after filtering and sorting) if (options.paging) { const { page = 1, pageSize } = options.paging; if (pageSize !== undefined) { const paginationInjector = new SqlPaginationInjector(); const paginationOptions = { page, pageSize }; // Ensure we have a SimpleSelectQuery for the injector const simpleQuery = QueryBuilder.buildSimpleQuery(modifiedQuery); modifiedQuery = paginationInjector.inject(simpleQuery, paginationOptions); } } // 4. Apply serialization last (transform the final query structure to JSON) // Note: boolean values are handled at RawSqlClient level for auto-loading if (options.serialize && typeof options.serialize === 'object') { const jsonBuilder = new PostgresJsonQueryBuilder(); // Ensure we have a SimpleSelectQuery for the JSON builder const simpleQuery = QueryBuilder.buildSimpleQuery(modifiedQuery); modifiedQuery = jsonBuilder.buildJsonQuery(simpleQuery, options.serialize); } return modifiedQuery; } /** * Builds a SelectQuery with only filtering applied. * Convenience method for when you only need dynamic WHERE conditions. * * @param sqlContent Raw SQL string to parse and modify * @param filter Filter conditions to apply * @returns Modified SelectQuery with filter conditions applied */ buildFilteredQuery(sqlContent, filter) { return this.buildQuery(sqlContent, { filter }); } /** * 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, sort) { return this.buildQuery(sqlContent, { sort }); } /** * 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, paging) { return this.buildQuery(sqlContent, { paging }); } /** * Builds a SelectQuery with only JSON serialization applied. * Convenience method for when you only need hierarchical JSON transformation. * * @param sqlContent Raw SQL string to parse and modify * @param serialize JSON mapping configuration to apply * @returns Modified SelectQuery with JSON serialization applied */ buildSerializedQuery(sqlContent, serialize) { return this.buildQuery(sqlContent, { serialize }); } /** * 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) { try { SelectQueryParser.parse(sqlContent); return true; } catch (error) { throw new Error(`Invalid SQL: ${error instanceof Error ? error.message : 'Unknown error'}`); } } } //# sourceMappingURL=DynamicQueryBuilder.js.map