UNPKG

rawsql-ts

Version:

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

164 lines 8.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DynamicQueryBuilder = void 0; const SelectQueryParser_1 = require("../parsers/SelectQueryParser"); const SqlParamInjector_1 = require("./SqlParamInjector"); const SqlSortInjector_1 = require("./SqlSortInjector"); const SqlPaginationInjector_1 = require("./SqlPaginationInjector"); const PostgresJsonQueryBuilder_1 = require("./PostgresJsonQueryBuilder"); const QueryBuilder_1 = require("./QueryBuilder"); const SqlParameterBinder_1 = require("./SqlParameterBinder"); const ParameterDetector_1 = require("../utils/ParameterDetector"); /** * DynamicQueryBuilder combines SQL parsing with dynamic condition injection (filters, sorts, paging, JSON serialization). * * 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. * - Supports JSON serialization for hierarchical projections. */ 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_1.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_1.ParameterDetector.separateFilters(modifiedQuery, options.filter); // Bind hardcoded parameters if any exist if (Object.keys(hardcodedParams).length > 0) { const parameterBinder = new SqlParameterBinder_1.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_1.SqlParamInjector(this.tableColumnResolver); // Ensure we have a SimpleSelectQuery for the injector const simpleQuery = QueryBuilder_1.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_1.SqlSortInjector(this.tableColumnResolver); // Ensure we have a SimpleSelectQuery for the injector const simpleQuery = QueryBuilder_1.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_1.SqlPaginationInjector(); const paginationOptions = { page, pageSize }; // Ensure we have a SimpleSelectQuery for the injector const simpleQuery = QueryBuilder_1.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_1.PostgresJsonQueryBuilder(); // Ensure we have a SimpleSelectQuery for the JSON builder const simpleQuery = QueryBuilder_1.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_1.SelectQueryParser.parse(sqlContent); return true; } catch (error) { throw new Error(`Invalid SQL: ${error instanceof Error ? error.message : 'Unknown error'}`); } } } exports.DynamicQueryBuilder = DynamicQueryBuilder; //# sourceMappingURL=DynamicQueryBuilder.js.map