UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

407 lines 16 kB
"use strict"; /** * Query Router * * Implements intelligent query routing for the Hybrid Storage Manager * to optimize query performance across different storage layers. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryRouter = void 0; const events_1 = require("events"); const logger_1 = __importDefault(require("../../../utils/logger")); const types_1 = require("./types"); class QueryRouter extends events_1.EventEmitter { routingRules = new Map(); performanceMetrics = new Map(); metricsCollectionTimer; constructor(_config) { super(); this.initializeRoutingRules(); this.initializeMetrics(); this.setupMetricsCollection(); } /** * Initialize the query router */ async initialize() { try { logger_1.default.info('Initializing Query Router'); // Initialize performance metrics for all storage layers for (const layer of Object.values(types_1.StorageLayer)) { this.performanceMetrics.set(layer, this.createEmptyMetrics()); } logger_1.default.info('Query Router initialized successfully'); this.emit('initialized'); } catch (error) { logger_1.default.error('Failed to initialize Query Router:', error); throw error; } } /** * Route a query to the optimal storage layer */ async routeQuery(request) { try { const startTime = Date.now(); // Analyze query characteristics const queryAnalysis = this.analyzeQuery(request); // Determine optimal storage layer const optimalLayer = this.selectOptimalLayer(queryAnalysis, request); // Create routing decision const decision = { primaryLayer: optimalLayer, fallbackLayers: this.getFallbackLayers(optimalLayer, queryAnalysis), reasoning: this.generateRoutingReasoning(optimalLayer, queryAnalysis), estimatedLatency: this.estimateLatency(optimalLayer, queryAnalysis), cacheStrategy: this.determineCacheStrategy(queryAnalysis), queryId: this.generateQueryId(request) }; // Update routing metrics this.updateRoutingMetrics(decision, Date.now() - startTime); this.emit('query_routed', { request, decision }); return decision; } catch (error) { logger_1.default.error('Failed to route query:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new types_1.QueryError(`Query routing failed: ${errorMessage}`, request.query, error instanceof Error ? error : undefined); } } /** * Update performance metrics for a storage layer */ updatePerformanceMetrics(layer, latency, success, resultSize) { const metrics = this.performanceMetrics.get(layer); if (!metrics) return; metrics.totalQueries++; metrics.totalLatency += latency; metrics.averageLatency = metrics.totalLatency / metrics.totalQueries; if (success) { metrics.successfulQueries++; } else { metrics.failedQueries++; } metrics.successRate = metrics.successfulQueries / metrics.totalQueries; if (resultSize !== undefined) { metrics.totalResultSize += resultSize; metrics.averageResultSize = metrics.totalResultSize / metrics.successfulQueries; } // Update min/max latency if (latency < metrics.minLatency || metrics.minLatency === 0) { metrics.minLatency = latency; } if (latency > metrics.maxLatency) { metrics.maxLatency = latency; } this.emit('metrics_updated', { layer, metrics }); } /** * Get performance metrics for all storage layers */ getPerformanceMetrics() { return new Map(this.performanceMetrics); } /** * Get routing statistics */ getRoutingStats() { const stats = {}; for (const [layer, metrics] of this.performanceMetrics) { stats[layer] = { totalQueries: metrics.totalQueries, successRate: metrics.successRate, averageLatency: metrics.averageLatency, minLatency: metrics.minLatency, maxLatency: metrics.maxLatency, averageResultSize: metrics.averageResultSize }; } return stats; } /** * Add custom routing rule */ addRoutingRule(ruleId, route) { this.routingRules.set(ruleId, route); logger_1.default.debug(`Added routing rule: ${ruleId}`); this.emit('routing_rule_added', { ruleId, route }); } /** * Remove routing rule */ removeRoutingRule(ruleId) { const removed = this.routingRules.delete(ruleId); if (removed) { logger_1.default.debug(`Removed routing rule: ${ruleId}`); this.emit('routing_rule_removed', { ruleId }); } return removed; } /** * Shutdown the query router */ async shutdown() { try { logger_1.default.info('Shutting down Query Router'); if (this.metricsCollectionTimer) { clearInterval(this.metricsCollectionTimer); this.metricsCollectionTimer = undefined; } this.routingRules.clear(); this.performanceMetrics.clear(); logger_1.default.info('Query Router shutdown completed'); this.emit('shutdown'); } catch (error) { logger_1.default.error('Query Router shutdown failed:', error); throw error; } } // Private helper methods initializeRoutingRules() { // Default routing rules based on query types // Simple lookups go to cache first, then in-memory this.routingRules.set('simple_lookup', { condition: (req) => req.type === types_1.QueryType.SIMPLE_LOOKUP, primaryLayer: types_1.StorageLayer.CACHE, fallbackLayers: [types_1.StorageLayer.IN_MEMORY, types_1.StorageLayer.NEO4J], priority: 1 }); // Complex graph queries go to Neo4j this.routingRules.set('graph_traversal', { condition: (req) => req.type === types_1.QueryType.GRAPH_TRAVERSAL, primaryLayer: types_1.StorageLayer.NEO4J, fallbackLayers: [types_1.StorageLayer.IN_MEMORY], priority: 1 }); // Full-text search goes to appropriate layer this.routingRules.set('full_text_search', { condition: (req) => req.type === types_1.QueryType.FULL_TEXT_SEARCH, primaryLayer: types_1.StorageLayer.NEO4J, fallbackLayers: [types_1.StorageLayer.RDF_FILES], priority: 1 }); // Aggregation queries go to Neo4j this.routingRules.set('aggregation', { condition: (req) => req.type === types_1.QueryType.AGGREGATION, primaryLayer: types_1.StorageLayer.NEO4J, fallbackLayers: [types_1.StorageLayer.IN_MEMORY], priority: 1 }); // Pattern matching goes to in-memory for speed this.routingRules.set('pattern_matching', { condition: (req) => req.type === types_1.QueryType.PATTERN_MATCHING, primaryLayer: types_1.StorageLayer.IN_MEMORY, fallbackLayers: [types_1.StorageLayer.NEO4J], priority: 1 }); } initializeMetrics() { for (const layer of Object.values(types_1.StorageLayer)) { this.performanceMetrics.set(layer, this.createEmptyMetrics()); } } createEmptyMetrics() { return { totalQueries: 0, successfulQueries: 0, failedQueries: 0, totalLatency: 0, averageLatency: 0, minLatency: 0, maxLatency: 0, successRate: 0, totalResultSize: 0, averageResultSize: 0 }; } setupMetricsCollection() { this.metricsCollectionTimer = setInterval(() => { this.collectAndEmitMetrics(); }, 30000); // Collect every 30 seconds // Ensure timer doesn't keep Node.js process alive this.metricsCollectionTimer.unref(); } collectAndEmitMetrics() { const allMetrics = this.getPerformanceMetrics(); this.emit('metrics_collected', { metrics: allMetrics, timestamp: new Date() }); } analyzeQuery(request) { const query = request.query.toLowerCase(); // Analyze query complexity let complexity = types_1.QueryComplexity.LOW; if (query.includes('join') || query.includes('match') && query.includes('where')) { complexity = types_1.QueryComplexity.MEDIUM; } if (query.includes('collect') || query.includes('count') || query.includes('sum') || query.includes('avg') || query.includes('max') || query.includes('min')) { complexity = types_1.QueryComplexity.HIGH; } // Estimate result size based on query patterns let estimatedResultSize = 100; // Default if (query.includes('limit')) { const limitMatch = query.match(/limit\s+(\d+)/); if (limitMatch) { estimatedResultSize = parseInt(limitMatch[1]); } } else if (query.includes('*') || query.includes('all')) { estimatedResultSize = 10000; // Large result set } // Check for joins and aggregations const requiresJoins = query.includes('join') || (query.includes('match') && query.includes('-')); const requiresAggregation = query.includes('collect') || query.includes('count') || query.includes('sum') || query.includes('avg'); // Determine if cacheable const isCacheable = !query.includes('now()') && !query.includes('rand()') && !query.includes('timestamp') && complexity !== types_1.QueryComplexity.HIGH; return { complexity, estimatedResultSize, requiresJoins, requiresAggregation, isCacheable }; } selectOptimalLayer(analysis, request) { // Check custom routing rules first for (const [ruleId, rule] of this.routingRules) { if (rule.condition(request)) { logger_1.default.debug(`Query matched routing rule: ${ruleId}`); return rule.primaryLayer; } } // Default routing logic based on analysis if (analysis.isCacheable && analysis.complexity === types_1.QueryComplexity.LOW) { return types_1.StorageLayer.CACHE; } if (analysis.requiresAggregation || analysis.complexity === types_1.QueryComplexity.HIGH) { return types_1.StorageLayer.NEO4J; } if (analysis.requiresJoins || request.type === types_1.QueryType.GRAPH_TRAVERSAL) { return types_1.StorageLayer.NEO4J; } if (analysis.estimatedResultSize < 1000 && analysis.complexity === types_1.QueryComplexity.LOW) { return types_1.StorageLayer.IN_MEMORY; } // Default to Neo4j for complex queries return types_1.StorageLayer.NEO4J; } getFallbackLayers(primaryLayer, analysis) { const fallbacks = []; switch (primaryLayer) { case types_1.StorageLayer.CACHE: fallbacks.push(types_1.StorageLayer.IN_MEMORY, types_1.StorageLayer.NEO4J); break; case types_1.StorageLayer.IN_MEMORY: fallbacks.push(types_1.StorageLayer.NEO4J); if (analysis.isCacheable) { fallbacks.unshift(types_1.StorageLayer.CACHE); } break; case types_1.StorageLayer.NEO4J: if (analysis.complexity === types_1.QueryComplexity.LOW) { fallbacks.push(types_1.StorageLayer.IN_MEMORY); } break; case types_1.StorageLayer.RDF_FILES: fallbacks.push(types_1.StorageLayer.NEO4J, types_1.StorageLayer.IN_MEMORY); break; } return fallbacks; } generateRoutingReasoning(layer, analysis) { const reasons = []; switch (layer) { case types_1.StorageLayer.CACHE: reasons.push('Query is cacheable and has low complexity'); break; case types_1.StorageLayer.IN_MEMORY: reasons.push('Query has low complexity and small result set'); break; case types_1.StorageLayer.NEO4J: if (analysis.requiresAggregation) { reasons.push('Query requires aggregation operations'); } if (analysis.requiresJoins) { reasons.push('Query requires complex joins'); } if (analysis.complexity === types_1.QueryComplexity.HIGH) { reasons.push('Query has high complexity'); } break; case types_1.StorageLayer.RDF_FILES: reasons.push('Query requires direct RDF file access'); break; } return reasons.join('; '); } estimateLatency(layer, analysis) { const metrics = this.performanceMetrics.get(layer); const baseLatency = metrics?.averageLatency || this.getDefaultLatency(layer); // Adjust based on complexity let multiplier = 1; switch (analysis.complexity) { case types_1.QueryComplexity.LOW: multiplier = 1; break; case types_1.QueryComplexity.MEDIUM: multiplier = 2; break; case types_1.QueryComplexity.HIGH: multiplier = 4; break; } // Adjust based on result size if (analysis.estimatedResultSize > 1000) { multiplier *= 1.5; } return Math.round(baseLatency * multiplier); } getDefaultLatency(layer) { switch (layer) { case types_1.StorageLayer.CACHE: return 1; // 1ms case types_1.StorageLayer.IN_MEMORY: return 5; // 5ms case types_1.StorageLayer.NEO4J: return 50; // 50ms case types_1.StorageLayer.RDF_FILES: return 100; // 100ms default: return 50; } } determineCacheStrategy(analysis) { if (!analysis.isCacheable) { return 'none'; } if (analysis.complexity === types_1.QueryComplexity.LOW && analysis.estimatedResultSize < 100) { return 'long'; } if (analysis.complexity === types_1.QueryComplexity.MEDIUM) { return 'medium'; } return 'short'; } generateQueryId(request) { const hash = require('crypto') .createHash('md5') .update(request.query + JSON.stringify(request.parameters || {})) .digest('hex'); return `query_${hash.substring(0, 8)}`; } updateRoutingMetrics(decision, routingTime) { // This could be expanded to track routing-specific metrics logger_1.default.debug(`Query routed to ${decision.primaryLayer} in ${routingTime}ms`); } } exports.QueryRouter = QueryRouter; exports.default = QueryRouter; //# sourceMappingURL=QueryRouter.js.map