agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
130 lines (109 loc) • 3.96 kB
JavaScript
/**
* @file N+1 query detector
* @description Detects potential N+1 query patterns with context awareness
*/
const { detectExecutionContext } = require('./contextDetector');
/**
* Detects potential N+1 query patterns
* @param {string} content - File content
* @param {string} filePath - Path to the file
* @returns {Array} Array of N+1 query issues
*/
function detectNPlusOne(content, filePath) {
const issues = [];
// Get execution context to avoid false positives
const context = detectExecutionContext(content, filePath);
// Skip CLI tools - they rarely have database operations
if (context.type === 'cli' || context.isCLI) {
return issues;
}
// Skip test files unless they have clear database setup
if (context.type === 'test' && !hasTestDatabaseSetup(content)) {
return issues;
}
const loops = [
/for\s*\(\s*(let|const|var)\s+.+\)/,
/\.forEach\s*\(/,
/\.map\s*\(/,
/while\s*\(/
];
// More specific database patterns to reduce false positives
const dbPatterns = [
// Direct database calls
/db\.query\s*\(/,
/database\.query\s*\(/,
/connection\.query\s*\(/,
// ORM patterns with context
/Model\.(find|findOne|findAll)\s*\(/,
/\.find\s*\(\s*{.*}\s*\)/, // Object-based find (not array.find())
/\.findOne\s*\(\s*{.*}\s*\)/,
/\.findById\s*\(/,
/\.findWhere\s*\(/,
// SQL patterns
/SELECT\s+.*FROM.*WHERE/i,
/INSERT\s+INTO.*VALUES/i,
/UPDATE\s+.*SET.*WHERE/i,
// Async database operations
/await\s+.*\.(query|find|findOne|get|fetch)\s*\([^)]*{/,
/\.then\s*\(\s*.*\.(query|find|findOne)/
];
const hasLoop = loops.some(pattern => pattern.test(content));
const hasDbCall = dbPatterns.some(pattern => pattern.test(content));
// Additional validation: must have database-related imports or setup
const hasDatabaseContext = hasDatabaseImportsOrSetup(content);
if (hasLoop && hasDbCall && hasDatabaseContext) {
const severity = context.type === 'server' ? 'HIGH' : 'MEDIUM';
issues.push({
type: 'n_plus_one',
severity,
category: 'Database',
location: filePath,
pattern: 'Loop + Database Query',
summary: `Possible N+1 query pattern in ${context.type} context: database calls inside loops`,
recommendation: context.type === 'server'
? 'CRITICAL: Refactor with batched queries, joins, or eager loading to prevent server overload'
: 'Consider batching database operations to improve performance',
impact: context.type === 'server'
? 'Can cause exponential database load growth affecting all users'
: 'May cause performance issues with large datasets',
context: context.type
});
}
return issues;
}
/**
* Check if content has database-related imports or setup
* @param {string} content - File content
* @returns {boolean} True if database context is present
*/
function hasDatabaseImportsOrSetup(content) {
const dbContextPatterns = [
// Database libraries
/require\s*\(\s*['"`]mysql['"`]\s*\)/,
/require\s*\(\s*['"`]pg['"`]\s*\)/,
/require\s*\(\s*['"`]mongodb['"`]\s*\)/,
/require\s*\(\s*['"`]mongoose['"`]\s*\)/,
/require\s*\(\s*['"`]sequelize['"`]\s*\)/,
/import.*from\s+['"`](mysql|pg|mongodb|mongoose|sequelize)['"`]/,
// Database variables
/\b(db|database|connection|pool|client)\s*=/,
/\b(DB_|DATABASE_)/,
// Common ORM/database setup
/mongoose\.connect/,
/createConnection/,
/new.*Client/,
/\.authenticate\s*\(\)/
];
return dbContextPatterns.some(pattern => pattern.test(content));
}
/**
* Check if test file has database setup
* @param {string} content - File content
* @returns {boolean} True if test has database setup
*/
function hasTestDatabaseSetup(content) {
return /beforeEach.*db|beforeAll.*db|setupDatabase|createTestDb/i.test(content);
}
module.exports = {
detectNPlusOne
};