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