UNPKG

rawsql-ts

Version:

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

157 lines 7.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryFlowDiagramGenerator = void 0; const SelectQueryParser_1 = require("../parsers/SelectQueryParser"); const SimpleSelectQuery_1 = require("../models/SimpleSelectQuery"); const BinarySelectQuery_1 = require("../models/BinarySelectQuery"); const DataFlowGraph_1 = require("../reporting/models/DataFlowGraph"); const DataSourceHandler_1 = require("../reporting/services/DataSourceHandler"); const JoinHandler_1 = require("../reporting/services/JoinHandler"); const ProcessHandler_1 = require("../reporting/services/ProcessHandler"); const CTEHandler_1 = require("../reporting/services/CTEHandler"); /** * QueryFlowDiagramGenerator using model-based architecture * Generates Mermaid diagrams from SQL queries following consistent principles */ class QueryFlowDiagramGenerator { constructor() { this.graph = new DataFlowGraph_1.DataFlowGraph(); this.dataSourceHandler = new DataSourceHandler_1.DataSourceHandler(this.graph); this.joinHandler = new JoinHandler_1.JoinHandler(this.graph, this.dataSourceHandler); this.processHandler = new ProcessHandler_1.ProcessHandler(this.graph, this.dataSourceHandler); this.cteHandler = new CTEHandler_1.CTEHandler(this.graph); } generateMermaidFlow(query, options) { // Reset state for new diagram generation this.graph = new DataFlowGraph_1.DataFlowGraph(); this.dataSourceHandler = new DataSourceHandler_1.DataSourceHandler(this.graph); this.joinHandler = new JoinHandler_1.JoinHandler(this.graph, this.dataSourceHandler); this.processHandler = new ProcessHandler_1.ProcessHandler(this.graph, this.dataSourceHandler); this.cteHandler = new CTEHandler_1.CTEHandler(this.graph); this.joinHandler.resetJoinCounter(); // Parse SQL if string const parsedQuery = typeof query === 'string' ? SelectQueryParser_1.SelectQueryParser.parse(query) : query; // Process the query const cteNames = new Set(); this.processQuery(parsedQuery, 'main', cteNames); // Generate Mermaid output return this.graph.generateMermaid((options === null || options === void 0 ? void 0 : options.direction) || 'TD', options === null || options === void 0 ? void 0 : options.title); } static generate(sql) { const generator = new QueryFlowDiagramGenerator(); return generator.generateMermaidFlow(sql); } processQuery(query, context, cteNames) { if (query instanceof SimpleSelectQuery_1.SimpleSelectQuery) { return this.processSimpleQuery(query, context, cteNames); } else if (query instanceof BinarySelectQuery_1.BinarySelectQuery) { return this.processBinaryQuery(query, context, cteNames); } throw new Error('Unsupported query type'); } processSimpleQuery(query, context, cteNames) { // Process CTEs first if (query.withClause) { this.cteHandler.processCTEs(query.withClause, cteNames, this.processQuery.bind(this)); } let currentNodeId = ''; // 1. Process FROM clause (including JOINs) if (query.fromClause) { const hasJoins = query.fromClause.joins && query.fromClause.joins.length > 0; if (hasJoins) { // If there are JOINs, process normally (data source -> JOIN -> SELECT) currentNodeId = this.joinHandler.processFromClause(query.fromClause, cteNames, this.processQuery.bind(this)); } else { // If no JOINs, connect data source directly to SELECT currentNodeId = this.dataSourceHandler.processSource(query.fromClause.source, cteNames, this.processQuery.bind(this)); } } // 2-7. Process other clauses in execution order if (currentNodeId) { currentNodeId = this.processHandler.processQueryClauses(query, context, currentNodeId, cteNames, this.processQuery.bind(this)); } // Handle output node creation based on context return this.handleOutputNode(currentNodeId, context); } processBinaryQuery(query, context, cteNames) { // Check if this is a chain of the same operation const parts = this.flattenBinaryChain(query, query.operator.value); if (parts.length > 2) { return this.processMultiPartOperation(parts, query.operator.value, context, cteNames); } else { return this.processSimpleBinaryOperation(query, context, cteNames); } } processSimpleBinaryOperation(query, context, cteNames) { const leftNodeId = this.processQuery(query.left, `${context}_left`, cteNames); const rightNodeId = this.processQuery(query.right, `${context}_right`, cteNames); // Create operation node with unique ID based on context const operationId = context === 'main' ? 'main' : context.replace(/^cte_/, ''); const operationNode = this.graph.createSetOperationNode(operationId, query.operator.value); // Connect left and right to operation if (leftNodeId && !this.graph.hasConnection(leftNodeId, operationNode.id)) { this.graph.addConnection(leftNodeId, operationNode.id); } if (rightNodeId && !this.graph.hasConnection(rightNodeId, operationNode.id)) { this.graph.addConnection(rightNodeId, operationNode.id); } // Return the operation node directly without adding SELECT return operationNode.id; } processMultiPartOperation(parts, operator, context, cteNames) { const partNodes = []; // Use context to create unique operation ID const operationId = context === 'main' ? 'main' : context.replace(/^cte_/, ''); const operationNode = this.graph.createSetOperationNode(operationId, operator); // Process each part with numbered naming for (let i = 0; i < parts.length; i++) { const partContext = `${context}_part${i + 1}`; const partNodeId = this.processQuery(parts[i], partContext, cteNames); partNodes.push(partNodeId); } // Connect all parts to operation for (const partNodeId of partNodes) { if (partNodeId && !this.graph.hasConnection(partNodeId, operationNode.id)) { this.graph.addConnection(partNodeId, operationNode.id); } } // Return the operation node directly without adding SELECT return operationNode.id; } handleOutputNode(currentNodeId, context) { // Simple principle: Only create output node for main query // All other contexts return their processing result directly if (context === 'main') { const outputNode = this.graph.createOutputNode(context); if (currentNodeId) { this.graph.addConnection(currentNodeId, outputNode.id); } return outputNode.id; } return currentNodeId; } /** * Flattens a binary operation chain into individual parts */ flattenBinaryChain(query, operator) { const parts = []; const collectParts = (q) => { if (q instanceof BinarySelectQuery_1.BinarySelectQuery && q.operator.value === operator) { collectParts(q.left); collectParts(q.right); } else { parts.push(q); } }; collectParts(query); return parts; } } exports.QueryFlowDiagramGenerator = QueryFlowDiagramGenerator; //# sourceMappingURL=QueryFlowDiagramGenerator.js.map