Implement remove() method for Supabase

This commit is contained in:
Christopher Hertel
2026-02-14 01:50:38 +01:00
parent bb837bf188
commit 8611a43a0c
2 changed files with 102 additions and 2 deletions

View File

@@ -16,7 +16,6 @@ use Symfony\AI\Store\Document\Metadata;
use Symfony\AI\Store\Document\VectorDocument;
use Symfony\AI\Store\Exception\InvalidArgumentException;
use Symfony\AI\Store\Exception\RuntimeException;
use Symfony\AI\Store\Exception\UnsupportedFeatureException;
use Symfony\AI\Store\StoreInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -95,7 +94,37 @@ final class Store implements StoreInterface
public function remove(string|array $ids, array $options = []): void
{
throw new UnsupportedFeatureException('Method not implemented yet.');
if (\is_string($ids)) {
$ids = [$ids];
}
if (0 === \count($ids)) {
return;
}
// Supabase REST API supports batch deletes using the 'in' filter
// We'll chunk the ids to avoid potential URL length limits
$chunkSize = 200;
foreach (array_chunk($ids, $chunkSize) as $chunk) {
$idsString = implode(',', array_map(static fn ($id) => '"'.str_replace('"', '""', $id).'"', $chunk));
$response = $this->httpClient->request(
'DELETE',
\sprintf('%s/rest/v1/%s?id=in.(%s)', $this->url, $this->table, $idsString),
[
'headers' => [
'apikey' => $this->apiKey,
'Authorization' => 'Bearer '.$this->apiKey,
'Content-Type' => 'application/json',
],
]
);
if ($response->getStatusCode() >= 400) {
throw new RuntimeException('Supabase delete failed: '.$response->getContent(false));
}
}
}
/**

View File

@@ -218,6 +218,77 @@ class StoreTest extends TestCase
$this->assertSame(1, $httpClient->getRequestsCount());
}
public function testRemoveSingleDocument()
{
$httpClient = new MockHttpClient(new MockResponse('', ['http_code' => 204]));
$store = $this->createStore($httpClient);
$documentId = 'doc-id-123';
$store->remove($documentId);
$this->assertSame(1, $httpClient->getRequestsCount());
}
public function testRemoveMultipleDocuments()
{
$httpClient = new MockHttpClient(new MockResponse('', ['http_code' => 204]));
$store = $this->createStore($httpClient);
$store->remove(['doc-id-1', 'doc-id-2', 'doc-id-3']);
$this->assertSame(1, $httpClient->getRequestsCount());
}
public function testRemoveEmptyArrayDoesNothing()
{
$httpClient = new MockHttpClient();
$store = $this->createStore($httpClient);
$store->remove([]);
$this->assertSame(0, $httpClient->getRequestsCount());
}
public function testRemoveThrowsExceptionOnHttpError()
{
$httpClient = new MockHttpClient(new MockResponse('Delete failed', ['http_code' => 400]));
$store = $this->createStore($httpClient);
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Supabase delete failed: Delete failed');
$store->remove('doc-id-123');
}
public function testRemoveChunksLargeNumberOfIds()
{
$httpClient = new MockHttpClient([
new MockResponse('', ['http_code' => 204]),
new MockResponse('', ['http_code' => 204]),
new MockResponse('', ['http_code' => 204]),
]);
$store = $this->createStore($httpClient);
$ids = [];
for ($i = 0; $i < 401; ++$i) {
$ids[] = 'doc-id-'.$i;
}
$store->remove($ids);
// Should make 3 API calls with chunks of: 200 + 200 + 1
$this->assertSame(3, $httpClient->getRequestsCount());
}
public function testRemoveWithSpecialCharactersInId()
{
$httpClient = new MockHttpClient(new MockResponse('', ['http_code' => 204]));
$store = $this->createStore($httpClient);
$store->remove('id-with-"quotes"');
$this->assertSame(1, $httpClient->getRequestsCount());
}
private function createStore(MockHttpClient $httpClient, ?int $vectorDimension = 2): SupabaseStore
{
return new SupabaseStore(