UNPKG

rawsql-ts

Version:

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

259 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FilterableItemCollector = exports.FilterableItem = void 0; const SimpleSelectQuery_1 = require("../models/SimpleSelectQuery"); const SchemaCollector_1 = require("./SchemaCollector"); const ParameterDetector_1 = require("../utils/ParameterDetector"); const SelectableColumnCollector_1 = require("./SelectableColumnCollector"); /** * Represents a filterable item that can be used in DynamicQueryBuilder * Can be either a table column or a SQL parameter */ class FilterableItem { constructor(name, type, tableName) { this.name = name; this.type = type; this.tableName = tableName; } } exports.FilterableItem = FilterableItem; /** * Collects filterable items (columns and parameters) from SQL queries * for use in DynamicQueryBuilder filtering functionality. * * This class combines: * - Table columns (from SelectableColumnCollector with FullName duplicate detection) * - SQL parameters (from ParameterDetector) * * Features: * - FullName mode preserves columns with same names from different tables (u.id vs p.id) * - Upstream collection (default) provides comprehensive column discovery for maximum filtering * - Qualified mode option for table.column naming in complex JOINs * * This allows DynamicQueryBuilder to filter on both actual table columns * and fixed parameters defined in the SQL with full JOIN table support. */ class FilterableItemCollector { /** * Creates a new FilterableItemCollector * @param tableColumnResolver Optional resolver for wildcard column expansion * @param options Optional configuration options * - qualified: If true, return table.column names; if false, return column names only * - upstream: If true (default), collect all available columns from upstream sources for maximum filtering capability */ constructor(tableColumnResolver, options) { this.tableColumnResolver = tableColumnResolver; this.options = { qualified: false, upstream: true, ...options }; } /** * Collects all filterable items (columns and parameters) from a SQL query * @param query The parsed SQL query to analyze * @returns Array of FilterableItem objects representing columns and parameters */ collect(query) { const items = []; // 1. Collect table columns using SchemaCollector const columnItems = this.collectColumns(query); items.push(...columnItems); // 2. Collect SQL parameters using ParameterDetector const parameterItems = this.collectParameters(query); items.push(...parameterItems); // 3. Remove duplicates (same name and type) return this.removeDuplicates(items); } /** * Collects table columns using both SelectableColumnCollector and SchemaCollector */ collectColumns(query) { const items = []; // First, collect columns using SelectableColumnCollector (includes WHERE clause columns) try { const columnCollector = new SelectableColumnCollector_1.SelectableColumnCollector(this.tableColumnResolver, false, // includeUsingColumns SelectableColumnCollector_1.DuplicateDetectionMode.FullName, // Use full names to preserve duplicates { upstream: this.options.upstream } // Enable upstream collection based on options ); const columns = columnCollector.collect(query); // Convert column information to FilterableItem objects for (const column of columns) { let tableName = undefined; let realTableName = undefined; // Primary: Extract table name from column reference namespace using getNamespace() if (column.value && typeof column.value.getNamespace === 'function') { const namespace = column.value.getNamespace(); if (namespace && namespace.trim() !== '') { tableName = namespace; // Get real table name if using qualified mode if (this.options.qualified) { realTableName = this.getRealTableName(query, namespace); } } } // Fallback: Try to infer from query structure for simple queries if (!tableName) { tableName = this.inferTableNameFromQuery(query); if (tableName && this.options.qualified) { realTableName = tableName; // For simple queries, table name is already real } } // Generate column name based on qualified option let columnName = column.name; if (this.options.qualified && (realTableName || tableName)) { const nameToUse = realTableName || tableName; columnName = `${nameToUse}.${column.name}`; } items.push(new FilterableItem(columnName, 'column', tableName)); } } catch (error) { // If SelectableColumnCollector fails, fall back to SchemaCollector only console.warn('Failed to collect columns with SelectableColumnCollector, using fallback:', error); try { const schemaCollector = new SchemaCollector_1.SchemaCollector(this.tableColumnResolver, true); const schemas = schemaCollector.collect(query); for (const schema of schemas) { for (const columnName of schema.columns) { // Generate column name based on qualified option let finalColumnName = columnName; if (this.options.qualified) { // For SchemaCollector, schema.name should already be the real table name finalColumnName = `${schema.name}.${columnName}`; } items.push(new FilterableItem(finalColumnName, 'column', schema.name)); } } } catch (fallbackError) { console.warn('Failed to collect columns with both approaches:', error, fallbackError); } } return items; } /** * Attempts to infer table name from query structure for simple cases */ inferTableNameFromQuery(query) { // For simple queries with single table, try to extract table name if (query instanceof SimpleSelectQuery_1.SimpleSelectQuery && query.fromClause && query.fromClause.source) { const datasource = query.fromClause.source.datasource; if (datasource && typeof datasource.table === 'object') { const table = datasource.table; if (table && typeof table.name === 'string') { return table.name; } } } return undefined; } /** * Attempts to resolve real table name from alias/namespace */ getRealTableName(query, aliasOrName) { var _a, _b; try { // Handle CTEs by converting to simple query first const simpleQuery = query.type === 'WITH' ? query.toSimpleQuery() : query; if (simpleQuery instanceof SimpleSelectQuery_1.SimpleSelectQuery && simpleQuery.fromClause) { // Check main datasource if ((_a = simpleQuery.fromClause.source) === null || _a === void 0 ? void 0 : _a.datasource) { const mainSource = simpleQuery.fromClause.source; const realName = this.extractRealTableName(mainSource, aliasOrName); if (realName) return realName; } // Check JOIN clauses const fromClause = simpleQuery.fromClause; if (fromClause.joinClauses && Array.isArray(fromClause.joinClauses)) { for (const joinClause of fromClause.joinClauses) { if ((_b = joinClause.source) === null || _b === void 0 ? void 0 : _b.datasource) { const realName = this.extractRealTableName(joinClause.source, aliasOrName); if (realName) return realName; } } } } } catch (error) { console.warn('Error resolving real table name:', error); } // If we can't resolve, return the original name return aliasOrName; } /** * Extracts real table name from a datasource */ extractRealTableName(source, aliasOrName) { var _a, _b, _c; try { const datasource = source.datasource; if (!datasource) return undefined; // Get alias from multiple possible locations const alias = source.alias || ((_b = (_a = source.aliasExpression) === null || _a === void 0 ? void 0 : _a.table) === null || _b === void 0 ? void 0 : _b.name); const realTableName = (_c = datasource.table) === null || _c === void 0 ? void 0 : _c.name; if (alias === aliasOrName && realTableName) { return realTableName; } // If no alias but names match, it's a direct table reference if (!alias && realTableName === aliasOrName) { return realTableName; } } catch (error) { // Ignore errors in extraction } return undefined; } /** * Collects SQL parameters from the query using ParameterDetector */ collectParameters(query) { const items = []; try { // Use existing ParameterDetector to extract parameter names from AST const parameterNames = ParameterDetector_1.ParameterDetector.extractParameterNames(query); // Convert parameter names to FilterableItem objects for (const paramName of parameterNames) { items.push(new FilterableItem(paramName, 'parameter')); } } catch (error) { // If parameter extraction fails, continue with empty parameter list console.warn('Failed to collect parameters:', error); } return items; } /** * Removes duplicate items with the same name, type, and table name * This preserves columns with the same name from different tables */ removeDuplicates(items) { const seen = new Set(); const result = []; for (const item of items) { // Include table name in the key to preserve columns from different tables const key = `${item.type}:${item.name}:${item.tableName || 'none'}`; if (!seen.has(key)) { seen.add(key); result.push(item); } } return result.sort((a, b) => { // Sort by type first (columns before parameters), then by table name, then by name if (a.type !== b.type) { return a.type === 'column' ? -1 : 1; } // For columns, sort by table name first, then column name if (a.type === 'column') { const tableA = a.tableName || ''; const tableB = b.tableName || ''; if (tableA !== tableB) { return tableA.localeCompare(tableB); } } return a.name.localeCompare(b.name); }); } } exports.FilterableItemCollector = FilterableItemCollector; //# sourceMappingURL=FilterableItemCollector.js.map