UNPKG

rawsql-ts

Version:

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

285 lines 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CTEDependencyAnalyzer = void 0; const SimpleSelectQuery_1 = require("../models/SimpleSelectQuery"); const CTECollector_1 = require("./CTECollector"); const TableSourceCollector_1 = require("./TableSourceCollector"); const CTETableReferenceCollector_1 = require("./CTETableReferenceCollector"); /** * Analyzer for CTE dependencies in SQL queries. * Provides functionality to analyze dependencies, detect circular references, * and generate topological ordering of CTEs. */ class CTEDependencyAnalyzer { constructor() { this.dependencyGraph = null; this.cteMap = new Map(); // For analyzing CTE-to-CTE dependencies within WITH clause // Excludes CTEs from results to avoid circular references this.sourceCollector = new TableSourceCollector_1.TableSourceCollector(false); // For analyzing main query references to CTEs // Includes CTEs in results to detect CTE usage this.cteReferenceCollector = new CTETableReferenceCollector_1.CTETableReferenceCollector(); this.cteCollector = new CTECollector_1.CTECollector(); } /** * Analyzes the dependencies between CTEs in the given query * @param query The query to analyze * @returns The dependency graph */ analyzeDependencies(query) { const ctes = this.cteCollector.collect(query); this.buildCTEMap(ctes); this.dependencyGraph = this.buildDependencyGraph(ctes, query); return this.dependencyGraph; } /** * Gets the list of CTEs that the specified CTE depends on * @param cteName The name of the CTE * @returns Array of CTE names this CTE depends on */ getDependencies(cteName) { this.ensureAnalyzed(); const node = this.findNodeByName(cteName); return node ? [...node.dependencies] : []; } /** * Gets the list of CTEs that depend on the specified CTE * @param cteName The name of the CTE * @returns Array of CTE names that depend on this CTE */ getDependents(cteName) { this.ensureAnalyzed(); const node = this.findNodeByName(cteName); return node ? [...node.dependents] : []; } /** * Gets the list of CTEs that are directly referenced by the main query * @returns Array of CTE names referenced by the main query */ getMainQueryDependencies() { this.ensureAnalyzed(); const mainQueryNode = this.findNodeByName(CTEDependencyAnalyzer.MAIN_QUERY_NAME); return mainQueryNode ? [...mainQueryNode.dependencies] : []; } /** * Gets nodes by type (CTE or ROOT) * @param nodeType The type of nodes to retrieve * @returns Array of nodes of the specified type */ getNodesByType(nodeType) { this.ensureAnalyzed(); return this.dependencyGraph.nodes.filter(n => n.type === nodeType); } /** * Gets the main query node * @returns The main query node or undefined if not found */ getMainQueryNode() { this.ensureAnalyzed(); return this.findNodeByName(CTEDependencyAnalyzer.MAIN_QUERY_NAME); } /** * Checks if there are any circular dependencies in the CTE graph * @returns true if circular dependencies exist, false otherwise */ hasCircularDependency() { this.ensureAnalyzed(); try { this.getExecutionOrder(); return false; } catch (error) { if (error instanceof Error && error.message.includes(CTEDependencyAnalyzer.ERROR_MESSAGES.CIRCULAR_REFERENCE)) { return true; } throw error; } } /** * Gets the topological sort order for CTE execution * @returns Array of CTE names in execution order * @throws Error if circular dependencies are detected */ getExecutionOrder() { this.ensureAnalyzed(); const visited = new Set(); const visiting = new Set(); const result = []; // Build adjacency list from dependency graph const dependencyMap = new Map(); for (const node of this.dependencyGraph.nodes) { dependencyMap.set(node.name, new Set(node.dependencies)); } const visit = (nodeName) => { if (visited.has(nodeName)) return; if (visiting.has(nodeName)) { throw new Error(`${CTEDependencyAnalyzer.ERROR_MESSAGES.CIRCULAR_REFERENCE}: ${nodeName}`); } visiting.add(nodeName); const deps = dependencyMap.get(nodeName) || new Set(); for (const dep of deps) { visit(dep); } visiting.delete(nodeName); visited.add(nodeName); result.push(nodeName); }; // Visit all nodes for (const node of this.dependencyGraph.nodes) { if (!visited.has(node.name)) { visit(node.name); } } return result; } /** * Builds the dependency graph from the given CTEs and main query * @param ctes Array of CommonTable objects * @param mainQuery The main query that may reference CTEs * @returns The constructed dependency graph */ buildDependencyGraph(ctes, mainQuery) { const nodes = []; const edges = []; const dependencyMap = new Map(); const dependentMap = new Map(); // Initialize maps for all CTEs and main query for (const cte of ctes) { const name = CTEDependencyAnalyzer.getCTEName(cte); dependencyMap.set(name, new Set()); dependentMap.set(name, new Set()); } dependencyMap.set(CTEDependencyAnalyzer.MAIN_QUERY_NAME, new Set()); dependentMap.set(CTEDependencyAnalyzer.MAIN_QUERY_NAME, new Set()); // Analyze dependencies for each CTE for (const cte of ctes) { const cteName = CTEDependencyAnalyzer.getCTEName(cte); // Find all table/CTE references in this CTE's query // Uses sourceCollector which excludes CTEs to get only real table dependencies const referencedTables = this.sourceCollector.collect(cte.query); for (const referencedTable of referencedTables) { const referencedName = referencedTable.table.name; // Only consider references to other CTEs in our collection if (this.cteMap.has(referencedName) && referencedName !== cteName) { dependencyMap.get(cteName).add(referencedName); dependentMap.get(referencedName).add(cteName); edges.push({ from: cteName, to: referencedName }); } } } // Analyze main query references to CTEs (excluding WITH clause) const mainQueryWithoutCTE = this.getMainQueryWithoutCTE(mainQuery); if (mainQueryWithoutCTE) { // Uses cteReferenceCollector which includes CTEs to detect CTE usage in main query const mainQueryReferences = this.cteReferenceCollector.collect(mainQueryWithoutCTE); for (const referencedTable of mainQueryReferences) { const referencedName = referencedTable.table.name; // If main query references a CTE, create dependency edge if (this.cteMap.has(referencedName)) { dependencyMap.get(CTEDependencyAnalyzer.MAIN_QUERY_NAME).add(referencedName); dependentMap.get(referencedName).add(CTEDependencyAnalyzer.MAIN_QUERY_NAME); edges.push({ from: CTEDependencyAnalyzer.MAIN_QUERY_NAME, to: referencedName }); } } } // Create CTE nodes for (const cte of ctes) { const name = CTEDependencyAnalyzer.getCTEName(cte); nodes.push({ name, type: 'CTE', cte, dependencies: Array.from(dependencyMap.get(name) || new Set()), dependents: Array.from(dependentMap.get(name) || new Set()) }); } // Create main query node nodes.push({ name: CTEDependencyAnalyzer.MAIN_QUERY_NAME, type: 'ROOT', cte: null, dependencies: Array.from(dependencyMap.get(CTEDependencyAnalyzer.MAIN_QUERY_NAME) || new Set()), dependents: Array.from(dependentMap.get(CTEDependencyAnalyzer.MAIN_QUERY_NAME) || new Set()) }); return { nodes, edges }; } /** * Ensures that dependency analysis has been performed * @throws Error if analyzeDependencies has not been called */ ensureAnalyzed() { if (!this.dependencyGraph) { throw new Error(CTEDependencyAnalyzer.ERROR_MESSAGES.NOT_ANALYZED); } } /** * Builds the CTE name-to-object mapping for quick lookups * @param ctes Array of CommonTable objects */ buildCTEMap(ctes) { this.cteMap.clear(); for (const cte of ctes) { const name = CTEDependencyAnalyzer.getCTEName(cte); this.cteMap.set(name, cte); } } /** * Finds a node in the dependency graph by CTE name * @param cteName The name of the CTE to find * @returns The CTENode if found, undefined otherwise */ findNodeByName(cteName) { var _a; return (_a = this.dependencyGraph) === null || _a === void 0 ? void 0 : _a.nodes.find(n => n.name === cteName); } /** * Gets the main query without the WITH clause for analyzing main query dependencies * @param query The complete query with WITH clause * @returns A query without WITH clause, or null if no main query exists */ getMainQueryWithoutCTE(query) { if (!query.withClause) { // No WITH clause, return the query as-is return query; } // Create a copy of the query without the WITH clause const mainQueryCopy = new SimpleSelectQuery_1.SimpleSelectQuery({ selectClause: query.selectClause, fromClause: query.fromClause, whereClause: query.whereClause, groupByClause: query.groupByClause, havingClause: query.havingClause, orderByClause: query.orderByClause, limitClause: query.limitClause, offsetClause: query.offsetClause, fetchClause: query.fetchClause, forClause: query.forClause, windowClause: query.windowClause, // Intentionally skip withClause (defaults to null) }); return mainQueryCopy; } /** * Extracts the name from a CommonTable * @param cte The CommonTable object * @returns The name of the CTE */ static getCTEName(cte) { return cte.aliasExpression.table.name; } } exports.CTEDependencyAnalyzer = CTEDependencyAnalyzer; CTEDependencyAnalyzer.ERROR_MESSAGES = { NOT_ANALYZED: "Must call analyzeDependencies first", CIRCULAR_REFERENCE: "Circular reference detected in CTE" }; CTEDependencyAnalyzer.MAIN_QUERY_NAME = 'MAIN_QUERY'; //# sourceMappingURL=CTEDependencyAnalyzer.js.map