setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec(' CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT UNIQUE, category TEXT, price REAL, stock INTEGER, created_at TEXT ) '); $pdo->exec('CREATE INDEX idx_products_category ON products(category)'); $pdo->exec('CREATE INDEX idx_products_price ON products(price)'); $pdo->exec('CREATE INDEX idx_products_stock ON products(stock)'); $pdo->exec('CREATE INDEX idx_products_category_price ON products(category, price)'); $connection = new PdoConnection($pdo, 'sqlite'); $analyzer = new IndexAnalyzer($connection); $cache = new \App\Framework\Cache\Driver\InMemoryCache(); $usageTracker = new IndexUsageTracker($cache, $analyzer); $this->detector = new UnusedIndexDetector($analyzer, $usageTracker); $this->tableName = 'products'; }); it('finds unused indexes', function () { $unusedIndexes = $this->detector->findUnusedIndexes($this->tableName); expect($unusedIndexes)->toBeArray(); // Since we haven't tracked any usage, all non-unique indexes should be marked as unused expect(count($unusedIndexes))->toBeGreaterThan(0); }); it('detects duplicate indexes', function () { // Create duplicate index in test // (In real scenario, we'd have actual duplicates) $duplicates = $this->detector->findDuplicateIndexes($this->tableName); expect($duplicates)->toBeArray(); }); it('finds redundant indexes (prefix pattern)', function () { $redundant = $this->detector->findRedundantIndexes($this->tableName); expect($redundant)->toBeArray(); // idx_products_category is redundant with idx_products_category_price // because category is a prefix of [category, price] if (!empty($redundant)) { expect($redundant[0])->toHaveKey('redundant_index'); expect($redundant[0])->toHaveKey('covered_by'); } }); it('generates comprehensive unused index report', function () { $report = $this->detector->getUnusedIndexReport($this->tableName); expect($report)->toHaveKey('unused'); expect($report)->toHaveKey('duplicates'); expect($report)->toHaveKey('redundant'); expect($report)->toHaveKey('total_removable'); expect($report)->toHaveKey('estimated_space_savings'); expect($report['unused'])->toBeArray(); expect($report['total_removable'])->toBeInt(); }); it('generates DROP statements for unused indexes', function () { $statements = $this->detector->generateDropStatements($this->tableName); expect($statements)->toBeArray(); foreach ($statements as $statement) { expect($statement)->toContain('DROP INDEX'); expect($statement)->toContain('ON products'); } }); it('never suggests dropping PRIMARY or UNIQUE indexes', function () { $unusedIndexes = $this->detector->findUnusedIndexes($this->tableName); // Filter for any PRIMARY or UNIQUE suggestions $primaryOrUnique = array_filter($unusedIndexes, function ($index) { return str_contains(strtolower($index['index_name']), 'primary') || str_contains(strtolower($index['index_name']), 'unique'); }); expect($primaryOrUnique)->toBeEmpty(); }); it('estimates space savings correctly', function () { $report = $this->detector->getUnusedIndexReport($this->tableName); expect($report['estimated_space_savings'])->toBeString(); expect($report['estimated_space_savings'])->toMatch('/(MB|GB|< 1 MB)/'); }); });