db = DatabaseFactory::content(); } /** * {@inheritDoc} */ public function getStats(): array { $result = $this->db->query( 'SELECT COUNT(*) as total, COALESCE(SUM(token_count), 0) as tokens, SUM(CASE WHEN qdrant_id IS NOT NULL THEN 1 ELSE 0 END) as embedded FROM chunks' )->fetch(); return $result !== false ? $result : ['total' => 0, 'tokens' => 0, 'embedded' => 0]; } /** * {@inheritDoc} */ public function findRecent(int $limit = 5): array { $stmt = $this->db->prepare( 'SELECT c.id, c.content, c.token_count, c.created_at, c.qdrant_id, d.filename FROM chunks c JOIN documents d ON c.document_id = d.id ORDER BY c.created_at DESC LIMIT :limit' ); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->execute(); return $stmt->fetchAll(); } /** * {@inheritDoc} */ public function findByDocument(int $documentId): array { $stmt = $this->db->prepare( 'SELECT c.id, c.chunk_index, c.content, c.token_count, c.heading_path, c.metadata, c.qdrant_id, c.created_at FROM chunks c WHERE c.document_id = :id ORDER BY c.chunk_index' ); $stmt->execute(['id' => $documentId]); return $stmt->fetchAll(); } /** * {@inheritDoc} */ public function findFiltered(string $search = '', string $embedded = '', int $limit = 50, int $offset = 0): array { $sql = 'SELECT c.id, c.chunk_index, c.content, c.token_count, c.qdrant_id, c.created_at, d.filename, d.id as document_id FROM chunks c JOIN documents d ON c.document_id = d.id WHERE 1=1'; $params = []; if ($search !== '') { $sql .= ' AND c.content LIKE :search'; $params['search'] = '%' . $search . '%'; } if ($embedded === 'yes') { $sql .= ' AND c.qdrant_id IS NOT NULL'; } elseif ($embedded === 'no') { $sql .= ' AND c.qdrant_id IS NULL'; } $sql .= ' ORDER BY c.created_at DESC LIMIT ' . $limit . ' OFFSET ' . $offset; $stmt = $this->db->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } /** * {@inheritDoc} */ public function count(string $search = '', string $embedded = ''): int { $sql = 'SELECT COUNT(*) FROM chunks c JOIN documents d ON c.document_id = d.id WHERE 1=1'; $params = []; if ($search !== '') { $sql .= ' AND c.content LIKE :search'; $params['search'] = '%' . $search . '%'; } if ($embedded === 'yes') { $sql .= ' AND c.qdrant_id IS NOT NULL'; } elseif ($embedded === 'no') { $sql .= ' AND c.qdrant_id IS NULL'; } $stmt = $this->db->prepare($sql); $stmt->execute($params); return (int) $stmt->fetchColumn(); } /** * {@inheritDoc} */ public function find(int $id): ?array { $stmt = $this->db->prepare( 'SELECT c.*, d.filename, d.source_path, d.id as document_id FROM chunks c JOIN documents d ON c.document_id = d.id WHERE c.id = :id' ); $stmt->execute(['id' => $id]); $result = $stmt->fetch(); return $result === false ? null : $result; } /** * {@inheritDoc} */ public function findByDocumentAndIndex(int $documentId, int $chunkIndex): ?array { $stmt = $this->db->prepare( 'SELECT id, chunk_index FROM chunks WHERE document_id = :doc_id AND chunk_index = :idx' ); $stmt->execute(['doc_id' => $documentId, 'idx' => $chunkIndex]); $result = $stmt->fetch(); return $result === false ? null : $result; } }