UNPKG

rawsql-ts

Version:

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

253 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CTEDependencyTracer = void 0; const SelectQuery_1 = require("../models/SelectQuery"); const CTECollector_1 = require("./CTECollector"); const SelectableColumnCollector_1 = require("./SelectableColumnCollector"); const TableSourceCollector_1 = require("./TableSourceCollector"); /** * Debug utility for visualizing CTE dependencies and column search paths */ class CTEDependencyTracer { constructor(options) { var _a; this.columnCollector = new SelectableColumnCollector_1.SelectableColumnCollector(); this.silent = (_a = options === null || options === void 0 ? void 0 : options.silent) !== null && _a !== void 0 ? _a : false; } /** * Build complete CTE dependency graph */ buildGraph(query) { const cteCollector = new CTECollector_1.CTECollector(); const ctes = cteCollector.collect(query); const nodes = new Map(); // First pass: Create all nodes ctes.forEach(cte => { const cteName = cte.getSourceAliasName(); let columns = []; try { const columnRefs = this.columnCollector.collect(cte.query); columns = columnRefs.map(col => col.name); } catch (error) { if (!this.silent) { console.warn(`Failed to collect columns for CTE ${cteName}: ${error instanceof Error ? error.message : String(error)}`); } } nodes.set(cteName, { name: cteName, columns, dependencies: [], dependents: [], query: cte.query, level: 0 }); }); // Second pass: Build dependencies ctes.forEach(cte => { const cteName = cte.getSourceAliasName(); const node = nodes.get(cteName); // Find all CTEs referenced in this CTE's query const referencedCTEs = this.findReferencedCTEs(cte.query, nodes); node.dependencies = referencedCTEs; // Update dependents referencedCTEs.forEach(depName => { const depNode = nodes.get(depName); if (depNode && !depNode.dependents.includes(cteName)) { depNode.dependents.push(cteName); } }); }); // Third pass: Calculate levels this.calculateLevels(nodes); // Identify root and leaf nodes const rootNodes = []; const leafNodes = []; nodes.forEach((node, name) => { if (node.dependencies.length === 0) { rootNodes.push(name); } if (node.dependents.length === 0) { leafNodes.push(name); } }); return { nodes, rootNodes, leafNodes }; } /** * Trace column search path through CTE dependencies */ traceColumnSearch(query, columnName) { const graph = this.buildGraph(query); const searchPath = []; const foundIn = []; const notFoundIn = []; // Start from main query searchPath.push('MAIN_QUERY'); let mainColumns = []; try { // SelectableColumnCollector only supports SimpleSelectQuery if (query instanceof SelectQuery_1.SimpleSelectQuery) { const columnRefs = this.columnCollector.collect(query); mainColumns = columnRefs.map(col => col.name); } else if (query instanceof SelectQuery_1.BinarySelectQuery) { // For UNION/INTERSECT/EXCEPT queries, collect from all branches const leftColumns = query.left instanceof SelectQuery_1.SimpleSelectQuery ? this.columnCollector.collect(query.left).map(col => col.name) : []; const rightColumns = query.right instanceof SelectQuery_1.SimpleSelectQuery ? this.columnCollector.collect(query.right).map(col => col.name) : []; // Combine and deduplicate columns from both branches const allColumns = [...leftColumns, ...rightColumns]; mainColumns = [...new Set(allColumns)]; } } catch (error) { if (!this.silent) { console.warn(`Failed to collect columns from main query: ${error instanceof Error ? error.message : String(error)}`); } } if (mainColumns.some(col => col.toLowerCase() === columnName.toLowerCase())) { foundIn.push('MAIN_QUERY'); } else { notFoundIn.push('MAIN_QUERY'); } // Search through CTEs in dependency order (leaf to root) const visited = new Set(); const searchOrder = this.getSearchOrder(graph); searchOrder.forEach(cteName => { if (visited.has(cteName)) return; visited.add(cteName); searchPath.push(cteName); const node = graph.nodes.get(cteName); if (node.columns.some(col => col.toLowerCase() === columnName.toLowerCase())) { foundIn.push(cteName); } else { notFoundIn.push(cteName); } }); return { searchPath, foundIn, notFoundIn, graph }; } /** * Print visual representation of CTE dependency graph */ printGraph(graph) { if (this.silent) return; console.log('\n=== CTE Dependency Graph ==='); // Group by levels const levels = new Map(); graph.nodes.forEach((node, name) => { const level = node.level; if (!levels.has(level)) { levels.set(level, []); } levels.get(level).push(name); }); // Print level by level const maxLevel = Math.max(...Array.from(levels.keys())); for (let level = 0; level <= maxLevel; level++) { const nodesAtLevel = levels.get(level) || []; if (nodesAtLevel.length > 0) { console.log(`\nLevel ${level}:`); nodesAtLevel.forEach(name => { const node = graph.nodes.get(name); console.log(` ${name} (${node.columns.length} cols)`); if (node.dependencies.length > 0) { console.log(` depends on: ${node.dependencies.join(', ')}`); } }); } } } /** * Print column search trace */ printColumnTrace(columnName, trace) { if (this.silent) return; console.log(`\n=== Column Search Trace for "${columnName}" ===`); console.log(`Search path: ${trace.searchPath.join(' → ')}`); console.log(`Found in: ${trace.foundIn.length > 0 ? trace.foundIn.join(', ') : 'NONE'}`); console.log(`Not found in: ${trace.notFoundIn.join(', ')}`); if (trace.foundIn.length > 0) { console.log('\n--- Details of CTEs containing the column ---'); trace.foundIn.forEach(cteName => { if (cteName === 'MAIN_QUERY') return; const node = trace.graph.nodes.get(cteName); if (node) { console.log(`${cteName}:`); console.log(` All columns: ${node.columns.join(', ')}`); console.log(` Dependencies: ${node.dependencies.length > 0 ? node.dependencies.join(', ') : 'none'}`); } }); } } /** * Find CTEs that are actually referenced in the given query. * Uses TableSourceCollector to properly identify table references from the AST. */ findReferencedCTEs(query, allCTEs) { // Use TableSourceCollector to get all table references from the query const tableCollector = new TableSourceCollector_1.TableSourceCollector(); const tableSources = tableCollector.collect(query); const referenced = []; // Check each table source to see if it matches a CTE name for (const source of tableSources) { const tableName = source.table.name; if (tableName && allCTEs.has(tableName)) { if (!referenced.includes(tableName)) { referenced.push(tableName); } } } return referenced; } calculateLevels(nodes) { const visited = new Set(); const calculateLevel = (nodeName) => { if (visited.has(nodeName)) { return nodes.get(nodeName).level; } visited.add(nodeName); const node = nodes.get(nodeName); if (node.dependencies.length === 0) { node.level = 0; return 0; } let maxDepLevel = -1; node.dependencies.forEach(depName => { const depLevel = calculateLevel(depName); maxDepLevel = Math.max(maxDepLevel, depLevel); }); node.level = maxDepLevel + 1; return node.level; }; nodes.forEach((_, name) => calculateLevel(name)); } getSearchOrder(graph) { // Return CTEs in order from leaf to root (level descending) const ordered = []; const levels = new Map(); graph.nodes.forEach((node, name) => { const level = node.level; if (!levels.has(level)) { levels.set(level, []); } levels.get(level).push(name); }); const maxLevel = Math.max(...Array.from(levels.keys())); for (let level = maxLevel; level >= 0; level--) { const nodesAtLevel = levels.get(level) || []; ordered.push(...nodesAtLevel); } return ordered; } } exports.CTEDependencyTracer = CTEDependencyTracer; //# sourceMappingURL=CTEDependencyTracer.js.map