- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
314 lines
11 KiB
PHP
314 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
|
|
use App\Framework\Database\Profiling\QueryAnalyzer;
|
|
use App\Framework\Database\Profiling\QueryProfile;
|
|
use App\Framework\Database\Profiling\QueryAnalysis;
|
|
use App\Framework\Database\ValueObjects\SqlQuery;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
|
|
echo "=== Testing Database Query Optimization Tools ===\n\n";
|
|
|
|
echo "1. Testing Query Analysis System:\n";
|
|
|
|
try {
|
|
// Create mock connection for analyzer
|
|
$mockConnection = new class {
|
|
public function query(string $sql) {
|
|
// Mock implementation that returns a result-like object
|
|
return new class {
|
|
public function fetch() {
|
|
static $called = false;
|
|
if (!$called) {
|
|
$called = true;
|
|
return [
|
|
'id' => 1,
|
|
'select_type' => 'SIMPLE',
|
|
'table' => 'users',
|
|
'type' => 'ALL',
|
|
'key' => null,
|
|
'rows' => 1000
|
|
];
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
}
|
|
|
|
public function queryScalar(string $sql) {
|
|
return '{"Plan": {"Node Type": "Seq Scan", "Relation Name": "users"}}';
|
|
}
|
|
};
|
|
|
|
$analyzer = new QueryAnalyzer($mockConnection);
|
|
|
|
echo " ✅ QueryAnalyzer created successfully\n\n";
|
|
|
|
} catch (\Throwable $e) {
|
|
echo " ❌ Error creating QueryAnalyzer: {$e->getMessage()}\n\n";
|
|
}
|
|
|
|
echo "2. Testing Query Analysis with Different Query Types:\n";
|
|
|
|
$testQueries = [
|
|
[
|
|
'name' => 'Simple SELECT',
|
|
'sql' => 'SELECT id, name FROM users WHERE active = 1',
|
|
'execution_time_ms' => 25.5,
|
|
'memory_usage' => 512000
|
|
],
|
|
[
|
|
'name' => 'SELECT with wildcard',
|
|
'sql' => 'SELECT * FROM users WHERE email LIKE "%@example.com"',
|
|
'execution_time_ms' => 1250.0,
|
|
'memory_usage' => 5242880
|
|
],
|
|
[
|
|
'name' => 'Complex JOIN query',
|
|
'sql' => 'SELECT u.*, p.name as profile_name FROM users u LEFT JOIN profiles p ON u.id = p.user_id LEFT JOIN settings s ON u.id = s.user_id WHERE u.active = 1 ORDER BY u.created_at DESC',
|
|
'execution_time_ms' => 2100.7,
|
|
'memory_usage' => 10485760
|
|
],
|
|
[
|
|
'name' => 'Subquery with aggregation',
|
|
'sql' => 'SELECT COUNT(*) FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > (SELECT AVG(total) FROM orders))',
|
|
'execution_time_ms' => 3500.2,
|
|
'memory_usage' => 15728640
|
|
],
|
|
[
|
|
'name' => 'Optimized query',
|
|
'sql' => 'SELECT id, email FROM users WHERE created_at >= ? AND status = ? LIMIT 100',
|
|
'execution_time_ms' => 15.3,
|
|
'memory_usage' => 204800
|
|
]
|
|
];
|
|
|
|
foreach ($testQueries as $testQuery) {
|
|
try {
|
|
echo " 🔍 Analyzing: {$testQuery['name']}\n";
|
|
|
|
// Create a QueryProfile for testing
|
|
$profile = new class(
|
|
$testQuery['sql'],
|
|
$testQuery['execution_time_ms'],
|
|
$testQuery['memory_usage']
|
|
) {
|
|
public string $id;
|
|
public object $query;
|
|
public Duration $executionTime;
|
|
public object $startTimestamp;
|
|
public object $endTimestamp;
|
|
public int $memoryUsage;
|
|
|
|
public function __construct(string $sql, float $executionTimeMs, int $memoryUsage) {
|
|
$this->id = uniqid('query_');
|
|
$this->query = new class($sql) {
|
|
public function __construct(public string $sql) {}
|
|
};
|
|
$this->executionTime = Duration::fromMilliseconds($executionTimeMs);
|
|
$this->startTimestamp = new class {
|
|
public function __construct() {}
|
|
};
|
|
$this->endTimestamp = new class {
|
|
public function __construct() {}
|
|
};
|
|
$this->memoryUsage = $memoryUsage;
|
|
}
|
|
|
|
public function getComplexityScore(): int {
|
|
$sql = strtoupper($this->query->sql);
|
|
$complexity = 0;
|
|
$complexity += substr_count($sql, 'JOIN') * 2;
|
|
$complexity += substr_count($sql, 'SELECT') - 1;
|
|
$complexity += substr_count($sql, 'UNION') * 3;
|
|
$complexity += substr_count($sql, 'GROUP BY') * 2;
|
|
$complexity += substr_count($sql, 'ORDER BY');
|
|
$complexity += substr_count($sql, 'SUBQUERY') * 4;
|
|
return max(1, $complexity);
|
|
}
|
|
};
|
|
|
|
$analysis = $analyzer->analyzeQuery($profile);
|
|
|
|
echo " • SQL: " . substr($testQuery['sql'], 0, 60) . (strlen($testQuery['sql']) > 60 ? '...' : '') . "\n";
|
|
echo " • Execution time: {$testQuery['execution_time_ms']}ms\n";
|
|
echo " • Memory usage: " . number_format($testQuery['memory_usage'] / 1024 / 1024, 2) . " MB\n";
|
|
echo " • Optimization Score: {$analysis->optimizationScore}/100\n";
|
|
echo " • Issues found: " . count($analysis->issues) . "\n";
|
|
echo " • Suggestions: " . count($analysis->suggestions) . "\n";
|
|
|
|
if (!empty($analysis->issues)) {
|
|
echo " • Top issue: {$analysis->issues[0]}\n";
|
|
}
|
|
|
|
if (!empty($analysis->suggestions)) {
|
|
echo " • Top suggestion: {$analysis->suggestions[0]}\n";
|
|
}
|
|
|
|
if (!empty($analysis->indexRecommendations)) {
|
|
echo " • Index recommendation: {$analysis->indexRecommendations[0]}\n";
|
|
}
|
|
|
|
echo "\n";
|
|
|
|
} catch (\Throwable $e) {
|
|
echo " ❌ Error analyzing query: {$e->getMessage()}\n\n";
|
|
}
|
|
}
|
|
|
|
echo "3. Testing Batch Analysis and Summary:\n";
|
|
|
|
try {
|
|
// Create profiles for batch analysis
|
|
$profiles = [];
|
|
foreach ($testQueries as $i => $testQuery) {
|
|
$profiles[] = new class(
|
|
$testQuery['sql'],
|
|
$testQuery['execution_time_ms'],
|
|
$testQuery['memory_usage'],
|
|
$i
|
|
) {
|
|
public string $id;
|
|
public object $query;
|
|
public Duration $executionTime;
|
|
public object $startTimestamp;
|
|
public object $endTimestamp;
|
|
public int $memoryUsage;
|
|
|
|
public function __construct(string $sql, float $executionTimeMs, int $memoryUsage, int $index) {
|
|
$this->id = "query_{$index}";
|
|
$this->query = new class($sql) {
|
|
public function __construct(public string $sql) {}
|
|
};
|
|
$this->executionTime = Duration::fromMilliseconds($executionTimeMs);
|
|
$this->startTimestamp = new class {
|
|
public function __construct() {}
|
|
};
|
|
$this->endTimestamp = new class {
|
|
public function __construct() {}
|
|
};
|
|
$this->memoryUsage = $memoryUsage;
|
|
}
|
|
|
|
public function getComplexityScore(): int {
|
|
$sql = strtoupper($this->query->sql);
|
|
$complexity = 0;
|
|
$complexity += substr_count($sql, 'JOIN') * 2;
|
|
$complexity += substr_count($sql, 'SELECT') - 1;
|
|
$complexity += substr_count($sql, 'UNION') * 3;
|
|
$complexity += substr_count($sql, 'GROUP BY') * 2;
|
|
$complexity += substr_count($sql, 'ORDER BY');
|
|
return max(1, $complexity);
|
|
}
|
|
};
|
|
}
|
|
|
|
$batchAnalyses = $analyzer->batchAnalyze($profiles);
|
|
$summary = $analyzer->getOptimizationSummary($batchAnalyses);
|
|
|
|
echo " 📊 Batch Analysis Summary:\n";
|
|
echo " • Total queries analyzed: {$summary['total_queries_analyzed']}\n";
|
|
echo " • Average optimization score: {$summary['average_optimization_score']}/100\n";
|
|
echo " • Total issues: {$summary['total_issues']}\n";
|
|
echo " • Total suggestions: {$summary['total_suggestions']}\n";
|
|
echo " • Overall assessment: {$summary['overall_assessment']}\n\n";
|
|
|
|
echo " 🔥 Most Common Issues:\n";
|
|
foreach ($summary['most_common_issues'] as $issue => $count) {
|
|
echo " • {$issue} (occurred {$count} times)\n";
|
|
}
|
|
echo "\n";
|
|
|
|
echo " 💡 Most Common Suggestions:\n";
|
|
foreach ($summary['most_common_suggestions'] as $suggestion => $count) {
|
|
echo " • {$suggestion} (suggested {$count} times)\n";
|
|
}
|
|
echo "\n";
|
|
|
|
} catch (\Throwable $e) {
|
|
echo " ❌ Error in batch analysis: {$e->getMessage()}\n\n";
|
|
}
|
|
|
|
echo "4. Testing Individual Query Analysis Patterns:\n";
|
|
|
|
$specialTestCases = [
|
|
[
|
|
'name' => 'N+1 Pattern Detection',
|
|
'queries' => [
|
|
'SELECT * FROM users',
|
|
'SELECT * FROM profiles WHERE user_id = 1',
|
|
'SELECT * FROM profiles WHERE user_id = 2',
|
|
'SELECT * FROM profiles WHERE user_id = 3',
|
|
'SELECT * FROM profiles WHERE user_id = 4',
|
|
'SELECT * FROM profiles WHERE user_id = 5'
|
|
]
|
|
],
|
|
[
|
|
'name' => 'Index Opportunity Detection',
|
|
'queries' => [
|
|
'SELECT * FROM orders WHERE customer_id = 123',
|
|
'SELECT * FROM orders WHERE customer_id = 456',
|
|
'SELECT * FROM orders WHERE customer_id = 789',
|
|
'SELECT * FROM products WHERE category_id = 10',
|
|
'SELECT * FROM products WHERE category_id = 20'
|
|
]
|
|
]
|
|
];
|
|
|
|
foreach ($specialTestCases as $testCase) {
|
|
echo " 🔍 Testing: {$testCase['name']}\n";
|
|
|
|
$queryGroups = [];
|
|
foreach ($testCase['queries'] as $sql) {
|
|
// Normalize SQL for pattern detection
|
|
$normalized = preg_replace('/\b\d+\b/', '?', $sql);
|
|
$normalized = preg_replace('/\s+/', ' ', trim($normalized));
|
|
$queryGroups[$normalized][] = $sql;
|
|
}
|
|
|
|
foreach ($queryGroups as $pattern => $queries) {
|
|
if (count($queries) > 1) {
|
|
echo " • Pattern detected: " . substr($pattern, 0, 50) . "...\n";
|
|
echo " • Execution count: " . count($queries) . "\n";
|
|
echo " • Recommendation: ";
|
|
|
|
if (str_contains($pattern, 'profiles WHERE user_id')) {
|
|
echo "Use eager loading to avoid N+1 queries\n";
|
|
} elseif (str_contains($pattern, 'WHERE customer_id') || str_contains($pattern, 'WHERE category_id')) {
|
|
echo "Consider adding index on the WHERE clause column\n";
|
|
} else {
|
|
echo "Review for optimization opportunities\n";
|
|
}
|
|
}
|
|
}
|
|
echo "\n";
|
|
}
|
|
|
|
echo "5. Testing Performance Assessment:\n";
|
|
|
|
$performanceTests = [
|
|
['score' => 95, 'expected' => 'excellent'],
|
|
['score' => 80, 'expected' => 'good'],
|
|
['score' => 65, 'expected' => 'fair'],
|
|
['score' => 45, 'expected' => 'poor'],
|
|
['score' => 25, 'expected' => 'critical']
|
|
];
|
|
|
|
foreach ($performanceTests as $test) {
|
|
$assessment = match (true) {
|
|
$test['score'] >= 90 => 'excellent',
|
|
$test['score'] >= 75 => 'good',
|
|
$test['score'] >= 60 => 'fair',
|
|
$test['score'] >= 40 => 'poor',
|
|
default => 'critical'
|
|
};
|
|
|
|
$status = $assessment === $test['expected'] ? '✅' : '❌';
|
|
echo " {$status} Score {$test['score']}: {$assessment} (expected: {$test['expected']})\n";
|
|
}
|
|
|
|
echo "\n=== Database Query Optimization Tools Test Completed ===\n"; |