Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.06% covered (warning)
65.06%
54 / 83
11.11% covered (danger)
11.11%
1 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
DataPlane
65.06% covered (warning)
65.06%
54 / 83
11.11% covered (danger)
11.11%
1 / 9
75.68
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 upsert
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
3.10
 query
78.95% covered (warning)
78.95%
15 / 19
0.00% covered (danger)
0.00%
0 / 1
6.34
 fetch
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 delete
66.67% covered (warning)
66.67%
10 / 15
0.00% covered (danger)
0.00%
0 / 1
7.33
 update
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
5.31
 describeIndexStats
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 buildIndexHost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handleResponse
58.33% covered (warning)
58.33%
7 / 12
0.00% covered (danger)
0.00%
0 / 1
5.16
1<?php
2
3declare(strict_types=1);
4
5namespace Mbvb1223\Pinecone\Data;
6
7use GuzzleHttp\Client;
8use GuzzleHttp\Exception\GuzzleException;
9use Mbvb1223\Pinecone\Errors\PineconeApiException;
10use Mbvb1223\Pinecone\Errors\PineconeException;
11use Psr\Http\Message\ResponseInterface;
12
13class DataPlane
14{
15    public function __construct(private readonly Client $httpClient)
16    {
17    }
18
19    public function upsert(array $vectors, ?string $namespace = null): array
20    {
21        try {
22            $payload = ['vectors' => $vectors];
23            if ($namespace) {
24                $payload['namespace'] = $namespace;
25            }
26
27            $response = $this->httpClient->post('/vectors/upsert', [
28                'json' => $payload,
29            ]);
30
31            return $this->handleResponse($response);
32        } catch (GuzzleException $e) {
33            throw new PineconeException('Failed to upsert vectors: ' . $e->getMessage(), 0, $e);
34        }
35    }
36
37    public function query(
38        array $vector = [],
39        ?string $id = null,
40        int $topK = 10,
41        ?array $filter = null,
42        ?string $namespace = null,
43        bool $includeValues = false,
44        bool $includeMetadata = true
45    ): array {
46        try {
47            $payload = [
48                'topK' => $topK,
49                'includeValues' => $includeValues,
50                'includeMetadata' => $includeMetadata,
51            ];
52
53            if (!empty($vector)) {
54                $payload['vector'] = $vector;
55            }
56
57            if ($id) {
58                $payload['id'] = $id;
59            }
60
61            if ($filter) {
62                $payload['filter'] = $filter;
63            }
64
65            if ($namespace) {
66                $payload['namespace'] = $namespace;
67            }
68
69            $response = $this->httpClient->post('/query', [
70                'json' => $payload,
71            ]);
72
73            return $this->handleResponse($response);
74        } catch (GuzzleException $e) {
75            throw new PineconeException('Failed to query vectors: ' . $e->getMessage(), 0, $e);
76        }
77    }
78
79    public function fetch(array $ids, ?string $namespace = null): array
80    {
81        try {
82            $idQueries = implode('&', array_map(fn ($id) => 'ids=' . urlencode($id), $ids));
83
84            $namespaceQuery = $namespace ? '&namespace=' . urlencode($namespace) : '';
85
86            $response = $this->httpClient->get('/vectors/fetch?' . $idQueries . $namespaceQuery);
87
88            return $this->handleResponse($response)['vectors'] ?? [];
89        } catch (GuzzleException $e) {
90            throw new PineconeException('Failed to fetch vectors: ' . $e->getMessage(), 0, $e);
91        }
92    }
93
94    public function delete(array $ids = [], ?array $filter = null, ?string $namespace = null, bool $deleteAll = false): array
95    {
96        try {
97            $payload = [];
98
99            if ($deleteAll) {
100                $payload['deleteAll'] = true;
101            } elseif (!empty($ids)) {
102                $payload['ids'] = $ids;
103            } elseif ($filter) {
104                $payload['filter'] = $filter;
105            }
106
107            if ($namespace) {
108                $payload['namespace'] = $namespace;
109            }
110
111            $response = $this->httpClient->post('/vectors/delete', [
112                'json' => $payload,
113            ]);
114
115            return $this->handleResponse($response);
116        } catch (GuzzleException $e) {
117            throw new PineconeException('Failed to delete vectors: ' . $e->getMessage(), 0, $e);
118        }
119    }
120
121    public function update(string $id, array $values = [], ?array $setMetadata = null, ?string $namespace = null): array
122    {
123        try {
124            $payload = ['id' => $id];
125
126            if (!empty($values)) {
127                $payload['values'] = $values;
128            }
129
130            if ($setMetadata) {
131                $payload['setMetadata'] = $setMetadata;
132            }
133
134            if ($namespace) {
135                $payload['namespace'] = $namespace;
136            }
137
138            $response = $this->httpClient->post('/vectors/update', [
139                'json' => $payload,
140            ]);
141
142            return $this->handleResponse($response);
143        } catch (GuzzleException $e) {
144            throw new PineconeException('Failed to update vector: ' . $e->getMessage(), 0, $e);
145        }
146    }
147
148    public function describeIndexStats(?array $filter = null): array
149    {
150        try {
151            $payload = [];
152            if ($filter) {
153                $payload['filter'] = $filter;
154            }
155
156            $response = $this->httpClient->post('/describe_index_stats', ['json' => (object) $payload]);
157
158            return $this->handleResponse($response);
159        } catch (GuzzleException $e) {
160            throw new PineconeException('Failed to describe index stats: ' . $e->getMessage(), 0, $e);
161        }
162    }
163
164    private function buildIndexHost(string $indexName): string
165    {
166        return "{$indexName}-{$this->config->getEnvironment()}.svc.{$this->config->getEnvironment()}.pinecone.io";
167    }
168
169    private function handleResponse(ResponseInterface $response): array
170    {
171        $statusCode = $response->getStatusCode();
172        $body = $response->getBody()->getContents();
173
174        if ($statusCode >= 400) {
175            $data = json_decode($body, true) ?? [];
176            $message = $data['message'] ?? 'API request failed';
177            throw new PineconeApiException($message, $statusCode, $data);
178        }
179
180        if (empty($body)) {
181            return [];
182        }
183
184        $decoded = json_decode($body, true);
185        if (json_last_error() !== JSON_ERROR_NONE) {
186            throw new PineconeException('Failed to decode JSON response: ' . json_last_error_msg());
187        }
188
189        return $decoded;
190    }
191}