Files
michaelschiemer/tests/Database/QueryOptimization/QueryPatternTest.php

187 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Database\QueryOptimization\ValueObjects\QueryLog;
use App\Framework\Database\QueryOptimization\ValueObjects\QueryPattern;
describe('QueryPattern Value Object', function () {
it('groups queries by pattern', function () {
$queries = [
new QueryLog('SELECT * FROM users WHERE id = ?', [1], 5.0, ''),
new QueryLog('SELECT * FROM users WHERE id = ?', [2], 4.5, ''),
new QueryLog('SELECT * FROM users WHERE id = ?', [3], 6.0, ''),
];
$pattern = new QueryPattern('SELECT * FROM users WHERE id = ?', $queries);
expect($pattern->getExecutionCount())->toBe(3);
expect($pattern->getTotalExecutionTimeMs())->toBe(15.5);
expect($pattern->getAverageExecutionTimeMs())->toBe(5.166666666666667);
});
it('detects potential N+1 pattern', function () {
$queries = [];
for ($i = 1; $i <= 10; $i++) {
$queries[] = new QueryLog(
sql: 'SELECT * FROM posts WHERE user_id = ?',
bindings: [$i],
executionTimeMs: 5.0,
stackTrace: 'UserController::show'
);
}
$pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries);
expect($pattern->isPotentialNPlusOne())->toBeTrue();
});
it('rejects pattern with too few executions', function () {
$queries = [
new QueryLog('SELECT * FROM users WHERE id = ?', [1], 5.0, ''),
new QueryLog('SELECT * FROM users WHERE id = ?', [2], 5.0, ''),
];
$pattern = new QueryPattern('SELECT * FROM users WHERE id = ?', $queries);
expect($pattern->isPotentialNPlusOne())->toBeFalse();
});
it('rejects pattern without WHERE clause', function () {
$queries = [];
for ($i = 1; $i <= 10; $i++) {
$queries[] = new QueryLog(
sql: 'SELECT * FROM users',
bindings: [],
executionTimeMs: 5.0,
stackTrace: ''
);
}
$pattern = new QueryPattern('SELECT * FROM users', $queries);
expect($pattern->isPotentialNPlusOne())->toBeFalse();
});
it('rejects pattern with JOIN', function () {
$queries = [];
for ($i = 1; $i <= 10; $i++) {
$queries[] = new QueryLog(
sql: 'SELECT * FROM users LEFT JOIN posts ON users.id = posts.user_id WHERE users.id = ?',
bindings: [$i],
executionTimeMs: 5.0,
stackTrace: ''
);
}
$pattern = new QueryPattern(
'SELECT * FROM users LEFT JOIN posts ON users.id = posts.user_id WHERE users.id = ?',
$queries
);
expect($pattern->isPotentialNPlusOne())->toBeFalse();
});
it('calculates N+1 severity correctly', function () {
$queries = [];
// High execution count (20 queries) + high total time
// Severity: exec_count(20)=+2, avg_time(55ms)=+3, consistent_caller=+2, total_time(1100ms)=+1 → 8 points
for ($i = 1; $i <= 20; $i++) {
$queries[] = new QueryLog(
sql: 'SELECT * FROM posts WHERE user_id = ?',
bindings: [$i],
executionTimeMs: 55.0, // Increased from 10.0 to reach >6 severity
stackTrace: 'UserController::show',
callerClass: 'UserController',
callerMethod: 'show'
);
}
$pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries);
$severity = $pattern->getNPlusOneSeverity();
// Should be high severity (execution count > 10, consistent caller, high total time)
expect($severity)->toBeGreaterThan(6);
});
it('classifies severity levels correctly', function () {
$highSeverityQueries = [];
// 50 queries with high execution time to reach CRITICAL or HIGH severity
// Severity: exec_count(50)=+3, avg_time(55ms)=+3, consistent_caller=+2, total_time(2750ms)=+1 → 9 points (CRITICAL)
for ($i = 1; $i <= 50; $i++) {
$highSeverityQueries[] = new QueryLog(
sql: 'SELECT * FROM posts WHERE user_id = ?',
bindings: [$i],
executionTimeMs: 55.0, // Increased from 15.0 to reach CRITICAL/HIGH
stackTrace: 'UserController::show',
callerClass: 'UserController',
callerMethod: 'show'
);
}
$pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $highSeverityQueries);
expect($pattern->getSeverityLevel())->toBeIn(['CRITICAL', 'HIGH']);
});
it('identifies table name', function () {
$queries = [
new QueryLog('SELECT * FROM users WHERE id = ?', [1], 5.0, ''),
];
$pattern = new QueryPattern('SELECT * FROM users WHERE id = ?', $queries);
expect($pattern->getTableName())->toBe('users');
});
it('checks for consistent caller', function () {
$queries = [];
for ($i = 1; $i <= 10; $i++) {
$queries[] = new QueryLog(
sql: 'SELECT * FROM posts WHERE user_id = ?',
bindings: [$i],
executionTimeMs: 5.0,
stackTrace: '',
callerClass: 'UserController',
callerMethod: 'show'
);
}
$pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries);
expect($pattern->hasConsistentCaller())->toBeTrue();
});
it('detects inconsistent caller', function () {
$queries = [
new QueryLog('SELECT * FROM posts WHERE user_id = ?', [1], 5.0, '', callerClass: 'UserController', callerMethod: 'show'),
new QueryLog('SELECT * FROM posts WHERE user_id = ?', [2], 5.0, '', callerClass: 'PostController', callerMethod: 'index'),
new QueryLog('SELECT * FROM posts WHERE user_id = ?', [3], 5.0, '', callerClass: 'UserController', callerMethod: 'show'),
];
$pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries);
expect($pattern->hasConsistentCaller())->toBeFalse();
});
it('gets caller locations', function () {
$queries = [
new QueryLog('SELECT * FROM posts WHERE user_id = ?', [1], 5.0, '', callerClass: 'UserController', callerMethod: 'show', callerLine: 42),
new QueryLog('SELECT * FROM posts WHERE user_id = ?', [2], 5.0, '', callerClass: 'UserController', callerMethod: 'show', callerLine: 42),
];
$pattern = new QueryPattern('SELECT * FROM posts WHERE user_id = ?', $queries);
$locations = $pattern->getCallerLocations();
expect($locations)->toContain('UserController::show:42');
});
});