UNPKG

@vasoyaprince14/sql-analyzer

Version:

🚀 Enhanced SQL database analyzer with AI-powered insights, comprehensive security analysis, RLS policy auditing, and beautiful HTML reports

230 lines • 8.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryBuilder = void 0; class QueryBuilder { constructor(client) { this.client = client; } /** * Build an optimized SQL query from options */ async buildQuery(options) { const { table, columns, where, joins, orderBy, groupBy, having, limit, offset } = options; let sql = 'SELECT '; // Build SELECT clause if (columns && columns.length > 0) { sql += columns.join(', '); } else { sql += '*'; } sql += ` FROM ${table}`; // Build JOIN clauses if (joins && joins.length > 0) { for (const join of joins) { sql += ` ${join.type} JOIN ${join.table} ON ${join.on}`; } } // Build WHERE clause if (where && Object.keys(where).length > 0) { const conditions = Object.entries(where).map(([key, value]) => { if (typeof value === 'string') { return `${key} = '${value}'`; } else if (Array.isArray(value)) { return `${key} IN (${value.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ')})`; } else { return `${key} = ${value}`; } }); sql += ` WHERE ${conditions.join(' AND ')}`; } // Build GROUP BY clause if (groupBy && groupBy.length > 0) { sql += ` GROUP BY ${groupBy.join(', ')}`; } // Build HAVING clause if (having) { sql += ` HAVING ${having}`; } // Build ORDER BY clause if (orderBy && orderBy.length > 0) { const orderClauses = orderBy.map(order => `${order.column} ${order.direction}`); sql += ` ORDER BY ${orderClauses.join(', ')}`; } // Build LIMIT and OFFSET if (limit) { sql += ` LIMIT ${limit}`; } if (offset) { sql += ` OFFSET ${offset}`; } // Generate explanation and optimization tips const explanation = this.generateExplanation(options); const optimizationTips = await this.generateOptimizationTips(options); return { sql, explanation, optimizationTips }; } /** * Generate suggested queries based on table analysis */ async suggestQueries(tableName) { try { // Get table columns const columnsResult = await this.client.query(` SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public' ORDER BY ordinal_position `, [tableName]); const columns = columnsResult.rows; const suggestions = []; // Find common column patterns const idColumn = columns.find(col => col.column_name === 'id'); const nameColumn = columns.find(col => col.column_name.includes('name')); const emailColumn = columns.find(col => col.column_name.includes('email')); const statusColumn = columns.find(col => col.column_name.includes('status')); const createdColumn = columns.find(col => col.column_name.includes('created')); // Basic queries suggestions.push(`SELECT * FROM ${tableName} LIMIT 10`); if (idColumn) { suggestions.push(`SELECT COUNT(*) FROM ${tableName}`); } if (nameColumn) { suggestions.push(`SELECT ${nameColumn.column_name} FROM ${tableName} ORDER BY ${nameColumn.column_name}`); } if (emailColumn) { suggestions.push(`SELECT DISTINCT ${emailColumn.column_name} FROM ${tableName}`); } if (statusColumn) { suggestions.push(`SELECT ${statusColumn.column_name}, COUNT(*) FROM ${tableName} GROUP BY ${statusColumn.column_name}`); } if (createdColumn) { suggestions.push(`SELECT * FROM ${tableName} ORDER BY ${createdColumn.column_name} DESC LIMIT 5`); } // Find foreign key relationships const fkResult = await this.client.query(` SELECT tc.column_name, ccu.table_name AS foreign_table_name FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 `, [tableName]); // Suggest JOIN queries fkResult.rows.forEach(fk => { suggestions.push(`SELECT * FROM ${tableName} t1 JOIN ${fk.foreign_table_name} t2 ON t1.${fk.column_name} = t2.id`); }); return suggestions; } catch (error) { return [`SELECT * FROM ${tableName} LIMIT 10`]; } } /** * Generate explanation for the query options */ generateExplanation(options) { let explanation = `This query retrieves data from the '${options.table}' table`; if (options.columns && options.columns.length > 0) { explanation += ` selecting specific columns: ${options.columns.join(', ')}`; } else { explanation += ' selecting all columns (*)'; } if (options.joins && options.joins.length > 0) { explanation += ` with ${options.joins.length} JOIN(s)`; } if (options.where) { const conditionCount = Object.keys(options.where).length; explanation += ` filtered by ${conditionCount} condition(s)`; } if (options.groupBy && options.groupBy.length > 0) { explanation += ` grouped by ${options.groupBy.join(', ')}`; } if (options.orderBy && options.orderBy.length > 0) { explanation += ` ordered by ${options.orderBy.map(o => `${o.column} ${o.direction}`).join(', ')}`; } if (options.limit) { explanation += ` limited to ${options.limit} rows`; } return explanation + '.'; } /** * Generate optimization tips for the query */ async generateOptimizationTips(options) { const tips = []; // Check for SELECT * if (!options.columns || options.columns.length === 0) { tips.push('Consider specifying only the columns you need instead of SELECT *'); } // Check for missing WHERE clause if (!options.where || Object.keys(options.where).length === 0) { tips.push('Add WHERE clauses to limit the data returned'); } // Check for ORDER BY without LIMIT if (options.orderBy && options.orderBy.length > 0 && !options.limit) { tips.push('Consider adding LIMIT when using ORDER BY to avoid sorting large result sets'); } // Check for potential index needs if (options.where) { Object.keys(options.where).forEach(column => { tips.push(`Consider creating an index on '${column}' if this query runs frequently`); }); } // Check JOIN efficiency if (options.joins && options.joins.length > 3) { tips.push('Multiple JOINs detected - consider if all are necessary'); } // Check GROUP BY efficiency if (options.groupBy && options.groupBy.length > 0) { tips.push('Ensure GROUP BY columns are indexed for better performance'); } return tips; } /** * Get available tables for query building */ async getAvailableTables() { try { const result = await this.client.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name `); return result.rows.map(row => row.table_name); } catch (error) { return []; } } /** * Get available columns for a table */ async getAvailableColumns(tableName) { try { const result = await this.client.query(` SELECT column_name as name, data_type as type FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public' ORDER BY ordinal_position `, [tableName]); return result.rows; } catch (error) { return []; } } } exports.QueryBuilder = QueryBuilder; //# sourceMappingURL=query-builder.js.map