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
JavaScript
;
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