GCC Code Coverage Report


Directory: src/
File: src/pg/vec32_pg.c
Date: 2026-09-30 11:11:31
Exec Total Coverage
Lines: 204 218 93.6%
Functions: 40 41 97.6%
Branches: 70 104 67.3%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Tiger Data, Inc.
3 * Licensed under the PostgreSQL License. See LICENSE for details.
4 *
5 * vec32_pg.c - PostgreSQL functions for the vec32 type
6 *
7 * Type I/O, distance functions, comparison operators, casts.
8 * Distance functions bridge to SIMD-accelerated pg_vectorsearch core.
9 */
10
11 #include <postgres.h>
12
13 #include <catalog/pg_type.h>
14 #include <fmgr.h>
15 #include <lib/stringinfo.h>
16 #include <libpq/pqformat.h>
17 #include <utils/array.h>
18 #include <utils/float.h>
19
20 #include "algo/distance.h"
21 #include "support_pg.h"
22
23 /* ----------------------------------------------------------------
24 * Type I/O
25 * ---------------------------------------------------------------- */
26
27 62 PG_FUNCTION_INFO_V1(vs_vec32_in);
28
29 Datum
30 139329 vs_vec32_in(PG_FUNCTION_ARGS)
31 {
32 139329 char *str = PG_GETARG_CSTRING(0);
33 139329 int32 typmod = PG_GETARG_INT32(2);
34 139329 float values[VEC32_MAX_DIM];
35 139329 int dim = 0;
36 139329 char *p = str;
37
38 /* Expect leading '[' */
39
3/4
✓ Branch 0 taken 139333 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 139329 times.
139333 while (*p && (*p == ' ' || *p == '\t'))
40 4 p++;
41
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 139328 times.
139329 if (*p != '[')
42
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ereport(ERROR,
43 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
44 errmsg("vec32 must start with \"[\"")));
45 139328 p++;
46
47 /* Parse comma-separated floats */
48
1/2
✓ Branch 0 taken 14196911 times.
✗ Branch 1 not taken.
14196911 while (*p)
49 {
50
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 14196911 times.
14196920 while (*p == ' ' || *p == '\t')
51 9 p++;
52
2/2
✓ Branch 0 taken 14057587 times.
✓ Branch 1 taken 139324 times.
14196911 if (*p == ']')
53 break;
54
55
2/2
✓ Branch 0 taken 13918260 times.
✓ Branch 1 taken 139327 times.
14057587 if (dim > 0)
56 {
57
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13918259 times.
13918260 if (*p != ',')
58
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ereport(ERROR,
59 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
60 errmsg("expected \",\" or \"]\" in vec32")));
61 13918259 p++;
62
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 13918259 times.
13918263 while (*p == ' ' || *p == '\t')
63 4 p++;
64 }
65
66
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13918259 times.
13918259 if (dim >= VEC32_MAX_DIM)
67 ✗ ereport(ERROR,
68 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
69 errmsg("vec32 cannot have more than %d dimensions",
70 VEC32_MAX_DIM)));
71
72 14057586 char *end;
73 14057586 errno = 0;
74 14057586 float val = strtof(p, &end);
75
3/4
✓ Branch 0 taken 14057585 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 14057585 times.
14057586 if (end == p || errno == ERANGE)
76
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ereport(ERROR,
77 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
78 errmsg("invalid input syntax for type vec32: \"%s\"",
79 str)));
80
81 14057585 vs_pg_check_value_finite(val);
82 14057583 values[dim++] = val;
83 14057583 p = end;
84 }
85
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 139324 times.
139324 if (*p != ']')
87 ✗ ereport(ERROR,
88 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
89 errmsg("vec32 must end with \"]\"")));
90 139324 p++;
91
92 /* Check no trailing content */
93
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 139324 times.
139328 while (*p == ' ' || *p == '\t')
94 4 p++;
95
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 139323 times.
139324 if (*p != '\0')
96
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ereport(ERROR,
97 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
98 errmsg("unexpected characters after \"]\" in vec32")));
99
100
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 139322 times.
139323 if (dim < 1)
101
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ereport(ERROR,
102 (errcode(ERRCODE_DATA_EXCEPTION),
103 errmsg("vec32 must have at least 1 dimension")));
104
105 139322 vs_pg_check_expected_dim(dim, typmod);
106
107 139322 Vec32 *result = vs_pg_vec32_alloc(dim);
108 139322 memcpy(result->x, values, dim * sizeof(float));
109
110 139322 PG_RETURN_VEC32_P(result);
111 }
112
113 24 PG_FUNCTION_INFO_V1(vs_vec32_out);
114
115 Datum
116 83 vs_vec32_out(PG_FUNCTION_ARGS)
117 {
118 83 Vec32 *v = PG_GETARG_VEC32_P(0);
119 83 StringInfoData buf;
120
121 83 initStringInfo(&buf);
122 83 appendStringInfoChar(&buf, '[');
123
124
2/2
✓ Branch 1 taken 1142 times.
✓ Branch 2 taken 83 times.
1225 for (int i = 0; i < v->dim; i++)
125 {
126
2/2
✓ Branch 0 taken 1059 times.
✓ Branch 1 taken 83 times.
1142 if (i > 0)
127 1059 appendStringInfoChar(&buf, ',');
128 1142 appendStringInfo(&buf, "%g", v->x[i]);
129 }
130
131 83 appendStringInfoChar(&buf, ']');
132
133 83 PG_RETURN_CSTRING(buf.data);
134 }
135
136 44 PG_FUNCTION_INFO_V1(vs_vec32_typmod_in);
137
138 Datum
139 300 vs_vec32_typmod_in(PG_FUNCTION_ARGS)
140 {
141 300 ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
142 300 int n;
143 300 int32 *tl = ArrayGetIntegerTypmods(ta, &n);
144
145
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 300 times.
300 if (n != 1)
146 ✗ ereport(ERROR,
147 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
148 errmsg("invalid type modifier")));
149
150 300 int dim = tl[0];
151
152 300 vs_pg_check_dim_valid(dim);
153
154 300 PG_RETURN_INT32(dim);
155 }
156
157 /* ----------------------------------------------------------------
158 * Distance functions
159 * ---------------------------------------------------------------- */
160
161 58 PG_FUNCTION_INFO_V1(vs_l2_distance);
162
163 Datum
164 69975 vs_l2_distance(PG_FUNCTION_ARGS)
165 {
166 69975 Vec32 *a = PG_GETARG_VEC32_P(0);
167 69975 Vec32 *b = PG_GETARG_VEC32_P(1);
168
169 69975 vs_pg_check_dims_match(a->dim, b->dim);
170
171 69974 Vec32Ref ra = Vec32ToRef(a);
172 69974 Vec32Ref rb = Vec32ToRef(b);
173 69974 float d2 = vs_distance_l2(ra, rb);
174 69974 double res = sqrt((double)d2);
175
176 69974 PG_RETURN_FLOAT8(res);
177 }
178
179 9 PG_FUNCTION_INFO_V1(vs_inner_product);
180
181 Datum
182 1 vs_inner_product(PG_FUNCTION_ARGS)
183 {
184 1 Vec32 *a = PG_GETARG_VEC32_P(0);
185 1 Vec32 *b = PG_GETARG_VEC32_P(1);
186
187 1 vs_pg_check_dims_match(a->dim, b->dim);
188
189 1 Vec32Ref ra = Vec32ToRef(a);
190 1 Vec32Ref rb = Vec32ToRef(b);
191 1 float nip = vs_distance_ip(ra, rb);
192 /* vs_distance_ip returns -dot, so negate to get actual dot */
193 1 double res = (double)(-nip);
194
195 1 PG_RETURN_FLOAT8(res);
196 }
197
198 14 PG_FUNCTION_INFO_V1(vs_cosine_distance);
199
200 Datum
201 6558 vs_cosine_distance(PG_FUNCTION_ARGS)
202 {
203 6558 Vec32 *a = PG_GETARG_VEC32_P(0);
204 6558 Vec32 *b = PG_GETARG_VEC32_P(1);
205
206 6558 vs_pg_check_dims_match(a->dim, b->dim);
207
208 6558 Vec32Ref ra = Vec32ToRef(a);
209 6558 Vec32Ref rb = Vec32ToRef(b);
210 6558 double res = (double)vs_distance_cosine(ra, rb);
211
212 6558 PG_RETURN_FLOAT8(res);
213 }
214
215 /* Private distance functions for operators (preserve metric semantics) */
216
217 8 PG_FUNCTION_INFO_V1(vs_vec32_l2_squared_distance);
218
219 Datum
220 ✗ vs_vec32_l2_squared_distance(PG_FUNCTION_ARGS)
221 {
222 ✗ Vec32 *a = PG_GETARG_VEC32_P(0);
223 ✗ Vec32 *b = PG_GETARG_VEC32_P(1);
224
225 ✗ vs_pg_check_dims_match(a->dim, b->dim);
226
227 ✗ Vec32Ref ra = Vec32ToRef(a);
228 ✗ Vec32Ref rb = Vec32ToRef(b);
229 ✗ double res = (double)vs_distance_l2(ra, rb);
230
231 ✗ PG_RETURN_FLOAT8(res);
232 }
233
234 9 PG_FUNCTION_INFO_V1(vs_vec32_negative_inner_product);
235
236 Datum
237 1 vs_vec32_negative_inner_product(PG_FUNCTION_ARGS)
238 {
239 1 Vec32 *a = PG_GETARG_VEC32_P(0);
240 1 Vec32 *b = PG_GETARG_VEC32_P(1);
241
242 1 vs_pg_check_dims_match(a->dim, b->dim);
243
244 1 Vec32Ref ra = Vec32ToRef(a);
245 1 Vec32Ref rb = Vec32ToRef(b);
246 1 double res = (double)vs_distance_ip(ra, rb);
247
248 1 PG_RETURN_FLOAT8(res);
249 }
250
251 /* ----------------------------------------------------------------
252 * Comparison functions (for btree opclass)
253 * ---------------------------------------------------------------- */
254
255 static int
256 1216462 vec32_cmp_internal(Vec32 *a, Vec32 *b)
257 {
258 1216462 int min_dim = (a->dim < b->dim) ? a->dim : b->dim;
259
260
2/2
✓ Branch 0 taken 1311084 times.
✓ Branch 1 taken 11 times.
1311095 for (int i = 0; i < min_dim; i++)
261 {
262
2/2
✓ Branch 0 taken 720829 times.
✓ Branch 1 taken 590255 times.
1311084 if (a->x[i] < b->x[i])
263 return -1;
264
2/2
✓ Branch 0 taken 94633 times.
✓ Branch 1 taken 626196 times.
720829 if (a->x[i] > b->x[i])
265 return 1;
266 }
267
268
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (a->dim < b->dim)
269 return -1;
270
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (a->dim > b->dim)
271 ✗ return 1;
272
273 return 0;
274 }
275
276 19 PG_FUNCTION_INFO_V1(vs_vec32_cmp);
277
278 Datum
279 1216456 vs_vec32_cmp(PG_FUNCTION_ARGS)
280 {
281 1216456 Vec32 *a = PG_GETARG_VEC32_P(0);
282 1216456 Vec32 *b = PG_GETARG_VEC32_P(1);
283 1216456 PG_RETURN_INT32(vec32_cmp_internal(a, b));
284 }
285
286 9 PG_FUNCTION_INFO_V1(vs_vec32_lt);
287
288 Datum
289 1 vs_vec32_lt(PG_FUNCTION_ARGS)
290 {
291 1 Vec32 *a = PG_GETARG_VEC32_P(0);
292 1 Vec32 *b = PG_GETARG_VEC32_P(1);
293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 PG_RETURN_BOOL(vec32_cmp_internal(a, b) < 0);
294 }
295
296 9 PG_FUNCTION_INFO_V1(vs_vec32_le);
297
298 Datum
299 1 vs_vec32_le(PG_FUNCTION_ARGS)
300 {
301 1 Vec32 *a = PG_GETARG_VEC32_P(0);
302 1 Vec32 *b = PG_GETARG_VEC32_P(1);
303
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 PG_RETURN_BOOL(vec32_cmp_internal(a, b) <= 0);
304 }
305
306 9 PG_FUNCTION_INFO_V1(vs_vec32_eq);
307
308 Datum
309 1 vs_vec32_eq(PG_FUNCTION_ARGS)
310 {
311 1 Vec32 *a = PG_GETARG_VEC32_P(0);
312 1 Vec32 *b = PG_GETARG_VEC32_P(1);
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 PG_RETURN_BOOL(vec32_cmp_internal(a, b) == 0);
314 }
315
316 9 PG_FUNCTION_INFO_V1(vs_vec32_ne);
317
318 Datum
319 1 vs_vec32_ne(PG_FUNCTION_ARGS)
320 {
321 1 Vec32 *a = PG_GETARG_VEC32_P(0);
322 1 Vec32 *b = PG_GETARG_VEC32_P(1);
323
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 PG_RETURN_BOOL(vec32_cmp_internal(a, b) != 0);
324 }
325
326 9 PG_FUNCTION_INFO_V1(vs_vec32_ge);
327
328 Datum
329 1 vs_vec32_ge(PG_FUNCTION_ARGS)
330 {
331 1 Vec32 *a = PG_GETARG_VEC32_P(0);
332 1 Vec32 *b = PG_GETARG_VEC32_P(1);
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 PG_RETURN_BOOL(vec32_cmp_internal(a, b) >= 0);
334 }
335
336 9 PG_FUNCTION_INFO_V1(vs_vec32_gt);
337
338 Datum
339 1 vs_vec32_gt(PG_FUNCTION_ARGS)
340 {
341 1 Vec32 *a = PG_GETARG_VEC32_P(0);
342 1 Vec32 *b = PG_GETARG_VEC32_P(1);
343
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 PG_RETURN_BOOL(vec32_cmp_internal(a, b) > 0);
344 }
345
346 /* ----------------------------------------------------------------
347 * Utility functions
348 * ---------------------------------------------------------------- */
349
350 9 PG_FUNCTION_INFO_V1(vs_vec32_dims);
351
352 Datum
353 1 vs_vec32_dims(PG_FUNCTION_ARGS)
354 {
355 1 Vec32 *v = PG_GETARG_VEC32_P(0);
356 1 PG_RETURN_INT32(v->dim);
357 }
358
359 9 PG_FUNCTION_INFO_V1(vs_pg_vec32_norm);
360
361 Datum
362 1 vs_pg_vec32_norm(PG_FUNCTION_ARGS)
363 {
364 1 Vec32 *v = PG_GETARG_VEC32_P(0);
365 1 double res = 0.0;
366
367
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
3 for (int i = 0; i < v->dim; i++)
368 2 res += (double)v->x[i] * (double)v->x[i];
369
370 1 PG_RETURN_FLOAT8(sqrt(res));
371 }
372
373 /* ----------------------------------------------------------------
374 * Cast functions
375 * ---------------------------------------------------------------- */
376
377 48 PG_FUNCTION_INFO_V1(vs_vec32);
378
379 Datum
380 138776 vs_vec32(PG_FUNCTION_ARGS)
381 {
382 138776 Vec32 *v = PG_GETARG_VEC32_P(0);
383 138776 int32 typmod = PG_GETARG_INT32(1);
384
385 138776 vs_pg_check_expected_dim(v->dim, typmod);
386
387 138775 PG_RETURN_VEC32_P(v);
388 }
389
390 31 PG_FUNCTION_INFO_V1(vs_array_to_vec32);
391
392 Datum
393 46460 vs_array_to_vec32(PG_FUNCTION_ARGS)
394 {
395 46460 ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
396
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46460 times.
46460 int32 typmod = PG_GETARG_INT32(1);
397 46460 Oid elemtype;
398 46460 int ndims;
399 46460 int *dims;
400 46460 int dim;
401 46460 Datum *elems;
402 46460 bool *nulls;
403 46460 int nelems;
404
405 46460 elemtype = ARR_ELEMTYPE(arr);
406 46460 ndims = ARR_NDIM(arr);
407
408
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46460 times.
46460 if (ndims > 1)
409 ✗ ereport(ERROR,
410 (errcode(ERRCODE_DATA_EXCEPTION),
411 errmsg("array must be 1-D")));
412
413
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 46459 times.
46460 if (ndims == 0)
414
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ereport(ERROR,
415 (errcode(ERRCODE_DATA_EXCEPTION),
416 errmsg("array must not be empty")));
417
418 46459 dims = ARR_DIMS(arr);
419 46459 dim = dims[0];
420
421 46459 vs_pg_check_dim_valid(dim);
422 46459 vs_pg_check_expected_dim(dim, typmod);
423
424 /*
425 * Only the two element types the SQL overloads declare. Deriving the
426 * element shape from "not float4" would hand deconstruct_array the
427 * wrong length for any other type.
428 */
429
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46459 times.
46459 if (elemtype != FLOAT4OID && elemtype != FLOAT8OID)
430 ✗ ereport(ERROR,
431 (errcode(ERRCODE_DATATYPE_MISMATCH),
432 errmsg("unsupported array element type")));
433
434
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 46422 times.
46496 deconstruct_array(
435 arr,
436 elemtype,
437 (elemtype == FLOAT4OID) ? sizeof(float4) : sizeof(float8),
438 (elemtype == FLOAT4OID) ? true : FLOAT8PASSBYVAL,
439 (elemtype == FLOAT4OID) ? TYPALIGN_INT : TYPALIGN_DOUBLE,
440 &elems,
441 &nulls,
442 &nelems);
443
444 46459 Vec32 *result = vs_pg_vec32_alloc(dim);
445
446
2/2
✓ Branch 1 taken 3922072 times.
✓ Branch 2 taken 46458 times.
3968530 for (int i = 0; i < dim; i++)
447 {
448
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3922071 times.
3922072 if (nulls[i])
449
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ereport(ERROR,
450 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
451 errmsg("array must not contain nulls")));
452
453 3922071 float val;
454
2/2
✓ Branch 0 taken 3921924 times.
✓ Branch 1 taken 147 times.
3922071 if (elemtype == FLOAT4OID)
455 3921924 val = DatumGetFloat4(elems[i]);
456 else
457 147 val = (float)DatumGetFloat8(elems[i]);
458
459 3922071 vs_pg_check_value_finite(val);
460 3922071 result->x[i] = val;
461 }
462
463 46458 PG_RETURN_VEC32_P(result);
464 }
465
466 9 PG_FUNCTION_INFO_V1(vs_vec32_to_float4);
467
468 Datum
469 1 vs_vec32_to_float4(PG_FUNCTION_ARGS)
470 {
471 1 Vec32 *v = PG_GETARG_VEC32_P(0);
472 1 Datum *elems = (Datum *)palloc(v->dim * sizeof(Datum));
473
474
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
4 for (int i = 0; i < v->dim; i++)
475 3 elems[i] = Float4GetDatum(v->x[i]);
476
477 1 ArrayType *result = construct_array(
478 elems, v->dim, FLOAT4OID, sizeof(float4), true, TYPALIGN_INT);
479
480 1 pfree(elems);
481
482 1 PG_RETURN_ARRAYTYPE_P(result);
483 }
484