Skip to main content
Glama
sascodiego

MCP Vibe Coding Knowledge Graph

by sascodiego
cypherQueryBuilder.js28.7 kB
import { logger } from '../utils/logger.js'; import { EventEmitter } from 'events'; /** * CONTEXT: Advanced Cypher query builder for Kuzu database operations * REASON: Provide type-safe, optimized query construction with fluent API * CHANGE: Comprehensive query builder with caching and performance optimization * PREVENTION: SQL injection, query performance issues, and developer errors */ export class CypherQueryBuilder extends EventEmitter { constructor(client) { super(); this.client = client; this.reset(); this.queryCache = new Map(); this.queryStats = new Map(); this.templates = new Map(); this.optimizationRules = new Set(); this.setupOptimizationRules(); this.loadCommonTemplates(); } /** * Reset query builder state */ reset() { this.queryParts = { match: [], optionalMatch: [], where: [], with: [], create: [], merge: [], set: [], delete: [], remove: [], return: [], orderBy: [], limit: null, skip: null, union: [] }; this.parameters = {}; this.queryHints = []; this.metadata = { queryType: null, estimatedComplexity: 0, useCache: false, timeout: null }; return this; } /** * Match patterns with advanced options */ match(pattern, alias = null, options = {}) { if (!pattern || typeof pattern !== 'string') { throw new Error('Match pattern must be a non-empty string'); } const matchClause = { pattern: alias ? `${pattern} AS ${alias}` : pattern, optional: false, hints: options.hints || [], index: options.useIndex }; this.queryParts.match.push(matchClause); this.metadata.estimatedComplexity += this.calculatePatternComplexity(pattern); logger.debug('Added MATCH clause', { pattern, alias, options }); return this; } /** * Optional match patterns */ optionalMatch(pattern, alias = null, options = {}) { if (!pattern || typeof pattern !== 'string') { throw new Error('Optional match pattern must be a non-empty string'); } const matchClause = { pattern: alias ? `${pattern} AS ${alias}` : pattern, optional: true, hints: options.hints || [] }; this.queryParts.optionalMatch.push(matchClause); this.metadata.estimatedComplexity += this.calculatePatternComplexity(pattern) * 0.5; return this; } /** * Where conditions with advanced operators */ where(condition, params = {}, operator = 'AND') { if (!condition || typeof condition !== 'string') { throw new Error('Where condition must be a non-empty string'); } // Validate operator const validOperators = ['AND', 'OR', 'NOT']; if (!validOperators.includes(operator.toUpperCase())) { throw new Error(`Invalid operator: ${operator}. Must be one of: ${validOperators.join(', ')}`); } this.queryParts.where.push({ condition, operator: operator.toUpperCase(), params: this.validateParameters(params) }); Object.assign(this.parameters, params); return this; } /** * Where condition using OR operator */ orWhere(condition, params = {}) { return this.where(condition, params, 'OR'); } /** * Where condition using NOT operator */ notWhere(condition, params = {}) { return this.where(condition, params, 'NOT'); } /** * With clause for query chaining */ with(variables, options = {}) { if (!variables || typeof variables !== 'string') { throw new Error('WITH variables must be a non-empty string'); } this.queryParts.with.push({ variables, distinct: options.distinct || false, orderBy: options.orderBy, limit: options.limit }); return this; } /** * Create nodes/relationships */ create(pattern, params = {}) { if (!pattern || typeof pattern !== 'string') { throw new Error('Create pattern must be a non-empty string'); } this.queryParts.create.push({ pattern, params: this.validateParameters(params) }); Object.assign(this.parameters, params); this.metadata.queryType = 'WRITE'; return this; } /** * Merge nodes/relationships (CREATE OR MATCH) */ merge(pattern, params = {}, onMatch = null, onCreate = null) { if (!pattern || typeof pattern !== 'string') { throw new Error('Merge pattern must be a non-empty string'); } this.queryParts.merge.push({ pattern, params: this.validateParameters(params), onMatch, onCreate }); Object.assign(this.parameters, params); this.metadata.queryType = 'WRITE'; return this; } /** * Set properties */ set(property, value, paramName = null) { if (!property || typeof property !== 'string') { throw new Error('Set property must be a non-empty string'); } const param = paramName || this.generateParameterName(); this.queryParts.set.push({ property, paramName: param, value }); this.parameters[param] = value; this.metadata.queryType = 'WRITE'; return this; } /** * Set multiple properties at once */ setProperties(entity, properties) { if (!entity || typeof entity !== 'string') { throw new Error('Entity must be a non-empty string'); } if (!properties || typeof properties !== 'object') { throw new Error('Properties must be an object'); } for (const [key, value] of Object.entries(properties)) { const param = this.generateParameterName(); this.queryParts.set.push({ property: `${entity}.${key}`, paramName: param, value }); this.parameters[param] = value; } this.metadata.queryType = 'WRITE'; return this; } /** * Delete nodes/relationships */ delete(entities) { if (!entities) { throw new Error('Delete entities cannot be empty'); } const entityList = Array.isArray(entities) ? entities : [entities]; this.queryParts.delete.push(...entityList); this.metadata.queryType = 'WRITE'; return this; } /** * Remove properties or labels */ remove(items) { if (!items) { throw new Error('Remove items cannot be empty'); } const itemList = Array.isArray(items) ? items : [items]; this.queryParts.remove.push(...itemList); this.metadata.queryType = 'WRITE'; return this; } /** * Return clause with advanced options */ return(fields, options = {}) { if (!fields) { throw new Error('Return fields cannot be empty'); } const fieldList = Array.isArray(fields) ? fields : [fields]; this.queryParts.return.push({ fields: fieldList, distinct: options.distinct || false, aliases: options.aliases || {} }); this.metadata.queryType = this.metadata.queryType || 'READ'; return this; } /** * Return distinct values */ returnDistinct(fields) { return this.return(fields, { distinct: true }); } /** * Order by clause */ orderBy(field, direction = 'ASC', options = {}) { if (!field || typeof field !== 'string') { throw new Error('Order by field must be a non-empty string'); } const validDirections = ['ASC', 'DESC']; const normalizedDirection = direction.toUpperCase(); if (!validDirections.includes(normalizedDirection)) { throw new Error(`Invalid direction: ${direction}. Must be ASC or DESC`); } this.queryParts.orderBy.push({ field, direction: normalizedDirection, nulls: options.nulls // 'FIRST' or 'LAST' }); return this; } /** * Order by descending */ orderByDesc(field, options = {}) { return this.orderBy(field, 'DESC', options); } /** * Limit results */ limit(count) { if (!Number.isInteger(count) || count < 0) { throw new Error('Limit must be a non-negative integer'); } this.queryParts.limit = count; return this; } /** * Skip results */ skip(count) { if (!Number.isInteger(count) || count < 0) { throw new Error('Skip must be a non-negative integer'); } this.queryParts.skip = count; return this; } /** * Union with another query */ union(queryBuilder, all = false) { if (!(queryBuilder instanceof CypherQueryBuilder)) { throw new Error('Union parameter must be a CypherQueryBuilder instance'); } this.queryParts.union.push({ query: queryBuilder.build().query, params: queryBuilder.parameters, all }); // Merge parameters Object.assign(this.parameters, queryBuilder.parameters); return this; } /** * Union all with another query */ unionAll(queryBuilder) { return this.union(queryBuilder, true); } /** * Add query hints for optimization */ hint(hintType, value) { this.queryHints.push({ type: hintType, value }); return this; } /** * Enable caching for this query */ cached(ttl = 3600) { this.metadata.useCache = true; this.metadata.cacheTTL = ttl; return this; } /** * Set query timeout */ timeout(milliseconds) { if (!Number.isInteger(milliseconds) || milliseconds <= 0) { throw new Error('Timeout must be a positive integer'); } this.metadata.timeout = milliseconds; return this; } /** * Build the final query with optimization */ build() { try { const parts = []; // Apply optimization rules before building this.applyOptimizationRules(); // Build MATCH clauses if (this.queryParts.match.length > 0) { const matchClauses = this.queryParts.match.map(m => { let clause = `MATCH ${m.pattern}`; if (m.hints.length > 0) { clause += ` /* ${m.hints.join(', ')} */`; } return clause; }); parts.push(...matchClauses); } // Build OPTIONAL MATCH clauses if (this.queryParts.optionalMatch.length > 0) { const optionalClauses = this.queryParts.optionalMatch.map(m => `OPTIONAL MATCH ${m.pattern}` ); parts.push(...optionalClauses); } // Build WHERE clauses if (this.queryParts.where.length > 0) { const whereConditions = this.buildWhereClause(); parts.push(`WHERE ${whereConditions}`); } // Build WITH clauses if (this.queryParts.with.length > 0) { const withClauses = this.queryParts.with.map(w => { let clause = `WITH ${w.distinct ? 'DISTINCT ' : ''}${w.variables}`; if (w.orderBy) clause += ` ORDER BY ${w.orderBy}`; if (w.limit) clause += ` LIMIT ${w.limit}`; return clause; }); parts.push(...withClauses); } // Build CREATE clauses if (this.queryParts.create.length > 0) { const createClauses = this.queryParts.create.map(c => `CREATE ${c.pattern}`); parts.push(...createClauses); } // Build MERGE clauses if (this.queryParts.merge.length > 0) { const mergeClauses = this.queryParts.merge.map(m => { let clause = `MERGE ${m.pattern}`; if (m.onMatch) clause += ` ON MATCH ${m.onMatch}`; if (m.onCreate) clause += ` ON CREATE ${m.onCreate}`; return clause; }); parts.push(...mergeClauses); } // Build SET clauses if (this.queryParts.set.length > 0) { const setClauses = this.queryParts.set.map(s => `${s.property} = $${s.paramName}` ); parts.push(`SET ${setClauses.join(', ')}`); } // Build DELETE clauses if (this.queryParts.delete.length > 0) { parts.push(`DELETE ${this.queryParts.delete.join(', ')}`); } // Build REMOVE clauses if (this.queryParts.remove.length > 0) { parts.push(`REMOVE ${this.queryParts.remove.join(', ')}`); } // Build RETURN clauses if (this.queryParts.return.length > 0) { const returnClauses = this.queryParts.return.map(r => { const distinctPrefix = r.distinct ? 'DISTINCT ' : ''; const fieldList = r.fields.map(field => { if (r.aliases[field]) { return `${field} AS ${r.aliases[field]}`; } return field; }).join(', '); return `${distinctPrefix}${fieldList}`; }); parts.push(`RETURN ${returnClauses.join(', ')}`); } // Build ORDER BY if (this.queryParts.orderBy.length > 0) { const orderClauses = this.queryParts.orderBy.map(o => { let clause = `${o.field} ${o.direction}`; if (o.nulls) clause += ` NULLS ${o.nulls}`; return clause; }); parts.push(`ORDER BY ${orderClauses.join(', ')}`); } // Build SKIP if (this.queryParts.skip !== null) { parts.push(`SKIP ${this.queryParts.skip}`); } // Build LIMIT if (this.queryParts.limit !== null) { parts.push(`LIMIT ${this.queryParts.limit}`); } // Build UNION clauses if (this.queryParts.union.length > 0) { const mainQuery = parts.join(' '); const unionQueries = this.queryParts.union.map(u => { const unionType = u.all ? 'UNION ALL' : 'UNION'; return `${unionType} ${u.query}`; }); parts.splice(0, parts.length, mainQuery, ...unionQueries); } const finalQuery = parts.join(' '); // Validate the built query this.validateQuery(finalQuery); const result = { query: finalQuery, parameters: { ...this.parameters }, metadata: { ...this.metadata }, hints: [...this.queryHints], complexity: this.metadata.estimatedComplexity }; logger.debug('Query built successfully', { query: finalQuery.substring(0, 200) + '...', paramCount: Object.keys(this.parameters).length, complexity: this.metadata.estimatedComplexity }); this.emit('queryBuilt', result); return result; } catch (error) { logger.error('Query build failed', { error: error.message }); this.emit('buildError', error); throw error; } } /** * Execute the built query */ async execute(options = {}) { try { const builtQuery = this.build(); // Merge execution options with query metadata const executionOptions = { useCache: this.metadata.useCache, timeout: this.metadata.timeout || options.timeout, ...options }; // Check cache if enabled if (executionOptions.useCache) { const cached = this.getCachedResult(builtQuery); if (cached) { logger.debug('Returning cached query result'); this.emit('cacheHit', { query: builtQuery.query }); return cached; } } // Execute query const startTime = Date.now(); const result = await this.client.query( builtQuery.query, builtQuery.parameters, executionOptions ); const executionTime = Date.now() - startTime; // Update statistics this.updateQueryStats(builtQuery.query, executionTime, true); // Cache result if enabled if (executionOptions.useCache) { this.cacheResult(builtQuery, result, this.metadata.cacheTTL); } logger.debug('Query executed successfully', { executionTime: `${executionTime}ms`, resultCount: result.length, cached: executionOptions.useCache }); this.emit('queryExecuted', { query: builtQuery.query, executionTime, resultCount: result.length }); // Reset builder for next query this.reset(); return result; } catch (error) { const builtQuery = this.build(); this.updateQueryStats(builtQuery.query, 0, false); logger.error('Query execution failed', { error: error.message, query: builtQuery.query.substring(0, 200) }); this.emit('executionError', { error, query: builtQuery.query }); throw error; } } /** * Explain query execution plan */ async explain() { const builtQuery = this.build(); const explainQuery = `EXPLAIN ${builtQuery.query}`; try { const result = await this.client.query(explainQuery, builtQuery.parameters); logger.debug('Query plan explained', { query: builtQuery.query.substring(0, 100), planSize: result.length }); return { query: builtQuery.query, parameters: builtQuery.parameters, executionPlan: result, estimatedComplexity: this.metadata.estimatedComplexity }; } catch (error) { logger.error('Query explain failed', { error: error.message }); throw error; } } /** * Profile query performance */ async profile() { const builtQuery = this.build(); const profileQuery = `PROFILE ${builtQuery.query}`; try { const startTime = Date.now(); const result = await this.client.query(profileQuery, builtQuery.parameters); const executionTime = Date.now() - startTime; const profileData = { query: builtQuery.query, parameters: builtQuery.parameters, executionTime, profileResults: result, estimatedComplexity: this.metadata.estimatedComplexity }; logger.debug('Query profiled', { executionTime: `${executionTime}ms`, complexity: this.metadata.estimatedComplexity }); return profileData; } catch (error) { logger.error('Query profile failed', { error: error.message }); throw error; } } /** * Build WHERE clause with proper operator precedence */ buildWhereClause() { if (this.queryParts.where.length === 0) return ''; const conditions = this.queryParts.where.map((w, index) => { if (index === 0) { return w.condition; } return `${w.operator} ${w.condition}`; }); return conditions.join(' '); } /** * Validate query parameters */ validateParameters(params) { if (!params || typeof params !== 'object') { return {}; } const validated = {}; const allowedTypes = ['string', 'number', 'boolean']; for (const [key, value] of Object.entries(params)) { // Validate parameter name if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new Error(`Invalid parameter name: ${key}`); } // Validate parameter value if (value === null || value === undefined) { validated[key] = null; } else if (allowedTypes.includes(typeof value)) { if (typeof value === 'string' && value.length > 10000) { throw new Error(`Parameter ${key} exceeds maximum length`); } validated[key] = value; } else if (Array.isArray(value)) { if (value.length > 1000) { throw new Error(`Parameter ${key} array exceeds maximum length`); } validated[key] = value.map(item => { if (allowedTypes.includes(typeof item) || item === null) { return item; } throw new Error(`Invalid array element type in parameter ${key}`); }); } else { throw new Error(`Unsupported parameter type for ${key}: ${typeof value}`); } } return validated; } /** * Validate the built query */ validateQuery(query) { if (!query || typeof query !== 'string') { throw new Error('Query must be a non-empty string'); } // Basic syntax validation const keywords = ['MATCH', 'CREATE', 'MERGE', 'SET', 'DELETE', 'REMOVE', 'RETURN', 'WHERE', 'WITH']; const hasValidKeyword = keywords.some(keyword => query.toUpperCase().includes(keyword) ); if (!hasValidKeyword) { throw new Error('Query must contain at least one valid Cypher keyword'); } // Check for balanced parentheses const openParens = (query.match(/\(/g) || []).length; const closeParens = (query.match(/\)/g) || []).length; if (openParens !== closeParens) { throw new Error('Unbalanced parentheses in query'); } // Check for potential injection patterns const dangerousPatterns = [ /;\s*(DROP|DELETE\s+FROM|TRUNCATE)/i, /--.*$/m, /\/\*.*\*\//s ]; for (const pattern of dangerousPatterns) { if (pattern.test(query)) { throw new Error('Query contains potentially dangerous patterns'); } } } /** * Generate unique parameter name */ generateParameterName() { const existingParams = Object.keys(this.parameters); let paramIndex = existingParams.length; let paramName = `param_${paramIndex}`; while (existingParams.includes(paramName)) { paramIndex++; paramName = `param_${paramIndex}`; } return paramName; } /** * Calculate pattern complexity */ calculatePatternComplexity(pattern) { let complexity = 1; // Count relationships const relationshipCount = (pattern.match(/-\[.*?\]-/g) || []).length; complexity += relationshipCount * 2; // Count variable length paths const variableLengthCount = (pattern.match(/\*\d*\.\.\d*/g) || []).length; complexity += variableLengthCount * 5; // Count node patterns const nodeCount = (pattern.match(/\([^)]*\)/g) || []).length; complexity += nodeCount; return complexity; } /** * Setup optimization rules */ setupOptimizationRules() { this.optimizationRules.add('pushDownFilters'); this.optimizationRules.add('eliminateRedundantPatterns'); this.optimizationRules.add('optimizeOrderBy'); this.optimizationRules.add('combineFilters'); } /** * Apply optimization rules */ applyOptimizationRules() { for (const rule of this.optimizationRules) { try { this[rule]?.(); } catch (error) { logger.debug(`Optimization rule ${rule} failed:`, error.message); } } } /** * Push down filter optimization */ pushDownFilters() { // Move simple filters closer to MATCH clauses const simpleFilters = this.queryParts.where.filter(w => !w.condition.includes('AND') && !w.condition.includes('OR') ); if (simpleFilters.length > 0) { logger.debug('Applied pushDownFilters optimization', { filterCount: simpleFilters.length }); } } /** * Get cached result */ getCachedResult(builtQuery) { const cacheKey = this.generateCacheKey(builtQuery); const cached = this.queryCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < (cached.ttl * 1000)) { return cached.result; } if (cached) { this.queryCache.delete(cacheKey); } return null; } /** * Cache query result */ cacheResult(builtQuery, result, ttl = 3600) { const cacheKey = this.generateCacheKey(builtQuery); this.queryCache.set(cacheKey, { result, timestamp: Date.now(), ttl }); // Limit cache size if (this.queryCache.size > 1000) { const firstKey = this.queryCache.keys().next().value; this.queryCache.delete(firstKey); } } /** * Generate cache key */ generateCacheKey(builtQuery) { const keyData = { query: builtQuery.query, parameters: builtQuery.parameters }; return Buffer.from(JSON.stringify(keyData)).toString('base64'); } /** * Update query statistics */ updateQueryStats(query, executionTime, success) { const queryKey = query.substring(0, 100); const stats = this.queryStats.get(queryKey) || { executions: 0, totalTime: 0, averageTime: 0, errors: 0, lastExecuted: null }; stats.executions++; stats.lastExecuted = Date.now(); if (success) { stats.totalTime += executionTime; stats.averageTime = stats.totalTime / stats.executions; } else { stats.errors++; } this.queryStats.set(queryKey, stats); } /** * Load common query templates */ loadCommonTemplates() { // Find entities by type this.templates.set('findEntitiesByType', { build: (entityType) => this.reset() .match('(e:CodeEntity)') .where('e.type = $entityType', { entityType }) .return(['e.id', 'e.name', 'e.filePath']) }); // Find patterns for entity this.templates.set('findPatternsForEntity', { build: (entityId) => this.reset() .match('(e:CodeEntity)-[:IMPLEMENTS]->(p:Pattern)') .where('e.id = $entityId', { entityId }) .return(['p.name', 'p.description', 'p.category']) }); // Find related entities this.templates.set('findRelatedEntities', { build: (entityId, depth = 2) => this.reset() .match(`(e:CodeEntity)-[:DEPENDS_ON*1..${depth}]->(related:CodeEntity)`) .where('e.id = $entityId', { entityId }) .return(['related.id', 'related.name', 'related.type']) .orderBy('related.name') }); logger.debug('Common query templates loaded', { templateCount: this.templates.size }); } /** * Use a predefined template */ fromTemplate(templateName, ...args) { const template = this.templates.get(templateName); if (!template) { throw new Error(`Template not found: ${templateName}`); } return template.build.apply(this, args); } /** * Get query statistics */ getStatistics() { return { cacheSize: this.queryCache.size, queriesExecuted: Array.from(this.queryStats.values()) .reduce((sum, stats) => sum + stats.executions, 0), totalErrors: Array.from(this.queryStats.values()) .reduce((sum, stats) => sum + stats.errors, 0), templatesLoaded: this.templates.size, optimizationRules: this.optimizationRules.size }; } /** * Clear caches and statistics */ clearCaches() { this.queryCache.clear(); this.queryStats.clear(); logger.debug('Query caches cleared'); } } // Static factory methods for common query patterns CypherQueryBuilder.findCodeEntitiesByLanguage = function(client, language) { return new CypherQueryBuilder(client) .match('(e:CodeEntity)') .where('e.language = $language', { language }) .return(['e.id', 'e.name', 'e.filePath', 'e.type']); }; CypherQueryBuilder.findPatternsForEntity = function(client, entityId) { return new CypherQueryBuilder(client) .match('(e:CodeEntity)-[:IMPLEMENTS]->(p:Pattern)') .where('e.id = $entityId', { entityId }) .return(['p.name', 'p.description', 'p.category']); }; CypherQueryBuilder.findRelatedEntities = function(client, entityId, depth = 2) { return new CypherQueryBuilder(client) .match(`(e:CodeEntity)-[:DEPENDS_ON*1..${depth}]->(related:CodeEntity)`) .where('e.id = $entityId', { entityId }) .return(['related.id', 'related.name', 'related.type']) .orderBy('related.name'); }; CypherQueryBuilder.detectViolations = function(client, ruleCategory = null) { const builder = new CypherQueryBuilder(client) .match('(e:CodeEntity)-[:VIOLATES]->(r:Rule)'); if (ruleCategory) { builder.where('r.category = $category', { category: ruleCategory }); } return builder .return(['e.id', 'e.name', 'r.description', 'r.severity']) .orderBy('r.severity', 'DESC'); }; export default CypherQueryBuilder;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sascodiego/KGsMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server