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