@vasoyaprince14/sql-analyzer
Version:
🚀 Enhanced SQL database analyzer with AI-powered insights, comprehensive security analysis, RLS policy auditing, and beautiful HTML reports
244 lines • 9.95 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexSuggestor = void 0;
class IndexSuggestor {
constructor(client) {
this.client = client;
}
/**
* Suggest indexes based on query analysis
*/
async suggestIndexes(result) {
const suggestions = [];
// Extract table and column information from the query
const queryInfo = this.extractQueryInfo(result.query);
// Generate suggestions based on WHERE clauses
const whereSuggestions = this.suggestIndexesForWhereClauses(queryInfo);
suggestions.push(...whereSuggestions);
// Generate suggestions based on JOIN conditions
const joinSuggestions = this.suggestIndexesForJoins(queryInfo);
suggestions.push(...joinSuggestions);
// Generate suggestions based on ORDER BY
const orderBySuggestions = this.suggestIndexesForOrderBy(queryInfo);
suggestions.push(...orderBySuggestions);
// Generate suggestions based on GROUP BY
const groupBySuggestions = this.suggestIndexesForGroupBy(queryInfo);
suggestions.push(...groupBySuggestions);
// Check for existing indexes to avoid duplicates
const existingIndexes = await this.getExistingIndexes(queryInfo.tables);
const filteredSuggestions = this.filterExistingIndexes(suggestions, existingIndexes);
return filteredSuggestions;
}
/**
* Extract table and column information from SQL query
*/
extractQueryInfo(sql) {
const normalizedSql = sql.toLowerCase();
// Extract table names (simplified)
const tableMatches = normalizedSql.match(/from\s+(\w+)|join\s+(\w+)/g);
const tables = tableMatches ?
tableMatches.map(match => match.replace(/from\s+|join\s+/, '')) : [];
// Extract WHERE conditions (simplified)
const whereMatches = normalizedSql.match(/(\w+)\.(\w+)\s*([=<>!]+)\s*[^,\s]+/g);
const whereColumns = whereMatches ?
whereMatches.map(match => {
const parts = match.match(/(\w+)\.(\w+)\s*([=<>!]+)/);
return parts ? {
table: parts[1],
column: parts[2],
operator: parts[3]
} : null;
}).filter((item) => item !== null) : [];
// Extract JOIN conditions (simplified)
const joinMatches = normalizedSql.match(/(\w+)\.(\w+)\s*=\s*(\w+)\.(\w+)/g);
const joinColumns = joinMatches ?
joinMatches.map(match => {
const parts = match.match(/(\w+)\.(\w+)\s*=\s*(\w+)\.(\w+)/);
return parts ? {
table: parts[1],
column: parts[2],
referencedTable: parts[3],
referencedColumn: parts[4]
} : null;
}).filter((item) => item !== null) : [];
// Extract ORDER BY columns (simplified)
const orderByMatches = normalizedSql.match(/order\s+by\s+(\w+)\.(\w+)(?:\s+(asc|desc))?/g);
const orderByColumns = orderByMatches ?
orderByMatches.map(match => {
const parts = match.match(/order\s+by\s+(\w+)\.(\w+)(?:\s+(asc|desc))?/);
return parts ? {
table: parts[1],
column: parts[2],
direction: parts[3] || 'asc'
} : null;
}).filter((item) => item !== null) : [];
// Extract GROUP BY columns (simplified)
const groupByMatches = normalizedSql.match(/group\s+by\s+(\w+)\.(\w+)/g);
const groupByColumns = groupByMatches ?
groupByMatches.map(match => {
const parts = match.match(/group\s+by\s+(\w+)\.(\w+)/);
return parts ? {
table: parts[1],
column: parts[2]
} : null;
}).filter((item) => item !== null) : [];
return {
tables,
whereColumns,
joinColumns,
orderByColumns,
groupByColumns
};
}
/**
* Suggest indexes for WHERE clause columns
*/
suggestIndexesForWhereClauses(queryInfo) {
const suggestions = [];
for (const whereCol of queryInfo.whereColumns) {
suggestions.push({
type: 'index',
priority: 'high',
title: `Index for WHERE clause on ${whereCol.table}.${whereCol.column}`,
description: `Create index for filtering on ${whereCol.table}.${whereCol.column}`,
sql: `CREATE INDEX idx_${whereCol.table}_${whereCol.column} ON ${whereCol.table}(${whereCol.column});`,
impact: 'high',
effort: 'low'
});
}
return suggestions;
}
/**
* Suggest indexes for JOIN conditions
*/
suggestIndexesForJoins(queryInfo) {
const suggestions = [];
for (const joinCol of queryInfo.joinColumns) {
suggestions.push({
type: 'index',
priority: 'high',
title: `Index for JOIN on ${joinCol.table}.${joinCol.column}`,
description: `Create index for join condition on ${joinCol.table}.${joinCol.column}`,
sql: `CREATE INDEX idx_${joinCol.table}_${joinCol.column} ON ${joinCol.table}(${joinCol.column});`,
impact: 'high',
effort: 'low'
});
}
return suggestions;
}
/**
* Suggest indexes for ORDER BY columns
*/
suggestIndexesForOrderBy(queryInfo) {
const suggestions = [];
for (const orderCol of queryInfo.orderByColumns) {
suggestions.push({
type: 'index',
priority: 'medium',
title: `Index for ORDER BY on ${orderCol.table}.${orderCol.column}`,
description: `Create index for sorting on ${orderCol.table}.${orderCol.column}`,
sql: `CREATE INDEX idx_${orderCol.table}_${orderCol.column} ON ${orderCol.table}(${orderCol.column});`,
impact: 'medium',
effort: 'low'
});
}
return suggestions;
}
/**
* Suggest indexes for GROUP BY columns
*/
suggestIndexesForGroupBy(queryInfo) {
const suggestions = [];
for (const groupCol of queryInfo.groupByColumns) {
suggestions.push({
type: 'index',
priority: 'medium',
title: `Index for GROUP BY on ${groupCol.table}.${groupCol.column}`,
description: `Create index for grouping on ${groupCol.table}.${groupCol.column}`,
sql: `CREATE INDEX idx_${groupCol.table}_${groupCol.column} ON ${groupCol.table}(${groupCol.column});`,
impact: 'medium',
effort: 'low'
});
}
return suggestions;
}
/**
* Get existing indexes for tables
*/
async getExistingIndexes(tables) {
const existingIndexes = [];
try {
for (const table of tables) {
const result = await this.client.query(`
SELECT
i.relname as index_name,
a.attname as column_name
FROM pg_class t
JOIN pg_index ix ON t.oid = ix.indrelid
JOIN pg_class i ON ix.indexrelid = i.oid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
WHERE t.relname = $1
`, [table]);
result.rows.forEach(row => {
existingIndexes.push({
table,
column: row.column_name
});
});
}
}
catch (error) {
console.warn('Failed to get existing indexes:', error);
}
return existingIndexes;
}
/**
* Filter out suggestions for indexes that already exist
*/
filterExistingIndexes(suggestions, existingIndexes) {
return suggestions.filter(suggestion => {
if (suggestion.sql) {
const match = suggestion.sql.match(/CREATE INDEX.*ON\s+(\w+)\((\w+)\)/);
if (match) {
const table = match[1];
const column = match[2];
return !existingIndexes.some(existing => existing.table === table && existing.column === column);
}
}
return true;
});
}
/**
* Suggest composite indexes for multiple conditions
*/
async suggestCompositeIndexes(result) {
const queryInfo = this.extractQueryInfo(result.query);
const suggestions = [];
// Group WHERE conditions by table
const whereByTable = {};
queryInfo.whereColumns.forEach(col => {
if (!whereByTable[col.table]) {
whereByTable[col.table] = [];
}
whereByTable[col.table].push(col.column);
});
// Suggest composite indexes for tables with multiple WHERE conditions
for (const [table, columns] of Object.entries(whereByTable)) {
if (columns.length > 1) {
const columnList = columns.join(', ');
suggestions.push({
type: 'index',
priority: 'high',
title: `Composite index for ${table} on ${columnList}`,
description: `Create composite index for multiple WHERE conditions on ${table}`,
sql: `CREATE INDEX idx_${table}_${columns.join('_')} ON ${table}(${columnList});`,
impact: 'high',
effort: 'low'
});
}
}
return suggestions;
}
}
exports.IndexSuggestor = IndexSuggestor;
//# sourceMappingURL=index-suggestor.js.map