From 23622ee346c85dac78f9be2c9ec0df0ad5ebbaad Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 22 Aug 2025 14:04:14 +0200 Subject: [PATCH 001/108] Added support for float16 and bfloat16. Renamed vector_convert_type to vector_as_type. --- API.md | 21 +- README.md | 2 +- examples/semantic_search/semantic_search.py | 4 +- src/distance-avx2.c | 474 ++++++++++++++++ src/distance-cpu.c | 355 +++++++++++- src/distance-cpu.h | 82 +++ src/distance-neon.c | 566 +++++++++++++++++++- src/distance-sse2.c | 421 +++++++++++++++ src/sqlite-vector.c | 155 +----- src/sqlite-vector.h | 2 +- 10 files changed, 1918 insertions(+), 164 deletions(-) diff --git a/API.md b/API.md index 8d1e08b..340c7a6 100644 --- a/API.md +++ b/API.md @@ -163,22 +163,23 @@ SELECT vector_cleanup('documents', 'embedding'); --- -## `vector_convert_f32(value)` +## `vector_as_f32(value)` -## `vector_convert_f16(value)` +## `vector_as_f16(value)` -## `vector_convert_bf16(value)` +## `vector_as_bf16(value)` -## `vector_convert_i8(value)` +## `vector_as_i8(value)` -## `vector_convert_u8(value)` +## `vector_as_u8(value)` **Returns:** `BLOB` **Description:** Encodes a vector into the required internal BLOB format to ensure correct storage and compatibility with the system’s vector representation. +A real conversion is performed ONLY in case of JSON input. When input is a BLOB, it is assumed to be already properly formatted. -Functions in the `vector_convert_` family should be used in all `INSERT`, `UPDATE`, and `DELETE` statements to properly format vector values. However, they are *not* required when specifying input vectors for the `vector_full_scan` or `vector_quantize_scan` virtual tables. +Functions in the `vector_as_` family should be used in all `INSERT`, `UPDATE`, and `DELETE` statements to properly format vector values. However, they are *not* required when specifying input vectors for the `vector_full_scan` or `vector_quantize_scan` virtual tables. **Parameters:** @@ -193,10 +194,10 @@ Functions in the `vector_convert_` family should be used in all `INSERT`, `UPDAT ```sql -- Insert a Float32 vector using JSON -INSERT INTO documents(embedding) VALUES(vector_convert_f32('[0.1, 0.2, 0.3]')); +INSERT INTO documents(embedding) VALUES(vector_as_f32('[0.1, 0.2, 0.3]')); -- Insert a UInt8 vector using raw BLOB (ensure correct formatting!) -INSERT INTO compressed_vectors(embedding) VALUES(vector_convert_u8(X'010203')); +INSERT INTO compressed_vectors(embedding) VALUES(vector_as_u8(X'010203')); ``` --- @@ -219,7 +220,7 @@ Performs a brute-force nearest neighbor search using the given vector. Despite i ```sql SELECT rowid, distance -FROM vector_full_scan('documents', 'embedding', vector_convert_f32('[0.1, 0.2, 0.3]'), 5); +FROM vector_full_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'), 5); ``` --- @@ -250,7 +251,7 @@ You **must run `vector_quantize()`** before using `vector_quantize_scan()` and w ```sql SELECT rowid, distance -FROM vector_quantize_scan('documents', 'embedding', vector_convert_f32('[0.1, 0.2, 0.3]'), 10); +FROM vector_quantize_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'), 10); ``` --- diff --git a/README.md b/README.md index 570128b..83c6bbc 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ CREATE TABLE images ( INSERT INTO images (embedding, label) VALUES (?, 'cat'); -- Insert a JSON vector (Float32, 384 dimensions) -INSERT INTO images (embedding, label) VALUES (vector_convert_f32('[0.3, 1.0, 0.9, 3.2, 1.4,...]'), 'dog'); +INSERT INTO images (embedding, label) VALUES (vector_as_f32('[0.3, 1.0, 0.9, 3.2, 1.4,...]'), 'dog'); -- Initialize the vector. By default, the distance function is L2. -- To use a different metric, specify one of the following options: diff --git a/examples/semantic_search/semantic_search.py b/examples/semantic_search/semantic_search.py index 1eaa2dc..74716d3 100644 --- a/examples/semantic_search/semantic_search.py +++ b/examples/semantic_search/semantic_search.py @@ -124,7 +124,7 @@ def index_file(self, filepath: str) -> int: embedding_json = json.dumps(embedding.tolist()) cursor.execute( - "INSERT INTO documents (filepath, content, embedding) VALUES (?, ?, vector_convert_f32(?))", + "INSERT INTO documents (filepath, content, embedding) VALUES (?, ?, vector_as_f32(?))", (filepath, chunk, embedding_json) ) chunk_count += 1 @@ -168,7 +168,7 @@ def search(self, query: str, limit: int = 3) -> Tuple[float, List[Tuple[str, str cursor.execute(""" SELECT d.id, d.filepath, d.content, v.distance FROM documents AS d - JOIN vector_quantize_scan('documents', 'embedding', vector_convert_f32(?), ?) AS v + JOIN vector_quantize_scan('documents', 'embedding', vector_as_f32(?), ?) AS v ON d.id = v.rowid; """, (query_json, limit)) elapsed_ms = round((time.time() - start_time) * 1000, 2) diff --git a/src/distance-avx2.c b/src/distance-avx2.c index e4046a4..75e6271 100644 --- a/src/distance-avx2.c +++ b/src/distance-avx2.c @@ -18,6 +18,50 @@ extern char *distance_backend_name; #define _mm256_abs_ps(x) _mm256_andnot_ps(_mm256_set1_ps(-0.0f), (x)) +static inline __m256 mm256_abs_ps(__m256 x) { + const __m256 mask = _mm256_castsi256_ps(_mm256_set1_epi32(0x7FFFFFFF)); + return _mm256_and_ps(x, mask); +} + +static inline double hsum256d(__m256d v) { + __m128d lo = _mm256_castpd256_pd128(v); + __m128d hi = _mm256_extractf128_pd(v, 1); + __m128d s = _mm_add_pd(lo, hi); + __m128d sh = _mm_unpackhi_pd(s, s); + __m128d ss = _mm_add_sd(s, sh); + return _mm_cvtsd_f64(ss); +} + +// per-block Inf mismatch test on 8 lanes (returns true if L1/L2 should be +Inf) +static inline bool block_has_l2_inf_mismatch_8(const uint16_t *a, const uint16_t *b) { + /* mismatch if (a_inf ^ b_inf) OR (both Inf and signs differ) */ + for (int k = 0; k < 8; ++k) { + uint16_t ak = a[k], bk = b[k]; + bool ai = f16_is_inf(ak), bi = f16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (f16_sign(ak) != f16_sign(bk)))) return true; + } + return false; +} + +/* 8×bf16 -> 8×f32: widen to u32, shift <<16, reinterpret as f32 */ +static inline __m256 bf16x8_to_f32x8_loadu(const uint16_t* p) { + __m128i v16 = _mm_loadu_si128((const __m128i*)p); // 8×u16 + __m256i v32 = _mm256_cvtepu16_epi32(v16); // 8×u32 + v32 = _mm256_slli_epi32(v32, 16); // <<16 + return _mm256_castsi256_ps(v32); // bitcast to f32 +} + +/* Any lane has infinite difference? (a_inf ^ b_inf) || (both inf and signs differ) */ +static inline bool block_has_l2_inf_mismatch_bf16_8(const uint16_t* a, const uint16_t* b) { + for (int k = 0; k < 8; ++k) { + uint16_t ak = a[k], bk = b[k]; + bool ai = bfloat16_is_inf(ak), bi = bfloat16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (bfloat16_sign(ak) != bfloat16_sign(bk)))) return true; + } + return false; +} + + // MARK: - FLOAT32 - static inline float float32_distance_l2_impl_avx2 (const void *v1, const void *v2, int n, bool use_sqrt) { @@ -117,6 +161,426 @@ float float32_distance_cosine_avx2 (const void *a, const void *b, int n) { return 1.0f - cosine_similarity; } +// MARK: - FLOAT16 - + +static inline float float16_distance_l2_impl_avx2(const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m256d acc0 = _mm256_setzero_pd(); /* sum of squares for low 4 lanes */ + __m256d acc1 = _mm256_setzero_pd(); /* sum of squares for high 4 lanes */ + int i = 0; + + for (; i <= n - 8; i += 8) { + /* Inf mismatch => distance is +Inf */ + if (block_has_l2_inf_mismatch_8(a + i, b + i)) return INFINITY; + + /* convert 8 f16 -> 8 f32 and zero-out NaN diffs */ + float diff_f32[8]; + for (int k = 0; k < 8; ++k) { + uint16_t ak = a[i + k], bk = b[i + k]; + if (f16_is_nan(ak) || f16_is_nan(bk)) { + diff_f32[k] = 0.0f; + } else { + float ax = float16_to_float32(ak); + float bx = float16_to_float32(bk); + float d = ax - bx; + diff_f32[k] = isnan(d) ? 0.0f : d; /* defensive */ + } + } + + __m256 d = _mm256_loadu_ps(diff_f32); + /* widen to f64 and accumulate squares */ + __m128 lo = _mm256_castps256_ps128(d); + __m128 hi = _mm256_extractf128_ps(d, 1); + __m256d dlo = _mm256_cvtps_pd(lo); + __m256d dhi = _mm256_cvtps_pd(hi); +#if defined(__FMA__) + acc0 = _mm256_fmadd_pd(dlo, dlo, acc0); + acc1 = _mm256_fmadd_pd(dhi, dhi, acc1); +#else + acc0 = _mm256_add_pd(acc0, _mm256_mul_pd(dlo, dlo)); + acc1 = _mm256_add_pd(acc1, _mm256_mul_pd(dhi, dhi)); +#endif + } + + double sum = hsum256d(acc0) + hsum256d(acc1); + + /* scalar tail with same NaN/Inf policy */ + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai) == f16_sign(bi))) return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + double d = (double)float16_to_float32(ai) - (double)float16_to_float32(bi); + sum = fma(d, d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} + +float float16_distance_l2_avx2 (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_avx2(v1, v2, n, true); +} + +float float16_distance_l2_squared_avx2 (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_avx2(v1, v2, n, false); +} + +float float16_distance_l1_avx2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m256d acc = _mm256_setzero_pd(); + int i = 0; + + for (; i <= n - 8; i += 8) { + if (block_has_l2_inf_mismatch_8(a + i, b + i)) return INFINITY; + + float absdiff_f32[8]; + for (int k = 0; k < 8; ++k) { + uint16_t ak = a[i + k], bk = b[i + k]; + if (f16_is_nan(ak) || f16_is_nan(bk)) { + absdiff_f32[k] = 0.0f; + } else { + float ax = float16_to_float32(ak); + float bx = float16_to_float32(bk); + float d = fabsf(ax - bx); + absdiff_f32[k] = isnan(d) ? 0.0f : d; + } + } + + __m256 d = _mm256_loadu_ps(absdiff_f32); + __m128 lo = _mm256_castps256_ps128(d); + __m128 hi = _mm256_extractf128_ps(d, 1); + __m256d dlo = _mm256_cvtps_pd(lo); + __m256d dhi = _mm256_cvtps_pd(hi); + acc = _mm256_add_pd(acc, dlo); + acc = _mm256_add_pd(acc, dhi); + } + + double sum = hsum256d(acc); + + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai) == f16_sign(bi))) return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + sum += fabs((double)float16_to_float32(ai) - (double)float16_to_float32(bi)); + } + + return (float)sum; +} + +float float16_distance_dot_avx2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m256d acc0 = _mm256_setzero_pd(); + __m256d acc1 = _mm256_setzero_pd(); + int i = 0; + + for (; i <= n - 8; i += 8) { + /* convert 8 f16 -> 8 f32; skip NaNs; detect Inf products */ + float prod_f32[8]; + for (int k = 0; k < 8; ++k) { + uint16_t ak = a[i + k], bk = b[i + k]; + if (f16_is_nan(ak) || f16_is_nan(bk)) { + prod_f32[k] = 0.0f; + continue; + } + /* if any lane yields ±Inf product, return immediately with sign */ + bool ai = f16_is_inf(ak), bi = f16_is_inf(bk); + if (ai || bi) { + /* Infinity * zero -> NaN (ignore); else sign = sign(a) ^ sign(b) */ + if ((ai && f16_is_zero(bk)) || (bi && f16_is_zero(ak))) { + prod_f32[k] = 0.0f; /* treat NaN as 0 contribution */ + } else { + int s = (f16_sign(ak) ^ f16_sign(bk)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; /* function returns -dot */ + } + } else { + float ax = float16_to_float32(ak); + float bx = float16_to_float32(bk); + float p = ax * bx; + if (isinf(p)) return (p > 0) ? -INFINITY : INFINITY; + prod_f32[k] = isnan(p) ? 0.0f : p; + } + } + + __m256 p = _mm256_loadu_ps(prod_f32); + __m128 lo = _mm256_castps256_ps128(p); + __m128 hi = _mm256_extractf128_ps(p, 1); + __m256d dlo = _mm256_cvtps_pd(lo); + __m256d dhi = _mm256_cvtps_pd(hi); + acc0 = _mm256_add_pd(acc0, dlo); + acc1 = _mm256_add_pd(acc1, dhi); + } + + double dot = hsum256d(acc0) + hsum256d(acc1); + + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + bool aiinf = f16_is_inf(ai), biinf = f16_is_inf(bi); + if (aiinf || biinf) { + if ((aiinf && f16_is_zero(bi)) || (biinf && f16_is_zero(ai))) { + /* Inf * 0 -> NaN: ignore */ + } else { + int s = (f16_sign(ai) ^ f16_sign(bi)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; /* returns -dot */ + } + } else { + float x = float16_to_float32(ai); + float y = float16_to_float32(bi); + double p = (double)x * (double)y; + if (isinf(p)) return (p > 0) ? -INFINITY : INFINITY; + if (!isnan(p)) dot += p; + } + } + + return (float)(-dot); +} + +float float16_distance_cosine_avx2 (const void *va, const void *vb, int n) { + const uint16_t *a = (const uint16_t *)va; + const uint16_t *b = (const uint16_t *)vb; + + /* If either vector contains any ±Inf, return max distance */ + for (int i = 0; i < n; ++i) { + if (f16_is_inf(a[i]) || f16_is_inf(b[i])) return 1.0f; + } + + /* reuse dot for dot, and norms as sqrt(-dot(self,self)) */ + float dot = -float16_distance_dot_avx2(a, b, n); + float norm_a = sqrtf(-float16_distance_dot_avx2(a, a, n)); + float norm_b = sqrtf(-float16_distance_dot_avx2(b, b, n)); + + if (!(norm_a > 0.0f) || !(norm_b > 0.0f) || !isfinite(norm_a) || !isfinite(norm_b) || !isfinite(dot)) + return 1.0f; + + float cosine = dot / (norm_a * norm_b); + if (cosine > 1.0f) cosine = 1.0f; + if (cosine < -1.0f) cosine = -1.0f; + return 1.0f - cosine; +} + +// MARK: - BFLOAT16 - + +static inline float bfloat16_distance_l2_impl_avx2 (const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m256d acc0 = _mm256_setzero_pd(); // low 4 lanes + __m256d acc1 = _mm256_setzero_pd(); // high 4 lanes + int i = 0; + + for (; i <= n - 8; i += 8) { + if (block_has_l2_inf_mismatch_bf16_8(a + i, b + i)) return INFINITY; + + __m256 af = bf16x8_to_f32x8_loadu(a + i); + __m256 bf = bf16x8_to_f32x8_loadu(b + i); + + /* widen each half to f64 and subtract in f64 to avoid f32 overflow */ + __m128 af_lo = _mm256_castps256_ps128(af); + __m128 af_hi = _mm256_extractf128_ps(af, 1); + __m128 bf_lo = _mm256_castps256_ps128(bf); + __m128 bf_hi = _mm256_extractf128_ps(bf, 1); + + __m256d a0 = _mm256_cvtps_pd(af_lo); + __m256d a1 = _mm256_cvtps_pd(af_hi); + __m256d b0 = _mm256_cvtps_pd(bf_lo); + __m256d b1 = _mm256_cvtps_pd(bf_hi); + + __m256d d0 = _mm256_sub_pd(a0, b0); + __m256d d1 = _mm256_sub_pd(a1, b1); + + /* zero-out NaNs: mask = (d==d) */ + __m256d z = _mm256_setzero_pd(); + __m256d m0 = _mm256_cmp_pd(d0, d0, _CMP_ORD_Q); // true if not NaN + __m256d m1 = _mm256_cmp_pd(d1, d1, _CMP_ORD_Q); + d0 = _mm256_blendv_pd(z, d0, m0); + d1 = _mm256_blendv_pd(z, d1, m1); + + #if defined(__FMA__) + acc0 = _mm256_fmadd_pd(d0, d0, acc0); + acc1 = _mm256_fmadd_pd(d1, d1, acc1); + #else + acc0 = _mm256_add_pd(acc0, _mm256_mul_pd(d0, d0)); + acc1 = _mm256_add_pd(acc1, _mm256_mul_pd(d1, d1)); + #endif + } + + double sum = hsum256d(acc0) + hsum256d(acc1); + + /* scalar tail */ + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((bfloat16_is_inf(ai) || bfloat16_is_inf(bi)) && !(bfloat16_is_inf(ai) && bfloat16_is_inf(bi) && bfloat16_sign(ai)==bfloat16_sign(bi))) return INFINITY; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + double d = (double)bfloat16_to_float32(ai) - (double)bfloat16_to_float32(bi); + sum = fma(d, d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} + +float bfloat16_distance_l2_avx2 (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_avx2(v1, v2, n, true); +} + +float bfloat16_distance_l2_squared_avx2 (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_avx2(v1, v2, n, false); +} + +float bfloat16_distance_l1_avx2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m256d acc = _mm256_setzero_pd(); + int i = 0; + + for (; i <= n - 8; i += 8) { + if (block_has_l2_inf_mismatch_bf16_8(a + i, b + i)) return INFINITY; + + __m256 af = bf16x8_to_f32x8_loadu(a + i); + __m256 bf = bf16x8_to_f32x8_loadu(b + i); + + __m128 af_lo = _mm256_castps256_ps128(af); + __m128 af_hi = _mm256_extractf128_ps(af, 1); + __m128 bf_lo = _mm256_castps256_ps128(bf); + __m128 bf_hi = _mm256_extractf128_ps(bf, 1); + + __m256d a0 = _mm256_cvtps_pd(af_lo); + __m256d a1 = _mm256_cvtps_pd(af_hi); + __m256d b0 = _mm256_cvtps_pd(bf_lo); + __m256d b1 = _mm256_cvtps_pd(bf_hi); + + __m256d d0 = _mm256_sub_pd(a0, b0); + __m256d d1 = _mm256_sub_pd(a1, b1); + + /* |d| and NaN→0 */ + __m256d sign = _mm256_set1_pd(-0.0); + d0 = _mm256_andnot_pd(sign, d0); + d1 = _mm256_andnot_pd(sign, d1); + __m256d z = _mm256_setzero_pd(); + __m256d m0 = _mm256_cmp_pd(d0, d0, _CMP_ORD_Q); + __m256d m1 = _mm256_cmp_pd(d1, d1, _CMP_ORD_Q); + d0 = _mm256_blendv_pd(z, d0, m0); + d1 = _mm256_blendv_pd(z, d1, m1); + + acc = _mm256_add_pd(acc, d0); + acc = _mm256_add_pd(acc, d1); + } + + /* sum */ + __m128d lo = _mm256_castpd256_pd128(acc); + __m128d hi = _mm256_extractf128_pd(acc, 1); + __m128d s = _mm_add_pd(lo, hi); + __m128d sh = _mm_unpackhi_pd(s, s); + __m128d ss = _mm_add_sd(s, sh); + double sum = _mm_cvtsd_f64(ss); + + /* tail */ + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((bfloat16_is_inf(ai) || bfloat16_is_inf(bi)) && !(bfloat16_is_inf(ai) && bfloat16_is_inf(bi) && bfloat16_sign(ai)==bfloat16_sign(bi))) return INFINITY; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + sum += fabs((double)bfloat16_to_float32(ai) - (double)bfloat16_to_float32(bi)); + } + + return (float)sum; +} + +float bfloat16_distance_dot_avx2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m256d acc0 = _mm256_setzero_pd(); + __m256d acc1 = _mm256_setzero_pd(); + int i = 0; + + for (; i <= n - 8; i += 8) { + /* quick Inf product check per-lane */ + for (int k=0;k<8;++k){ + uint16_t ak=a[i+k], bk=b[i+k]; + bool ai=bfloat16_is_inf(ak), bi=bfloat16_is_inf(bk); + if (ai || bi) { + if ((ai && bfloat16_is_zero(bk)) || (bi && bfloat16_is_zero(ak))) { + /* Inf * 0 ⇒ NaN: ignore lane */ + continue; + } else { + int s = (bfloat16_sign(ak) ^ bfloat16_sign(bk)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; /* function returns -dot */ + } + } + } + + __m256 af = bf16x8_to_f32x8_loadu(a + i); + __m256 bf = bf16x8_to_f32x8_loadu(b + i); + + /* zero-out NaNs before multiply */ + __m256 mask_a = _mm256_cmp_ps(af, af, _CMP_ORD_Q); + __m256 mask_b = _mm256_cmp_ps(bf, bf, _CMP_ORD_Q); + af = _mm256_blendv_ps(_mm256_setzero_ps(), af, mask_a); + bf = _mm256_blendv_ps(_mm256_setzero_ps(), bf, mask_b); + + __m256 prod = _mm256_mul_ps(af, bf); + + __m128 lo = _mm256_castps256_ps128(prod); + __m128 hi = _mm256_extractf128_ps(prod, 1); + __m256d d0 = _mm256_cvtps_pd(lo); + __m256d d1 = _mm256_cvtps_pd(hi); + + acc0 = _mm256_add_pd(acc0, d0); + acc1 = _mm256_add_pd(acc1, d1); + } + + /* sum */ + __m128d lo = _mm256_castpd256_pd128(acc0); + __m128d hi = _mm256_extractf128_pd(acc0, 1); + __m128d s = _mm_add_pd(lo, hi); + __m128d sh = _mm_unpackhi_pd(s, s); + double dot = _mm_cvtsd_f64(_mm_add_sd(s, sh)); + lo = _mm256_castpd256_pd128(acc1); + hi = _mm256_extractf128_pd(acc1, 1); + s = _mm_add_pd(lo, hi); + sh = _mm_unpackhi_pd(s, s); + dot += _mm_cvtsd_f64(_mm_add_sd(s, sh)); + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + bool aiinf=bfloat16_is_inf(ai), biinf=bfloat16_is_inf(bi); + if (aiinf || biinf) { + if ((aiinf && bfloat16_is_zero(bi)) || (biinf && bfloat16_is_zero(ai))) { + /* Inf*0 -> NaN: ignore */ + } else { + int sgn = (bfloat16_sign(ai) ^ bfloat16_sign(bi)) ? -1 : +1; + return sgn < 0 ? INFINITY : -INFINITY; /* returns -dot */ + } + } else { + double p = (double)bfloat16_to_float32(ai) * (double)bfloat16_to_float32(bi); + dot += p; + } + } + + return (float)(-dot); +} + +float bfloat16_distance_cosine_avx2 (const void *v1, const void *v2, int n) { + /* reuse dot routine like your original float32 version */ + float dot = -bfloat16_distance_dot_avx2(v1, v2, n); + float norm_a = sqrtf(-bfloat16_distance_dot_avx2(v1, v1, n)); + float norm_b = sqrtf(-bfloat16_distance_dot_avx2(v2, v2, n)); + + if (!(norm_a > 0.0f) || !(norm_b > 0.0f) || !isfinite(norm_a) || !isfinite(norm_b) || !isfinite(dot)) + return 1.0f; + + float cs = dot / (norm_a * norm_b); + if (cs > 1.0f) cs = 1.0f; + if (cs < -1.0f) cs = -1.0f; + return 1.0f - cs; +} + // MARK: - UINT8 - static inline float uint8_distance_l2_impl_avx2 (const void *v1, const void *v2, int n, bool use_sqrt) { @@ -492,22 +956,32 @@ float int8_distance_cosine_avx2 (const void *a, const void *b, int n) { void init_distance_functions_avx2 (void) { #if defined(__AVX2__) || (defined(_MSC_VER) && defined(__AVX2__)) dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F32] = float32_distance_l2_avx2; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F16] = float16_distance_l2_avx2; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_avx2; dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_U8] = uint8_distance_l2_avx2; dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_I8] = int8_distance_l2_avx2; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F32] = float32_distance_l2_squared_avx2; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F16] = float16_distance_l2_squared_avx2; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_squared_avx2; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_U8] = uint8_distance_l2_squared_avx2; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_I8] = int8_distance_l2_squared_avx2; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F32] = float32_distance_cosine_avx2; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F16] = float16_distance_cosine_avx2; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_BF16] = bfloat16_distance_cosine_avx2; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_U8] = uint8_distance_cosine_avx2; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_I8] = int8_distance_cosine_avx2; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F32] = float32_distance_dot_avx2; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F16] = float16_distance_dot_avx2; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_BF16] = bfloat16_distance_dot_avx2; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_U8] = uint8_distance_dot_avx2; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_I8] = int8_distance_dot_avx2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F32] = float32_distance_l1_avx2; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F16] = float16_distance_l1_avx2; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_BF16] = bfloat16_distance_l1_avx2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_avx2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_avx2; diff --git a/src/distance-cpu.c b/src/distance-cpu.c index 04badb9..fae0ab9 100644 --- a/src/distance-cpu.c +++ b/src/distance-cpu.c @@ -20,6 +20,20 @@ char *distance_backend_name = "CPU"; distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX] = {0}; +#define LASSQ_UPDATE(ad_) do { \ + double _ad = (ad_); \ + if (_ad != 0.0) { \ + if (scale < _ad) { \ + double r = scale / _ad; \ + ssq = 1.0 + ssq * (r * r); \ + scale = _ad; \ + } else { \ + double r = _ad / scale; \ + ssq += r * r; \ + } \ + } \ + } while (0) + // MARK: FLOAT32 - float float32_distance_l2_impl_cpu (const void *v1, const void *v2, int n, bool use_sqrt) { @@ -144,9 +158,316 @@ float float32_distance_l1_cpu (const void *v1, const void *v2, int n) { return sum; } +// MARK: - BFLOAT16 - + +// Overflow/underflow-safe L2 using LASSQ, unrolled by 4 +static inline float bfloat16_distance_l2_impl_cpu (const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + double scale = 0.0; + double ssq = 1.0; + int i = 0; + + // unrolled main loop (x4) + for (; i <= n - 4; i += 4) { + float x0 = bfloat16_to_float32(a[i ]), y0 = bfloat16_to_float32(b[i ]); + float x1 = bfloat16_to_float32(a[i + 1]), y1 = bfloat16_to_float32(b[i + 1]); + float x2 = bfloat16_to_float32(a[i + 2]), y2 = bfloat16_to_float32(b[i + 2]); + float x3 = bfloat16_to_float32(a[i + 3]), y3 = bfloat16_to_float32(b[i + 3]); + + float d0f = x0 - y0, d1f = x1 - y1, d2f = x2 - y2, d3f = x3 - y3; + + // If any difference is NaN, ignore that lane (treat contribution as 0) + if (isinf(d0f)) return INFINITY; if (!isnan(d0f)) LASSQ_UPDATE(fabs((double)d0f)); + if (isinf(d1f)) return INFINITY; if (!isnan(d1f)) LASSQ_UPDATE(fabs((double)d1f)); + if (isinf(d2f)) return INFINITY; if (!isnan(d2f)) LASSQ_UPDATE(fabs((double)d2f)); + if (isinf(d3f)) return INFINITY; if (!isnan(d3f)) LASSQ_UPDATE(fabs((double)d3f)); + } + + for (; i < n; ++i) { + float d = bfloat16_to_float32(a[i]) - bfloat16_to_float32(b[i]); + if (isinf(d)) return INFINITY; + if (!isnan(d)) LASSQ_UPDATE(fabs((double)d)); + } + + double sum_sq = (scale == 0.0) ? 0.0 : (scale * scale * ssq); + double out = use_sqrt ? sqrt(sum_sq) : sum_sq; + return (float)out; +} + +float bfloat16_distance_l2_cpu (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_cpu(v1, v2, n, true); +} + +float bfloat16_distance_l2_squared_cpu (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_cpu(v1, v2, n, false); +} + +float bfloat16_distance_cosine_cpu(const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + float dot = 0.0f, norm_x = 0.0f, norm_y = 0.0f; + int i = 0; + + // unroll the loop 4 times + for (; i <= n - 4; i += 4) { + float x0 = bfloat16_to_float32(a[i ]), y0 = bfloat16_to_float32(b[i ]); + float x1 = bfloat16_to_float32(a[i + 1]), y1 = bfloat16_to_float32(b[i + 1]); + float x2 = bfloat16_to_float32(a[i + 2]), y2 = bfloat16_to_float32(b[i + 2]); + float x3 = bfloat16_to_float32(a[i + 3]), y3 = bfloat16_to_float32(b[i + 3]); + + // accumulate (fmaf may fuse on capable CPUs) + dot = fmaf(x0, y0, dot); + dot = fmaf(x1, y1, dot); + dot = fmaf(x2, y2, dot); + dot = fmaf(x3, y3, dot); + + norm_x = fmaf(x0, x0, norm_x); + norm_x = fmaf(x1, x1, norm_x); + norm_x = fmaf(x2, x2, norm_x); + norm_x = fmaf(x3, x3, norm_x); + + norm_y = fmaf(y0, y0, norm_y); + norm_y = fmaf(y1, y1, norm_y); + norm_y = fmaf(y2, y2, norm_y); + norm_y = fmaf(y3, y3, norm_y); + } + + // tail loop + for (; i < n; ++i) { + float x = bfloat16_to_float32(a[i]); + float y = bfloat16_to_float32(b[i]); + dot = fmaf(x, y, dot); + norm_x = fmaf(x, x, norm_x); + norm_y = fmaf(y, y, norm_y); + } + + // max distance if one vector is zero + if (norm_x == 0.0f || norm_y == 0.0f) { + return 1.0f; + } + + return 1.0f - (dot / (sqrtf(norm_x) * sqrtf(norm_y))); +} + +float bfloat16_distance_dot_cpu (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + float dot = 0.0f; + int i = 0; + + // unroll the loop 4 times + for (; i <= n - 4; i += 4) { + float x0 = bfloat16_to_float32(a[i ]), y0 = bfloat16_to_float32(b[i ]); + float x1 = bfloat16_to_float32(a[i + 1]), y1 = bfloat16_to_float32(b[i + 1]); + float x2 = bfloat16_to_float32(a[i + 2]), y2 = bfloat16_to_float32(b[i + 2]); + float x3 = bfloat16_to_float32(a[i + 3]), y3 = bfloat16_to_float32(b[i + 3]); + + // fmaf often maps to a fused multiply-add, improving precision/speed + dot = fmaf(x0, y0, dot); + dot = fmaf(x1, y1, dot); + dot = fmaf(x2, y2, dot); + dot = fmaf(x3, y3, dot); + } + + // tail loop + for (; i < n; ++i) { + float x = bfloat16_to_float32(a[i]); + float y = bfloat16_to_float32(b[i]); + dot = fmaf(x, y, dot); + } + + return -dot; +} + +float bfloat16_distance_l1_cpu (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + float sum = 0.0f; + int i = 0; + + // unroll the loop 4 times + for (; i <= n - 4; i += 4) { + float a0 = bfloat16_to_float32(a[i ]), b0 = bfloat16_to_float32(b[i ]); + float a1 = bfloat16_to_float32(a[i + 1]), b1 = bfloat16_to_float32(b[i + 1]); + float a2 = bfloat16_to_float32(a[i + 2]), b2 = bfloat16_to_float32(b[i + 2]); + float a3 = bfloat16_to_float32(a[i + 3]), b3 = bfloat16_to_float32(b[i + 3]); + + sum += fabsf(a0 - b0); + sum += fabsf(a1 - b1); + sum += fabsf(a2 - b2); + sum += fabsf(a3 - b3); + } + + // tail loop + for (; i < n; ++i) { + float da = bfloat16_to_float32(a[i]); + float db = bfloat16_to_float32(b[i]); + sum += fabsf(da - db); + } + + return sum; +} + +// MARK: - FLOAT16 - + +static inline float float16_distance_l2_impl_cpu (const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; /* float16 bits */ + const uint16_t *b = (const uint16_t *)v2; + + double scale = 0.0; + double ssq = 1.0; + int i = 0; + + /* main loop, unrolled by 4 */ + for (; i <= n - 4; i += 4) { + uint16_t a0=a[i], a1=a[i+1], a2=a[i+2], a3=a[i+3]; + uint16_t b0=b[i], b1=b[i+1], b2=b[i+2], b3=b[i+3]; + + /* If any pair involves an infinity not matched with same-signed infinity → +Inf */ + if ((f16_is_inf(a0)||f16_is_inf(b0)) && !(f16_is_inf(a0)&&f16_is_inf(b0)&&f16_sign(a0)==f16_sign(b0))) return INFINITY; + if ((f16_is_inf(a1)||f16_is_inf(b1)) && !(f16_is_inf(a1)&&f16_is_inf(b1)&&f16_sign(a1)==f16_sign(b1))) return INFINITY; + if ((f16_is_inf(a2)||f16_is_inf(b2)) && !(f16_is_inf(a2)&&f16_is_inf(b2)&&f16_sign(a2)==f16_sign(b2))) return INFINITY; + if ((f16_is_inf(a3)||f16_is_inf(b3)) && !(f16_is_inf(a3)&&f16_is_inf(b3)&&f16_sign(a3)==f16_sign(b3))) return INFINITY; + + /* NaN lanes contribute 0 */ + if (!f16_is_nan(a0) && !f16_is_nan(b0)) { double d = (double)float16_to_float32(a0) - (double)float16_to_float32(b0); LASSQ_UPDATE(fabs(d)); } + if (!f16_is_nan(a1) && !f16_is_nan(b1)) { double d = (double)float16_to_float32(a1) - (double)float16_to_float32(b1); LASSQ_UPDATE(fabs(d)); } + if (!f16_is_nan(a2) && !f16_is_nan(b2)) { double d = (double)float16_to_float32(a2) - (double)float16_to_float32(b2); LASSQ_UPDATE(fabs(d)); } + if (!f16_is_nan(a3) && !f16_is_nan(b3)) { double d = (double)float16_to_float32(a3) - (double)float16_to_float32(b3); LASSQ_UPDATE(fabs(d)); } + } + + /* tail */ + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((f16_is_inf(ai)||f16_is_inf(bi)) && !(f16_is_inf(ai)&&f16_is_inf(bi)&&f16_sign(ai)==f16_sign(bi))) return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + double d = (double)float16_to_float32(ai) - (double)float16_to_float32(bi); + LASSQ_UPDATE(fabs(d)); + } + + double sum_sq = (scale == 0.0) ? 0.0 : (scale * scale * ssq); + double out = use_sqrt ? sqrt(sum_sq) : sum_sq; + return (float)out; +} + +float float16_distance_l2_cpu (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_cpu(v1, v2, n, true); +} + +float float16_distance_l2_squared_cpu (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_cpu(v1, v2, n, false); +} + +float float16_distance_l1_cpu (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + double sum = 0.0; + int i = 0; + + for (; i <= n - 4; i += 4) { + uint16_t a0=a[i], a1=a[i+1], a2=a[i+2], a3=a[i+3]; + uint16_t b0=b[i], b1=b[i+1], b2=b[i+2], b3=b[i+3]; + + /* Inf differences yield +Inf */ + if ((f16_is_inf(a0)||f16_is_inf(b0)) && !(f16_is_inf(a0)&&f16_is_inf(b0)&&f16_sign(a0)==f16_sign(b0))) return INFINITY; + if ((f16_is_inf(a1)||f16_is_inf(b1)) && !(f16_is_inf(a1)&&f16_is_inf(b1)&&f16_sign(a1)==f16_sign(b1))) return INFINITY; + if ((f16_is_inf(a2)||f16_is_inf(b2)) && !(f16_is_inf(a2)&&f16_is_inf(b2)&&f16_sign(a2)==f16_sign(b2))) return INFINITY; + if ((f16_is_inf(a3)||f16_is_inf(b3)) && !(f16_is_inf(a3)&&f16_is_inf(b3)&&f16_sign(a3)==f16_sign(b3))) return INFINITY; + + if (!f16_is_nan(a0) && !f16_is_nan(b0)) sum += fabs((double)float16_to_float32(a0) - (double)float16_to_float32(b0)); + if (!f16_is_nan(a1) && !f16_is_nan(b1)) sum += fabs((double)float16_to_float32(a1) - (double)float16_to_float32(b1)); + if (!f16_is_nan(a2) && !f16_is_nan(b2)) sum += fabs((double)float16_to_float32(a2) - (double)float16_to_float32(b2)); + if (!f16_is_nan(a3) && !f16_is_nan(b3)) sum += fabs((double)float16_to_float32(a3) - (double)float16_to_float32(b3)); + } + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((f16_is_inf(ai)||f16_is_inf(bi)) && !(f16_is_inf(ai)&&f16_is_inf(bi)&&f16_sign(ai)==f16_sign(bi))) return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + sum += fabs((double)float16_to_float32(ai) - (double)float16_to_float32(bi)); + } + + return (float)sum; +} + +float float16_distance_dot_cpu (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + double dot = 0.0; + int i = 0; + + for (; i <= n - 4; i += 4) { + float x0 = float16_to_float32(a[i ]), y0 = float16_to_float32(b[i ]); + float x1 = float16_to_float32(a[i + 1]), y1 = float16_to_float32(b[i + 1]); + float x2 = float16_to_float32(a[i + 2]), y2 = float16_to_float32(b[i + 2]); + float x3 = float16_to_float32(a[i + 3]), y3 = float16_to_float32(b[i + 3]); + + /* Skip NaN lanes */ + if (!isnan(x0) && !isnan(y0)) { double p = (double)x0 * (double)y0; if (isinf(p)) return (p>0)? -INFINITY : INFINITY; dot += p; } + if (!isnan(x1) && !isnan(y1)) { double p = (double)x1 * (double)y1; if (isinf(p)) return (p>0)? -INFINITY : INFINITY; dot += p; } + if (!isnan(x2) && !isnan(y2)) { double p = (double)x2 * (double)y2; if (isinf(p)) return (p>0)? -INFINITY : INFINITY; dot += p; } + if (!isnan(x3) && !isnan(y3)) { double p = (double)x3 * (double)y3; if (isinf(p)) return (p>0)? -INFINITY : INFINITY; dot += p; } + } + + for (; i < n; ++i) { + float x = float16_to_float32(a[i]); + float y = float16_to_float32(b[i]); + if (isnan(x) || isnan(y)) continue; + double p = (double)x * (double)y; + if (isinf(p)) return (p>0)? -INFINITY : INFINITY; + dot += p; + } + + return (float)(-dot); +} + +float float16_distance_cosine_cpu (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + double dot = 0.0, nx = 0.0, ny = 0.0; + int i = 0; + + for (; i <= n - 4; i += 4) { + float x0 = float16_to_float32(a[i ]), y0 = float16_to_float32(b[i ]); + float x1 = float16_to_float32(a[i + 1]), y1 = float16_to_float32(b[i + 1]); + float x2 = float16_to_float32(a[i + 2]), y2 = float16_to_float32(b[i + 2]); + float x3 = float16_to_float32(a[i + 3]), y3 = float16_to_float32(b[i + 3]); + + if (!isnan(x0) && !isnan(y0)) { double xd=x0, yd=y0; if (isinf(xd)||isinf(yd)) return 1.0f; dot += xd*yd; nx += xd*xd; ny += yd*yd; } + if (!isnan(x1) && !isnan(y1)) { double xd=x1, yd=y1; if (isinf(xd)||isinf(yd)) return 1.0f; dot += xd*yd; nx += xd*xd; ny += yd*yd; } + if (!isnan(x2) && !isnan(y2)) { double xd=x2, yd=y2; if (isinf(xd)||isinf(yd)) return 1.0f; dot += xd*yd; nx += xd*xd; ny += yd*yd; } + if (!isnan(x3) && !isnan(y3)) { double xd=x3, yd=y3; if (isinf(xd)||isinf(yd)) return 1.0f; dot += xd*yd; nx += xd*xd; ny += yd*yd; } + } + + for (; i < n; ++i) { + float x = float16_to_float32(a[i]); + float y = float16_to_float32(b[i]); + if (isnan(x) || isnan(y)) continue; + if (isinf((double)x) || isinf((double)y)) return 1.0f; + double xd=x, yd=y; + dot += xd*yd; nx += xd*xd; ny += yd*yd; + } + + double denom = sqrt(nx) * sqrt(ny); + if (!(denom > 0.0) || !isfinite(denom) || !isfinite(dot)) return 1.0f; + + double cosv = dot / denom; + if (cosv > 1.0) cosv = 1.0; + if (cosv < -1.0) cosv = -1.0; + return (float)(1.0 - cosv); +} + // MARK: - UINT8 - -static inline float uint8_distance_l2_imp_cpu (const void *v1, const void *v2, int n, bool use_sqrt) { +static inline float uint8_distance_l2_impl_cpu (const void *v1, const void *v2, int n, bool use_sqrt) { const uint8_t *a = (const uint8_t *)v1; const uint8_t *b = (const uint8_t *)v2; @@ -173,11 +494,11 @@ static inline float uint8_distance_l2_imp_cpu (const void *v1, const void *v2, i } float uint8_distance_l2_cpu (const void *v1, const void *v2, int n) { - return uint8_distance_l2_imp_cpu(v1, v2, n, true); + return uint8_distance_l2_impl_cpu(v1, v2, n, true); } float uint8_distance_l2_squared_cpu (const void *v1, const void *v2, int n) { - return uint8_distance_l2_imp_cpu(v1, v2, n, false); + return uint8_distance_l2_impl_cpu(v1, v2, n, false); } float uint8_distance_cosine_cpu (const void *v1, const void *v2, int n) { @@ -258,7 +579,7 @@ float uint8_distance_l1_cpu (const void *v1, const void *v2, int n) { // MARK: - INT8 - -float int8_distance_l2_imp_cpu (const void *v1, const void *v2, int n, bool use_sqrt) { +float int8_distance_l2_impl_cpu (const void *v1, const void *v2, int n, bool use_sqrt) { const int8_t *a = (const int8_t *)v1; const int8_t *b = (const int8_t *)v2; @@ -285,11 +606,11 @@ float int8_distance_l2_imp_cpu (const void *v1, const void *v2, int n, bool use_ } float int8_distance_l2_cpu (const void *v1, const void *v2, int n) { - return int8_distance_l2_imp_cpu(v1, v2, n, true); + return int8_distance_l2_impl_cpu(v1, v2, n, true); } float int8_distance_l2_squared_cpu (const void *v1, const void *v2, int n) { - return int8_distance_l2_imp_cpu(v1, v2, n, false); + return int8_distance_l2_impl_cpu(v1, v2, n, false); } float int8_distance_cosine_cpu (const void *v1, const void *v2, int n) { @@ -429,36 +750,36 @@ void init_cpu_functions (void) { distance_function_t cpu_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX] = { [VECTOR_DISTANCE_L2] = { [VECTOR_TYPE_F32] = float32_distance_l2_cpu, - [VECTOR_TYPE_F16] = NULL, - [VECTOR_TYPE_BF16] = NULL, + [VECTOR_TYPE_F16] = float16_distance_l2_cpu, + [VECTOR_TYPE_BF16] = bfloat16_distance_l2_cpu, [VECTOR_TYPE_U8] = uint8_distance_l2_cpu, [VECTOR_TYPE_I8] = int8_distance_l2_cpu, }, [VECTOR_DISTANCE_SQUARED_L2] = { [VECTOR_TYPE_F32] = float32_distance_l2_squared_cpu, - [VECTOR_TYPE_F16] = NULL, - [VECTOR_TYPE_BF16] = NULL, + [VECTOR_TYPE_F16] = float16_distance_l2_squared_cpu, + [VECTOR_TYPE_BF16] = bfloat16_distance_l2_squared_cpu, [VECTOR_TYPE_U8] = uint8_distance_l2_squared_cpu, [VECTOR_TYPE_I8] = int8_distance_l2_squared_cpu, }, [VECTOR_DISTANCE_COSINE] = { [VECTOR_TYPE_F32] = float32_distance_cosine_cpu, - [VECTOR_TYPE_F16] = NULL, - [VECTOR_TYPE_BF16] = NULL, + [VECTOR_TYPE_F16] = float16_distance_cosine_cpu, + [VECTOR_TYPE_BF16] = bfloat16_distance_cosine_cpu, [VECTOR_TYPE_U8] = uint8_distance_cosine_cpu, [VECTOR_TYPE_I8] = int8_distance_cosine_cpu, }, [VECTOR_DISTANCE_DOT] = { [VECTOR_TYPE_F32] = float32_distance_dot_cpu, - [VECTOR_TYPE_F16] = NULL, - [VECTOR_TYPE_BF16] = NULL, + [VECTOR_TYPE_F16] = float16_distance_dot_cpu, + [VECTOR_TYPE_BF16] = bfloat16_distance_dot_cpu, [VECTOR_TYPE_U8] = uint8_distance_dot_cpu, [VECTOR_TYPE_I8] = int8_distance_dot_cpu, }, [VECTOR_DISTANCE_L1] = { [VECTOR_TYPE_F32] = float32_distance_l1_cpu, - [VECTOR_TYPE_F16] = NULL, - [VECTOR_TYPE_BF16] = NULL, + [VECTOR_TYPE_F16] = float16_distance_l1_cpu, + [VECTOR_TYPE_BF16] = bfloat16_distance_l1_cpu, [VECTOR_TYPE_U8] = uint8_distance_l1_cpu, [VECTOR_TYPE_I8] = int8_distance_l1_cpu, } @@ -472,7 +793,7 @@ void init_distance_functions (bool force_cpu) { if (force_cpu) return; #if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) - if (1/*cpu_supports_avx2()*/) { + if (cpu_supports_avx2()) { init_distance_functions_avx2(); } else if (cpu_supports_sse2()) { init_distance_functions_sse2(); diff --git a/src/distance-cpu.h b/src/distance-cpu.h index 28e1966..83745d2 100644 --- a/src/distance-cpu.h +++ b/src/distance-cpu.h @@ -8,8 +8,25 @@ #ifndef __VECTOR_DISTANCE_CPU__ #define __VECTOR_DISTANCE_CPU__ +#include "fp16/fp16.h" #include #include +#include + +// Detect builtin bit_cast +#ifndef HAVE_BUILTIN_BIT_CAST + #if defined(__has_builtin) + #if __has_builtin(__builtin_bit_cast) + #define HAVE_BUILTIN_BIT_CAST 1 + #endif + #endif + /* GCC 11+ also has __builtin_bit_cast */ + #if !defined(HAVE_BUILTIN_BIT_CAST) && defined(__GNUC__) && !defined(__clang__) + #if __GNUC__ >= 11 + #define HAVE_BUILTIN_BIT_CAST 1 + #endif + #endif +#endif typedef enum { VECTOR_TYPE_F32 = 1, @@ -40,4 +57,69 @@ typedef float (*distance_function_t)(const void *v1, const void *v2, int n); // ENTRYPOINT void init_distance_functions (bool force_cpu); +// MARK: - FLOAT16/BFLOAT16 - +// typedef uint16_t bfloat16_t; // don't typedef to bfloat16_t to avoid mix with ’s native bfloat16_t + +// float <-> uint32_t bit casts +static inline uint32_t f32_to_bits (float f) { + #if defined(HAVE_BUILTIN_BIT_CAST) + return __builtin_bit_cast(uint32_t, f); + #else + union { float f; uint32_t u; } v = { .f = f }; + return v.u; + #endif +} + +static inline float bits_to_f32 (uint32_t u) { + #if defined(HAVE_BUILTIN_BIT_CAST) + return __builtin_bit_cast(float, u); + #else + union { uint32_t u; float f; } v = { .u = u }; + return v.f; + #endif +} + +// bfloat16 (stored as uint16_t) -> float32, and back (RNE) +static inline bool bfloat16_is_nan(uint16_t h) { /* exp==0xFF && frac!=0 */ + return ((h & 0x7F80u) == 0x7F80u) && ((h & 0x007Fu) != 0); +} +static inline bool bfloat16_is_inf(uint16_t h) { /* exp==0xFF && frac==0 */ + return ((h & 0x7F80u) == 0x7F80u) && ((h & 0x007Fu) == 0); +} +static inline bool bfloat16_is_zero(uint16_t h) { /* ±0 */ + return (h & 0x7FFFu) == 0; +} +static inline int bfloat16_sign(uint16_t h) { + return (h >> 15) & 1; +} +static inline float bfloat16_to_float32(uint16_t bf) { + return bits_to_f32((uint32_t)bf << 16); +} +static inline uint16_t float32_to_bfloat16(float f) { + uint32_t x = f32_to_bits(f); + uint32_t lsb = (x >> 16) & 1u; /* ties-to-even */ + uint32_t rnd = 0x7FFFu + lsb; + return (uint16_t)((x + rnd) >> 16); +} + +// ---- float16 (binary16) classifiers (work on raw uint16_t bits) +static inline bool f16_is_nan(uint16_t h) { /* exp==0x1F && frac!=0 */ + return ( (h & 0x7C00u) == 0x7C00u ) && ((h & 0x03FFu) != 0); +} +static inline bool f16_is_inf(uint16_t h) { /* exp==0x1F && frac==0 */ + return ( (h & 0x7C00u) == 0x7C00u ) && ((h & 0x03FFu) == 0); +} +static inline int f16_sign(uint16_t h) { + return (h >> 15) & 1; +} +static inline bool f16_is_zero(uint16_t h) { /* ±0 */ + return (h & 0x7FFFu) == 0; +} +static inline uint16_t float32_to_float16 (float f) { + return fp16_ieee_from_fp32_value(f); +} +static inline float float16_to_float32 (uint16_t h) { + return fp16_ieee_to_fp32_value(h); +} + #endif diff --git a/src/distance-neon.c b/src/distance-neon.c index c1af56d..d2c227e 100644 --- a/src/distance-neon.c +++ b/src/distance-neon.c @@ -11,6 +11,7 @@ #include #include + #if defined(__ARM_NEON) || defined(__ARM_NEON__) #include @@ -19,7 +20,7 @@ extern char *distance_backend_name; // MARK: FLOAT32 - -float float32_distance_l2_imp_neon (const void *v1, const void *v2, int n, bool use_sqrt) { +float float32_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool use_sqrt) { const float *a = (const float *)v1; const float *b = (const float *)v2; @@ -33,9 +34,13 @@ float float32_distance_l2_imp_neon (const void *v1, const void *v2, int n, bool acc = vmlaq_f32(acc, d, d); // acc += d * d } - float tmp[4]; - vst1q_f32(tmp, acc); - float sum = tmp[0] + tmp[1] + tmp[2] + tmp[3]; + float sum; + #if defined(__aarch64__) + sum = vaddvq_f32(acc); // fast horizontal add on arm64 + #else + float tmp[4]; vst1q_f32(tmp, acc); + sum = tmp[0] + tmp[1] + tmp[2] + tmp[3]; + #endif for (; i < n; ++i) { float d = a[i] - b[i]; @@ -46,11 +51,11 @@ float float32_distance_l2_imp_neon (const void *v1, const void *v2, int n, bool } float float32_distance_l2_neon (const void *v1, const void *v2, int n) { - return float32_distance_l2_imp_neon(v1, v2, n, true); + return float32_distance_l2_impl_neon(v1, v2, n, true); } float float32_distance_l2_squared_neon (const void *v1, const void *v2, int n) { - return float32_distance_l2_imp_neon(v1, v2, n, false); + return float32_distance_l2_impl_neon(v1, v2, n, false); } float float32_distance_cosine_neon (const void *v1, const void *v2, int n) { @@ -141,6 +146,545 @@ float float32_distance_l1_neon (const void *v1, const void *v2, int n) { return sum; } +// MARK: - BFLOAT16 - + +static inline float32x4_t bf16x4_to_f32x4_u16 (uint16x4_t h) { + // widen u16 -> u32 and shift left 16: exact bf16->f32 bit pattern + uint32x4_t u32 = vshll_n_u16(h, 16); + return vreinterpretq_f32_u32(u32); +} + +float bfloat16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + // Accumulate in f64 to avoid overflow from huge bf16 values. + float64x2_t acc0 = vdupq_n_f64(0.0), acc1 = vdupq_n_f64(0.0); + int i = 0; + + for (; i <= n - 8; i += 8) { + uint16x8_t av16 = vld1q_u16(a + i); + uint16x8_t bv16 = vld1q_u16(b + i); + + // low 4 + float32x4_t va0 = bf16x4_to_f32x4_u16(vget_low_u16(av16)); + float32x4_t vb0 = bf16x4_to_f32x4_u16(vget_low_u16(bv16)); + float32x4_t d0 = vsubq_f32(va0, vb0); + // mask-out NaNs: m = (d==d) + uint32x4_t m0 = vceqq_f32(d0, d0); + d0 = vbslq_f32(m0, d0, vdupq_n_f32(0.0f)); + float64x2_t d0lo = vcvt_f64_f32(vget_low_f32(d0)); + float64x2_t d0hi = vcvt_f64_f32(vget_high_f32(d0)); + acc0 = vfmaq_f64(acc0, d0lo, d0lo); + acc1 = vfmaq_f64(acc1, d0hi, d0hi); + + // high 4 + float32x4_t va1 = bf16x4_to_f32x4_u16(vget_high_u16(av16)); + float32x4_t vb1 = bf16x4_to_f32x4_u16(vget_high_u16(bv16)); + float32x4_t d1 = vsubq_f32(va1, vb1); + uint32x4_t m1 = vceqq_f32(d1, d1); + d1 = vbslq_f32(m1, d1, vdupq_n_f32(0.0f)); + float64x2_t d1lo = vcvt_f64_f32(vget_low_f32(d1)); + float64x2_t d1hi = vcvt_f64_f32(vget_high_f32(d1)); + acc0 = vfmaq_f64(acc0, d1lo, d1lo); + acc1 = vfmaq_f64(acc1, d1hi, d1hi); + } + + if (i <= n - 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + float32x4_t d = vsubq_f32(bf16x4_to_f32x4_u16(av16), + bf16x4_to_f32x4_u16(bv16)); + uint32x4_t m = vceqq_f32(d, d); + d = vbslq_f32(m, d, vdupq_n_f32(0.0f)); + float64x2_t dlo = vcvt_f64_f32(vget_low_f32(d)); + float64x2_t dhi = vcvt_f64_f32(vget_high_f32(d)); + acc0 = vfmaq_f64(acc0, dlo, dlo); + acc1 = vfmaq_f64(acc1, dhi, dhi); + i += 4; + } + + double sum = vaddvq_f64(vaddq_f64(acc0, acc1)); + + // scalar tail; treat NaN as 0, Inf as +Inf result + for (; i < n; ++i) { + float d = bfloat16_to_float32(a[i]) - bfloat16_to_float32(b[i]); + if (isinf(d)) return INFINITY; + if (!isnan(d)) sum = fma((double)d, (double)d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} + +float bfloat16_distance_l2_neon (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_neon(v1, v2, n, true); +} + +float bfloat16_distance_l2_squared_neon (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_neon(v1, v2, n, false); +} + +float bfloat16_distance_cosine_neon (const void *v1, const void *v2, int n) { + const uint16_t *restrict a = (const uint16_t *restrict)v1; + const uint16_t *restrict b = (const uint16_t *restrict)v2; + + float32x4_t acc_dot = vdupq_n_f32(0.0f); + float32x4_t acc_a2 = vdupq_n_f32(0.0f); + float32x4_t acc_b2 = vdupq_n_f32(0.0f); + int i = 0; + + // process 8 elements per iteration + for (; i <= n - 8; i += 8) { + uint16x8_t av16 = vld1q_u16(a + i); + uint16x8_t bv16 = vld1q_u16(b + i); + + // low 4 + float32x4_t va0 = bf16x4_to_f32x4_u16(vget_low_u16(av16)); + float32x4_t vb0 = bf16x4_to_f32x4_u16(vget_low_u16(bv16)); + acc_dot = vmlaq_f32(acc_dot, va0, vb0); + acc_a2 = vmlaq_f32(acc_a2, va0, va0); + acc_b2 = vmlaq_f32(acc_b2, vb0, vb0); + + // high 4 + float32x4_t va1 = bf16x4_to_f32x4_u16(vget_high_u16(av16)); + float32x4_t vb1 = bf16x4_to_f32x4_u16(vget_high_u16(bv16)); + acc_dot = vmlaq_f32(acc_dot, va1, vb1); + acc_a2 = vmlaq_f32(acc_a2, va1, va1); + acc_b2 = vmlaq_f32(acc_b2, vb1, vb1); + } + + // optional mid-tail of 4 + if (i <= n - 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + float32x4_t va = bf16x4_to_f32x4_u16(av16); + float32x4_t vb = bf16x4_to_f32x4_u16(bv16); + acc_dot = vmlaq_f32(acc_dot, va, vb); + acc_a2 = vmlaq_f32(acc_a2, va, va); + acc_b2 = vmlaq_f32(acc_b2, vb, vb); + i += 4; + } + + // horizontal reduction + float dot, norm_a, norm_b; +#if defined(__aarch64__) + dot = vaddvq_f32(acc_dot); + norm_a = vaddvq_f32(acc_a2); + norm_b = vaddvq_f32(acc_b2); +#else + float d[4], a2[4], b2[4]; + vst1q_f32(d, acc_dot); + vst1q_f32(a2, acc_a2); + vst1q_f32(b2, acc_b2); + dot = d[0] + d[1] + d[2] + d[3]; + norm_a = a2[0] + a2[1] + a2[2] + a2[3]; + norm_b = b2[0] + b2[1] + b2[2] + b2[3]; +#endif + + // scalar tail + for (; i < n; ++i) { + float fa = bfloat16_to_float32(a[i]); + float fb = bfloat16_to_float32(b[i]); + dot += fa * fb; + norm_a += fa * fa; + norm_b += fb * fb; + } + + if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f; + return 1.0f - (dot / (sqrtf(norm_a) * sqrtf(norm_b))); +} + +float bfloat16_distance_dot_neon (const void *v1, const void *v2, int n) { + const uint16_t *restrict a = (const uint16_t *restrict)v1; + const uint16_t *restrict b = (const uint16_t *restrict)v2; + + float32x4_t acc = vdupq_n_f32(0.0f); + int i = 0; + + // process 8 elements per iteration + for (; i <= n - 8; i += 8) { + uint16x8_t av16 = vld1q_u16(a + i); + uint16x8_t bv16 = vld1q_u16(b + i); + + // low 4 + float32x4_t va0 = bf16x4_to_f32x4_u16(vget_low_u16(av16)); + float32x4_t vb0 = bf16x4_to_f32x4_u16(vget_low_u16(bv16)); + acc = vmlaq_f32(acc, va0, vb0); + + // high 4 + float32x4_t va1 = bf16x4_to_f32x4_u16(vget_high_u16(av16)); + float32x4_t vb1 = bf16x4_to_f32x4_u16(vget_high_u16(bv16)); + acc = vmlaq_f32(acc, va1, vb1); + } + + // optional mid-tail of 4 + if (i <= n - 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + float32x4_t va = bf16x4_to_f32x4_u16(av16); + float32x4_t vb = bf16x4_to_f32x4_u16(bv16); + acc = vmlaq_f32(acc, va, vb); + i += 4; + } + + // horizontal sum + float dot; +#if defined(__aarch64__) + dot = vaddvq_f32(acc); +#else + float tmp[4]; vst1q_f32(tmp, acc); + dot = tmp[0] + tmp[1] + tmp[2] + tmp[3]; +#endif + + // scalar tail + for (; i < n; ++i) { + dot += bfloat16_to_float32(a[i]) * bfloat16_to_float32(b[i]); + } + + return -dot; +} + +float bfloat16_distance_l1_neon (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + float32x4_t acc = vdupq_n_f32(0.0f); + int i = 0; + + for (; i <= n - 4; i += 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + + float32x4_t va = bf16x4_to_f32x4_u16(av16); + float32x4_t vb = bf16x4_to_f32x4_u16(bv16); + + float32x4_t d = vabdq_f32(va, vb); // |a - b| + acc = vaddq_f32(acc, d); + } + + // horizontal reduction + float sum; +#if defined(__aarch64__) + sum = vaddvq_f32(acc); +#else + float tmp[4]; vst1q_f32(tmp, acc); + sum = tmp[0] + tmp[1] + tmp[2] + tmp[3]; +#endif + + // scalar tail + for (; i < n; ++i) { + float fa = bfloat16_to_float32(a[i]); + float fb = bfloat16_to_float32(b[i]); + sum += fabsf(fa - fb); + } + + return sum; +} + +// MARK: - FLOAT16 - + +// vector converter: 4×f16 bits (u16) -> f32x4 +static inline float32x4_t f16x4_to_f32x4_u16(uint16x4_t h) { +#if defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) + /* Fast path: NEON FP16 -> FP32 */ + float16x4_t h16 = vreinterpret_f16_u16(h); + return vcvt_f32_f16(h16); +#else + /* Portable per-lane conversion via your helper */ + float tmp[4]; + tmp[0] = float16_to_float32(vget_lane_u16(h, 0)); + tmp[1] = float16_to_float32(vget_lane_u16(h, 1)); + tmp[2] = float16_to_float32(vget_lane_u16(h, 2)); + tmp[3] = float16_to_float32(vget_lane_u16(h, 3)); + return vld1q_f32(tmp); +#endif +} + +float float16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + const uint16x4_t EXP_MASK = vdup_n_u16(0x7C00u); + const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); + const uint16x4_t SIGN_MASK = vdup_n_u16(0x8000u); + const uint16x4_t ZERO16 = vdup_n_u16(0); + + float64x2_t acc0 = vdupq_n_f64(0.0), acc1 = vdupq_n_f64(0.0); + int i = 0; + + for (; i <= n - 4; i += 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + + /* detect Inf mismatches: (a Inf XOR b Inf) OR (both Inf and sign differs) */ + uint16x4_t a_exp_all1 = vceq_u16(vand_u16(av16, EXP_MASK), EXP_MASK); + uint16x4_t b_exp_all1 = vceq_u16(vand_u16(bv16, EXP_MASK), EXP_MASK); + uint16x4_t a_frac_zero= vceq_u16(vand_u16(av16, FRAC_MASK), ZERO16); + uint16x4_t b_frac_zero= vceq_u16(vand_u16(bv16, FRAC_MASK), ZERO16); + + uint16x4_t a_inf = vand_u16(a_exp_all1, a_frac_zero); + uint16x4_t b_inf = vand_u16(b_exp_all1, b_frac_zero); + + uint16x4_t a_sign = vand_u16(av16, SIGN_MASK); + uint16x4_t b_sign = vand_u16(bv16, SIGN_MASK); + uint16x4_t same_sign = vceq_u16(veor_u16(a_sign, b_sign), ZERO16); + uint16x4_t sign_diff = vmvn_u16(same_sign); + + uint16x4_t mismatch = vorr_u16( + vorr_u16(vand_u16(a_inf, vmvn_u16(b_inf)), + vand_u16(b_inf, vmvn_u16(a_inf))), + vand_u16(vand_u16(a_inf, b_inf), sign_diff)); + if (vmaxv_u16(mismatch)) return INFINITY; + + /* convert to f32 then to f64, subtract in f64, mask NaNs to zero */ + float32x4_t af = f16x4_to_f32x4_u16(av16); + float32x4_t bf = f16x4_to_f32x4_u16(bv16); + float32x4_t d32 = vsubq_f32(af, bf); + uint32x4_t m = vceqq_f32(d32, d32); /* true where not-NaN */ + d32 = vbslq_f32(m, d32, vdupq_n_f32(0.0f)); + + float64x2_t dlo = vcvt_f64_f32(vget_low_f32(d32)); + float64x2_t dhi = vcvt_f64_f32(vget_high_f32(d32)); +#if defined(__ARM_FEATURE_FMA) + acc0 = vfmaq_f64(acc0, dlo, dlo); + acc1 = vfmaq_f64(acc1, dhi, dhi); +#else + acc0 = vaddq_f64(acc0, vmulq_f64(dlo, dlo)); + acc1 = vaddq_f64(acc1, vmulq_f64(dhi, dhi)); +#endif + } + + double sum = vaddvq_f64(vaddq_f64(acc0, acc1)); + + /* tail (scalar; same Inf/NaN policy) */ + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai)==f16_sign(bi))) return INFINITY; + float xa = float16_to_float32(ai); + float xb = float16_to_float32(bi); + float d = xa - xb; + if (!isnan(d)) sum = fma((double)d, (double)d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} + +float float16_distance_l2_neon (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_neon(v1, v2, n, true); +} +float float16_distance_l2_squared_neon (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_neon(v1, v2, n, false); +} + +/* ========================================================================= + Cosine distance (1 - dot/(||a||*||b||)) -- float16 (uint16_t) + ========================================================================= */ +float float16_distance_cosine_neon (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + const uint16x4_t EXP_MASK = vdup_n_u16(0x7C00u); + const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); + const uint16x4_t ZERO16 = vdup_n_u16(0); + + float64x2_t acc_dot_lo = vdupq_n_f64(0.0), acc_dot_hi = vdupq_n_f64(0.0); + float64x2_t acc_a2_lo = vdupq_n_f64(0.0), acc_a2_hi = vdupq_n_f64(0.0); + float64x2_t acc_b2_lo = vdupq_n_f64(0.0), acc_b2_hi = vdupq_n_f64(0.0); + int i = 0; + + for (; i <= n - 4; i += 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + + /* if any lane has ±Inf, return 1.0 (max distance) */ + uint16x4_t a_inf = vand_u16(vceq_u16(vand_u16(av16, EXP_MASK), EXP_MASK), + vceq_u16(vand_u16(av16, FRAC_MASK), ZERO16)); + uint16x4_t b_inf = vand_u16(vceq_u16(vand_u16(bv16, EXP_MASK), EXP_MASK), + vceq_u16(vand_u16(bv16, FRAC_MASK), ZERO16)); + if (vmaxv_u16(vorr_u16(a_inf, b_inf))) return 1.0f; + + float32x4_t ax = f16x4_to_f32x4_u16(av16); + float32x4_t by = f16x4_to_f32x4_u16(bv16); + + /* zero out NaNs */ + uint32x4_t mx = vceqq_f32(ax, ax); + uint32x4_t my = vceqq_f32(by, by); + ax = vbslq_f32(mx, ax, vdupq_n_f32(0.0f)); + by = vbslq_f32(my, by, vdupq_n_f32(0.0f)); + + /* widen to f64 and accumulate */ + float64x2_t ax_lo = vcvt_f64_f32(vget_low_f32(ax)), ax_hi = vcvt_f64_f32(vget_high_f32(ax)); + float64x2_t by_lo = vcvt_f64_f32(vget_low_f32(by)), by_hi = vcvt_f64_f32(vget_high_f32(by)); + +#if defined(__ARM_FEATURE_FMA) + acc_dot_lo = vfmaq_f64(acc_dot_lo, ax_lo, by_lo); + acc_dot_hi = vfmaq_f64(acc_dot_hi, ax_hi, by_hi); + acc_a2_lo = vfmaq_f64(acc_a2_lo, ax_lo, ax_lo); + acc_a2_hi = vfmaq_f64(acc_a2_hi, ax_hi, ax_hi); + acc_b2_lo = vfmaq_f64(acc_b2_lo, by_lo, by_lo); + acc_b2_hi = vfmaq_f64(acc_b2_hi, by_hi, by_hi); +#else + acc_dot_lo = vaddq_f64(acc_dot_lo, vmulq_f64(ax_lo, by_lo)); + acc_dot_hi = vaddq_f64(acc_dot_hi, vmulq_f64(ax_hi, by_hi)); + acc_a2_lo = vaddq_f64(acc_a2_lo, vmulq_f64(ax_lo, ax_lo)); + acc_a2_hi = vaddq_f64(acc_a2_hi, vmulq_f64(ax_hi, ax_hi)); + acc_b2_lo = vaddq_f64(acc_b2_lo, vmulq_f64(by_lo, by_lo)); + acc_b2_hi = vaddq_f64(acc_b2_hi, vmulq_f64(by_hi, by_hi)); +#endif + } + + double dot = vaddvq_f64(vaddq_f64(acc_dot_lo, acc_dot_hi)); + double normx= vaddvq_f64(vaddq_f64(acc_a2_lo, acc_a2_hi)); + double normy= vaddvq_f64(vaddq_f64(acc_b2_lo, acc_b2_hi)); + + /* tail (scalar) */ + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + if (f16_is_inf(ai) || f16_is_inf(bi)) return 1.0f; + double x = (double)float16_to_float32(ai); + double y = (double)float16_to_float32(bi); + dot += x * y; + normx+= x * x; + normy+= y * y; + } + + double denom = sqrt(normx) * sqrt(normy); + if (!(denom > 0.0) || !isfinite(denom) || !isfinite(dot)) return 1.0f; + + double c = dot / denom; + if (c > 1.0) c = 1.0; + if (c < -1.0) c = -1.0; + return (float)(1.0 - c); +} + +/* ========================================================================= + Dot (returns -dot) -- float16 (uint16_t) + ========================================================================= */ +float float16_distance_dot_neon (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + const uint16x4_t EXP_MASK = vdup_n_u16(0x7C00u); + const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); + const uint16x4_t ZERO16 = vdup_n_u16(0); + + float64x2_t acc_lo = vdupq_n_f64(0.0), acc_hi = vdupq_n_f64(0.0); + int i = 0; + + for (; i <= n - 4; i += 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + + /* if any lane is ±Inf, do scalar fallback for this block to get sign-correct ±Inf */ + uint16x4_t a_inf = vand_u16(vceq_u16(vand_u16(av16, EXP_MASK), EXP_MASK), + vceq_u16(vand_u16(av16, FRAC_MASK), ZERO16)); + uint16x4_t b_inf = vand_u16(vceq_u16(vand_u16(bv16, EXP_MASK), EXP_MASK), + vceq_u16(vand_u16(bv16, FRAC_MASK), ZERO16)); + if (vmaxv_u16(vorr_u16(a_inf, b_inf))) { + for (int k=0;k<4;++k){ + float x = float16_to_float32(a[i+k]); + float y = float16_to_float32(b[i+k]); + if (isnan(x) || isnan(y)) continue; + double p = (double)x * (double)y; + if (isinf(p)) return (p>0)? -INFINITY : INFINITY; + acc_lo = vsetq_lane_f64(vgetq_lane_f64(acc_lo,0)+p, acc_lo, 0); /* cheap add */ + } + continue; + } + + float32x4_t ax = f16x4_to_f32x4_u16(av16); + float32x4_t by = f16x4_to_f32x4_u16(bv16); + + /* zero out NaNs */ + uint32x4_t mx = vceqq_f32(ax, ax); + uint32x4_t my = vceqq_f32(by, by); + ax = vbslq_f32(mx, ax, vdupq_n_f32(0.0f)); + by = vbslq_f32(my, by, vdupq_n_f32(0.0f)); + + float32x4_t prod = vmulq_f32(ax, by); + float64x2_t lo = vcvt_f64_f32(vget_low_f32(prod)); + float64x2_t hi = vcvt_f64_f32(vget_high_f32(prod)); + acc_lo = vaddq_f64(acc_lo, lo); + acc_hi = vaddq_f64(acc_hi, hi); + } + + double dot = vaddvq_f64(vaddq_f64(acc_lo, acc_hi)); + + for (; i < n; ++i) { + float x = float16_to_float32(a[i]); + float y = float16_to_float32(b[i]); + if (isnan(x) || isnan(y)) continue; + double p = (double)x * (double)y; + if (isinf(p)) return (p>0)? -INFINITY : INFINITY; + dot += p; + } + + return (float)(-dot); +} + +/* ========================================================================= + L1 (sum |a-b|) -- float16 (uint16_t) + ========================================================================= */ +float float16_distance_l1_neon (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + const uint16x4_t EXP_MASK = vdup_n_u16(0x7C00u); + const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); + const uint16x4_t SIGN_MASK = vdup_n_u16(0x8000u); + const uint16x4_t ZERO16 = vdup_n_u16(0); + + float64x2_t acc = vdupq_n_f64(0.0); + int i = 0; + + for (; i <= n - 4; i += 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + + /* Inf mismatch => +Inf */ + uint16x4_t a_exp_all1 = vceq_u16(vand_u16(av16, EXP_MASK), EXP_MASK); + uint16x4_t b_exp_all1 = vceq_u16(vand_u16(bv16, EXP_MASK), EXP_MASK); + uint16x4_t a_frac_zero= vceq_u16(vand_u16(av16, FRAC_MASK), ZERO16); + uint16x4_t b_frac_zero= vceq_u16(vand_u16(bv16, FRAC_MASK), ZERO16); + uint16x4_t a_inf = vand_u16(a_exp_all1, a_frac_zero); + uint16x4_t b_inf = vand_u16(b_exp_all1, b_frac_zero); + uint16x4_t a_sign = vand_u16(av16, SIGN_MASK); + uint16x4_t b_sign = vand_u16(bv16, SIGN_MASK); + uint16x4_t same_sign = vceq_u16(veor_u16(a_sign, b_sign), ZERO16); + uint16x4_t sign_diff = vmvn_u16(same_sign); + uint16x4_t mismatch = vorr_u16( + vorr_u16(vand_u16(a_inf, vmvn_u16(b_inf)), + vand_u16(b_inf, vmvn_u16(a_inf))), + vand_u16(vand_u16(a_inf, b_inf), sign_diff)); + if (vmaxv_u16(mismatch)) return INFINITY; + + float32x4_t af = f16x4_to_f32x4_u16(av16); + float32x4_t bf = f16x4_to_f32x4_u16(bv16); + float32x4_t d = vabdq_f32(af, bf); /* |a-b| */ + uint32x4_t m = vceqq_f32(d, d); /* mask NaNs -> 0 */ + d = vbslq_f32(m, d, vdupq_n_f32(0.0f)); + + float64x2_t lo = vcvt_f64_f32(vget_low_f32(d)); + float64x2_t hi = vcvt_f64_f32(vget_high_f32(d)); + acc = vaddq_f64(acc, lo); + acc = vaddq_f64(acc, hi); + } + + double sum = vaddvq_f64(acc); + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai)==f16_sign(bi))) return INFINITY; + float da = float16_to_float32(ai); + float db = float16_to_float32(bi); + float d = fabsf(da - db); + if (!isnan(d)) sum += d; + } + return (float)sum; +} + + // MARK: - UINT8 - static inline float uint8_distance_l2_impl_neon(const void *v1, const void *v2, int n, bool use_sqrt) { @@ -565,22 +1109,32 @@ float int8_distance_l1_neon(const void *v1, const void *v2, int n) { void init_distance_functions_neon (void) { #if defined(__ARM_NEON) || defined(__ARM_NEON__) dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F32] = float32_distance_l2_neon; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F16] = float16_distance_l2_neon; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_neon; dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_U8] = uint8_distance_l2_neon; dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_I8] = int8_distance_l2_neon; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F32] = float32_distance_l2_squared_neon; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F16] = float16_distance_l2_squared_neon; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_squared_neon; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_U8] = uint8_distance_l2_squared_neon; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_I8] = int8_distance_l2_squared_neon; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F32] = float32_distance_cosine_neon; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F16] = float16_distance_cosine_neon; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_BF16] = bfloat16_distance_cosine_neon; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_U8] = uint8_distance_cosine_neon; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_I8] = int8_distance_cosine_neon; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F32] = float32_distance_dot_neon; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F16] = float16_distance_dot_neon; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_BF16] = bfloat16_distance_dot_neon; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_U8] = uint8_distance_dot_neon; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_I8] = int8_distance_dot_neon; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F32] = float32_distance_l1_neon; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F16] = float16_distance_l1_neon; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_BF16] = bfloat16_distance_l1_neon; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_neon; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_neon; diff --git a/src/distance-sse2.c b/src/distance-sse2.c index a264ec0..0e903a5 100644 --- a/src/distance-sse2.c +++ b/src/distance-sse2.c @@ -30,6 +30,37 @@ extern char *distance_backend_name; #define SIGN_EXTEND_EPI16_TO_EPI32_HI(v) \ _mm_srai_epi32(_mm_unpackhi_epi16(_mm_slli_epi32((v), 16), (v)), 16) +static inline double hsum128d(__m128d v) { + __m128d sh = _mm_unpackhi_pd(v, v); + __m128d s = _mm_add_sd(v, sh); + return _mm_cvtsd_f64(s); +} + +static inline __m128d mm_abs_pd(__m128d x) { + const __m128d sign = _mm_set1_pd(-0.0); + return _mm_andnot_pd(sign, x); +} + +static inline __m128 f16x4_to_f32x4_loadu(const uint16_t* p) { + float tmp[4]; + tmp[0] = float16_to_float32(p[0]); + tmp[1] = float16_to_float32(p[1]); + tmp[2] = float16_to_float32(p[2]); + tmp[3] = float16_to_float32(p[3]); + return _mm_loadu_ps(tmp); +} + +/* load 4 bf16 -> __m128 of f32 */ +static inline __m128 bf16x4_to_f32x4_loadu(const uint16_t* p) { + float tmp[4]; + tmp[0] = bfloat16_to_float32(p[0]); + tmp[1] = bfloat16_to_float32(p[1]); + tmp[2] = bfloat16_to_float32(p[2]); + tmp[3] = bfloat16_to_float32(p[3]); + return _mm_loadu_ps(tmp); +} + + // MARK: - FLOAT32 - static inline float float32_distance_l2_impl_sse2 (const void *v1, const void *v2, int n, bool use_sqrt) { @@ -157,6 +188,386 @@ float float32_distance_cosine_sse2 (const void *v1, const void *v2, int n) { return 1.0f - cosine_sim; } +// MARK: - FLOAT16 - + +static inline float float16_distance_l2_impl_sse2 (const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m128d acc0 = _mm_setzero_pd(); + __m128d acc1 = _mm_setzero_pd(); + int i = 0; + + for (; i <= n - 4; i += 4) { + /* Inf mismatch? (a_inf ^ b_inf) || (both inf with different signs) */ + for (int k=0;k<4;++k) { + uint16_t ak=a[i+k], bk=b[i+k]; + bool ai=f16_is_inf(ak), bi=f16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (f16_sign(ak) != f16_sign(bk)))) + return INFINITY; + } + + __m128 af = f16x4_to_f32x4_loadu(a + i); + __m128 bf = f16x4_to_f32x4_loadu(b + i); + + /* widen to f64 and subtract in f64 to avoid f32 overflow */ + __m128d a_lo = _mm_cvtps_pd(af); + __m128d b_lo = _mm_cvtps_pd(bf); + __m128 af_hi4 = _mm_movehl_ps(af, af); /* [a3 a2 .. ..] */ + __m128 bf_hi4 = _mm_movehl_ps(bf, bf); + __m128d a_hi = _mm_cvtps_pd(af_hi4); + __m128d b_hi = _mm_cvtps_pd(bf_hi4); + + __m128d d0 = _mm_sub_pd(a_lo, b_lo); + __m128d d1 = _mm_sub_pd(a_hi, b_hi); + + /* zero-out NaNs: m = (d==d) */ + __m128d m0 = _mm_cmpeq_pd(d0, d0); + __m128d m1 = _mm_cmpeq_pd(d1, d1); + d0 = _mm_and_pd(d0, m0); + d1 = _mm_and_pd(d1, m1); + + acc0 = _mm_add_pd(acc0, _mm_mul_pd(d0, d0)); + acc1 = _mm_add_pd(acc1, _mm_mul_pd(d1, d1)); + } + + double sum = hsum128d(acc0) + hsum128d(acc1); + + /* tail */ + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && + !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai)==f16_sign(bi))) + return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + double d = (double)float16_to_float32(ai) - (double)float16_to_float32(bi); + sum = fma(d, d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} +float float16_distance_l2_sse2 (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_sse2(v1, v2, n, true); +} + +float float16_distance_l2_squared_sse2 (const void *v1, const void *v2, int n) { + return float16_distance_l2_impl_sse2(v1, v2, n, false); +} + +float float16_distance_l1_sse2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m128d acc = _mm_setzero_pd(); + int i = 0; + + for (; i <= n - 4; i += 4) { + for (int k=0;k<4;++k) { + uint16_t ak=a[i+k], bk=b[i+k]; + bool ai=f16_is_inf(ak), bi=f16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (f16_sign(ak) != f16_sign(bk)))) + return INFINITY; + } + + __m128 af = f16x4_to_f32x4_loadu(a + i); + __m128 bf = f16x4_to_f32x4_loadu(b + i); + + __m128d a_lo = _mm_cvtps_pd(af); + __m128d b_lo = _mm_cvtps_pd(bf); + __m128 af_hi4 = _mm_movehl_ps(af, af); + __m128 bf_hi4 = _mm_movehl_ps(bf, bf); + __m128d a_hi = _mm_cvtps_pd(af_hi4); + __m128d b_hi = _mm_cvtps_pd(bf_hi4); + + __m128d d0 = mm_abs_pd(_mm_sub_pd(a_lo, b_lo)); + __m128d d1 = mm_abs_pd(_mm_sub_pd(a_hi, b_hi)); + + __m128d m0 = _mm_cmpeq_pd(d0, d0); + __m128d m1 = _mm_cmpeq_pd(d1, d1); + d0 = _mm_and_pd(d0, m0); + d1 = _mm_and_pd(d1, m1); + + acc = _mm_add_pd(acc, d0); + acc = _mm_add_pd(acc, d1); + } + + double sum = hsum128d(acc); + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && + !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai)==f16_sign(bi))) + return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + sum += fabs((double)float16_to_float32(ai) - (double)float16_to_float32(bi)); + } + + return (float)sum; +} + +float float16_distance_dot_sse2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m128d acc0 = _mm_setzero_pd(); + __m128d acc1 = _mm_setzero_pd(); + int i = 0; + + for (; i <= n - 4; i += 4) { + /* Handle Inf*X lanes up-front for correct ±Inf sign */ + for (int k=0;k<4;++k) { + uint16_t ak=a[i+k], bk=b[i+k]; + bool ai=f16_is_inf(ak), bi=f16_is_inf(bk); + if (ai || bi) { + if ((ai && f16_is_zero(bk)) || (bi && f16_is_zero(ak))) { + /* Inf * 0 => NaN: ignore lane */ + } else { + int s = (f16_sign(ak) ^ f16_sign(bk)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; /* function returns -dot */ + } + } + } + + __m128 af = f16x4_to_f32x4_loadu(a + i); + __m128 bf = f16x4_to_f32x4_loadu(b + i); + + /* zero-out NaNs */ + __m128 mask_a = _mm_cmpeq_ps(af, af); + __m128 mask_b = _mm_cmpeq_ps(bf, bf); + af = _mm_and_ps(af, mask_a); + bf = _mm_and_ps(bf, mask_b); + + /* widen and accumulate in f64 */ + __m128d a_lo = _mm_cvtps_pd(af); + __m128d b_lo = _mm_cvtps_pd(bf); + __m128 af_hi4 = _mm_movehl_ps(af, af); + __m128 bf_hi4 = _mm_movehl_ps(bf, bf); + __m128d a_hi = _mm_cvtps_pd(af_hi4); + __m128d b_hi = _mm_cvtps_pd(bf_hi4); + + acc0 = _mm_add_pd(acc0, _mm_mul_pd(a_lo, b_lo)); + acc1 = _mm_add_pd(acc1, _mm_mul_pd(a_hi, b_hi)); + } + + double dot = hsum128d(acc0) + hsum128d(acc1); + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + bool aiinf=f16_is_inf(ai), biinf=f16_is_inf(bi); + if (aiinf || biinf) { + if ((aiinf && f16_is_zero(bi)) || (biinf && f16_is_zero(ai))) { + /* Inf*0 -> NaN: ignore */ + } else { + int s = (f16_sign(ai) ^ f16_sign(bi)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; /* returns -dot */ + } + } else { + dot += (double)float16_to_float32(ai) * (double)float16_to_float32(bi); + } + } + + return (float)(-dot); +} + +float float16_distance_cosine_sse2 (const void *v1, const void *v2, int n) { + float dot = -float16_distance_dot_sse2(v1, v2, n); + float norm_a = sqrtf(-float16_distance_dot_sse2(v1, v1, n)); + float norm_b = sqrtf(-float16_distance_dot_sse2(v2, v2, n)); + if (!(norm_a > 0.0f) || !(norm_b > 0.0f) || !isfinite(norm_a) || !isfinite(norm_b) || !isfinite(dot)) + return 1.0f; + float cs = dot / (norm_a * norm_b); + if (cs > 1.0f) cs = 1.0f; else if (cs < -1.0f) cs = -1.0f; + return 1.0f - cs; +} + +// MARK: - BFLOAT16 - + +static inline float bfloat16_distance_l2_impl_sse2 (const void *v1, const void *v2, int n, bool use_sqrt) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m128d acc0 = _mm_setzero_pd(); + __m128d acc1 = _mm_setzero_pd(); + int i = 0; + + for (; i <= n - 4; i += 4) { + for (int k=0;k<4;++k) { + uint16_t ak=a[i+k], bk=b[i+k]; + bool ai=bfloat16_is_inf(ak), bi=bfloat16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (bfloat16_sign(ak) != bfloat16_sign(bk)))) + return INFINITY; + } + + __m128 af = bf16x4_to_f32x4_loadu(a + i); + __m128 bf = bf16x4_to_f32x4_loadu(b + i); + + __m128d a_lo = _mm_cvtps_pd(af); + __m128d b_lo = _mm_cvtps_pd(bf); + __m128 af_hi4 = _mm_movehl_ps(af, af); + __m128 bf_hi4 = _mm_movehl_ps(bf, bf); + __m128d a_hi = _mm_cvtps_pd(af_hi4); + __m128d b_hi = _mm_cvtps_pd(bf_hi4); + + __m128d d0 = _mm_sub_pd(a_lo, b_lo); + __m128d d1 = _mm_sub_pd(a_hi, b_hi); + + __m128d m0 = _mm_cmpeq_pd(d0, d0); + __m128d m1 = _mm_cmpeq_pd(d1, d1); + d0 = _mm_and_pd(d0, m0); + d1 = _mm_and_pd(d1, m1); + + acc0 = _mm_add_pd(acc0, _mm_mul_pd(d0, d0)); + acc1 = _mm_add_pd(acc1, _mm_mul_pd(d1, d1)); + } + + double sum = hsum128d(acc0) + hsum128d(acc1); + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((bfloat16_is_inf(ai) || bfloat16_is_inf(bi)) && + !(bfloat16_is_inf(ai) && bfloat16_is_inf(bi) && bfloat16_sign(ai)==bfloat16_sign(bi))) + return INFINITY; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + double d = (double)bfloat16_to_float32(ai) - (double)bfloat16_to_float32(bi); + sum = fma(d, d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} +float bfloat16_distance_l2_sse2 (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_sse2(v1, v2, n, true); +} + +float bfloat16_distance_l2_squared_sse2 (const void *v1, const void *v2, int n) { + return bfloat16_distance_l2_impl_sse2(v1, v2, n, false); +} + +float bfloat16_distance_l1_sse2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m128d acc = _mm_setzero_pd(); + int i = 0; + + for (; i <= n - 4; i += 4) { + for (int k=0;k<4;++k) { + uint16_t ak=a[i+k], bk=b[i+k]; + bool ai=bfloat16_is_inf(ak), bi=bfloat16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (bfloat16_sign(ak) != bfloat16_sign(bk)))) + return INFINITY; + } + + __m128 af = bf16x4_to_f32x4_loadu(a + i); + __m128 bf = bf16x4_to_f32x4_loadu(b + i); + + __m128d a_lo = _mm_cvtps_pd(af); + __m128d b_lo = _mm_cvtps_pd(bf); + __m128 af_hi4 = _mm_movehl_ps(af, af); + __m128 bf_hi4 = _mm_movehl_ps(bf, bf); + __m128d a_hi = _mm_cvtps_pd(af_hi4); + __m128d b_hi = _mm_cvtps_pd(bf_hi4); + + __m128d d0 = mm_abs_pd(_mm_sub_pd(a_lo, b_lo)); + __m128d d1 = mm_abs_pd(_mm_sub_pd(a_hi, b_hi)); + + __m128d m0 = _mm_cmpeq_pd(d0, d0); + __m128d m1 = _mm_cmpeq_pd(d1, d1); + d0 = _mm_and_pd(d0, m0); + d1 = _mm_and_pd(d1, m1); + + acc = _mm_add_pd(acc, d0); + acc = _mm_add_pd(acc, d1); + } + + double sum = hsum128d(acc); + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if ((bfloat16_is_inf(ai) || bfloat16_is_inf(bi)) && + !(bfloat16_is_inf(ai) && bfloat16_is_inf(bi) && bfloat16_sign(ai)==bfloat16_sign(bi))) + return INFINITY; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + sum += fabs((double)bfloat16_to_float32(ai) - (double)bfloat16_to_float32(bi)); + } + + return (float)sum; +} + +float bfloat16_distance_dot_sse2 (const void *v1, const void *v2, int n) { + const uint16_t *a = (const uint16_t *)v1; + const uint16_t *b = (const uint16_t *)v2; + + __m128d acc0 = _mm_setzero_pd(); + __m128d acc1 = _mm_setzero_pd(); + int i = 0; + + for (; i <= n - 4; i += 4) { + /* handle Inf*X per-lane for correct sign */ + for (int k=0;k<4;++k){ + uint16_t ak=a[i+k], bk=b[i+k]; + bool ai=bfloat16_is_inf(ak), bi=bfloat16_is_inf(bk); + if (ai || bi) { + if ((ai && bfloat16_is_zero(bk)) || (bi && bfloat16_is_zero(ak))) { + /* Inf*0 -> NaN: ignore lane */ + } else { + int s = (bfloat16_sign(ak) ^ bfloat16_sign(bk)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; /* function returns -dot */ + } + } + } + + __m128 af = bf16x4_to_f32x4_loadu(a + i); + __m128 bf = bf16x4_to_f32x4_loadu(b + i); + + /* zero NaNs */ + __m128 ma = _mm_cmpeq_ps(af, af); + __m128 mb = _mm_cmpeq_ps(bf, bf); + af = _mm_and_ps(af, ma); + bf = _mm_and_ps(bf, mb); + + /* widen and accumulate in f64 */ + __m128d a_lo = _mm_cvtps_pd(af); + __m128d b_lo = _mm_cvtps_pd(bf); + __m128 af_hi4 = _mm_movehl_ps(af, af); + __m128 bf_hi4 = _mm_movehl_ps(bf, bf); + __m128d a_hi = _mm_cvtps_pd(af_hi4); + __m128d b_hi = _mm_cvtps_pd(bf_hi4); + + acc0 = _mm_add_pd(acc0, _mm_mul_pd(a_lo, b_lo)); + acc1 = _mm_add_pd(acc1, _mm_mul_pd(a_hi, b_hi)); + } + + double dot = hsum128d(acc0) + hsum128d(acc1); + + for (; i < n; ++i) { + uint16_t ai=a[i], bi=b[i]; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + bool aiinf=bfloat16_is_inf(ai), biinf=bfloat16_is_inf(bi); + if (aiinf || biinf) { + if ((aiinf && bfloat16_is_zero(bi)) || (biinf && bfloat16_is_zero(ai))) { + /* Inf*0 -> NaN: ignore */ + } else { + int s = (bfloat16_sign(ai) ^ bfloat16_sign(bi)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; /* returns -dot */ + } + } else { + dot += (double)bfloat16_to_float32(ai) * (double)bfloat16_to_float32(bi); + } + } + + return (float)(-dot); +} + +float bfloat16_distance_cosine_sse2 (const void *v1, const void *v2, int n) { + float dot = -bfloat16_distance_dot_sse2(v1, v2, n); + float norm_a = sqrtf(-bfloat16_distance_dot_sse2(v1, v1, n)); + float norm_b = sqrtf(-bfloat16_distance_dot_sse2(v2, v2, n)); + if (!(norm_a > 0.0f) || !(norm_b > 0.0f) || !isfinite(norm_a) || !isfinite(norm_b) || !isfinite(dot)) return 1.0f; + float cs = dot / (norm_a * norm_b); + if (cs > 1.0f) cs = 1.0f; else if (cs < -1.0f) cs = -1.0f; + return 1.0f - cs; +} // MARK: - UINT8 - @@ -602,22 +1013,32 @@ float int8_distance_cosine_sse2 (const void *v1, const void *v2, int n) { void init_distance_functions_sse2 (void) { #if defined(__SSE2__) || (defined(_MSC_VER) && (defined(_M_X64) || (_M_IX86_FP >= 2))) dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F32] = float32_distance_l2_sse2; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F16] = float16_distance_l2_sse2; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_sse2; dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_U8] = uint8_distance_l2_sse2; dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_I8] = int8_distance_l2_sse2; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F32] = float32_distance_l2_squared_sse2; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F16] = float16_distance_l2_squared_sse2; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_squared_sse2; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_U8] = uint8_distance_l2_squared_sse2; dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_I8] = int8_distance_l2_squared_sse2; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F32] = float32_distance_cosine_sse2; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F16] = float16_distance_cosine_sse2; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_BF16] = bfloat16_distance_cosine_sse2; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_U8] = uint8_distance_cosine_sse2; dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_I8] = int8_distance_cosine_sse2; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F32] = float32_distance_dot_sse2; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F16] = float16_distance_dot_sse2; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_BF16] = bfloat16_distance_dot_sse2; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_U8] = uint8_distance_dot_sse2; dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_I8] = int8_distance_dot_sse2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F32] = float32_distance_l1_sse2; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F16] = float16_distance_l1_sse2; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_BF16] = bfloat16_distance_l1_sse2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_sse2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_sse2; diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index c5a5d36..218006a 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -156,105 +156,6 @@ typedef int (*vcursor_sort_callback)(vFullScanCursor *c); extern distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX]; extern char *distance_backend_name; -// MARK: - FLOAT / BFLOAT16 - - -// there is a native bfloat16_t ARM intrinsic -typedef uint16_t float16_t; -typedef uint16_t bfloat16_t; - -static inline bfloat16_t float32_to_bfloat16 (float f) { - uint32_t bits = *(uint32_t *)&f; - return (bfloat16_t)(bits >> 16); -} - -static inline float bfloat16_to_float32 (bfloat16_t bf) { - uint32_t bits = ((uint32_t)bf) << 16; - return *(float *)&bits; -} - -// convert 16-bit float (IEEE-754 binary16) to 32-bit float -static inline float float16_to_float32 (uint16_t h) { - uint16_t h_exp = (h & 0x7C00u); - uint16_t h_sig = (h & 0x03FFu); - uint32_t f_sgn = ((uint32_t)h & 0x8000u) << 16; - - if (h_exp == 0x0000u) { - // Zero or subnormal - if (h_sig == 0) { - return *((float*)&f_sgn); // ±0.0 - } else { - // Normalize subnormal - float result = (float)(h_sig) / 1024.0f; - return *((float*)&f_sgn) ? -result : result; - } - } else if (h_exp == 0x7C00u) { - // Inf or NaN - uint32_t f_inf_nan = f_sgn | 0x7F800000u | ((uint32_t)(h_sig) << 13); - return *((float*)&f_inf_nan); - } - - // Normalized number - uint32_t f_exp = ((uint32_t)(h_exp >> 10) + (127 - 15)) << 23; - uint32_t f_sig = ((uint32_t)h_sig) << 13; - uint32_t f = f_sgn | f_exp | f_sig; - - float result; - *((uint32_t*)&result) = f; - return result; -} - -// convert 32-bit float to 16-bit float (IEEE-754 binary16), result stored as uint16_t -static inline uint16_t float32_to_float16(float f) { - union { - uint32_t u; - float f; - } v; - v.f = f; - - uint32_t f_bits = v.u; - - uint32_t sign = (f_bits >> 16) & 0x8000; - int32_t exp = ((f_bits >> 23) & 0xFF) - 127 + 15; - uint32_t frac = f_bits & 0x007FFFFF; - - if (exp <= 0) { - // Subnormal or underflow - if (exp < -10) { - // Too small for subnormal — flush to zero - return (uint16_t)sign; - } - // Subnormal - frac |= 0x00800000; // add implicit leading 1 - int shift = 14 - exp; - uint16_t subnormal = (uint16_t)(frac >> shift); - // Round to nearest - if ((frac >> (shift - 1)) & 1) subnormal += 1; - return sign | subnormal; - } else if (exp >= 31) { - // Overflow → Inf or NaN - if (frac == 0) { - return (uint16_t)(sign | 0x7C00); // Inf - } else { - return (uint16_t)(sign | 0x7C00 | (frac >> 13)); // NaN - } - } - - // Normal range: round and pack - uint16_t h_exp = (uint16_t)(exp << 10); - uint16_t h_frac = (uint16_t)(frac >> 13); - // Round to nearest - if (frac & 0x00001000) { - h_frac += 1; - if (h_frac == 0x0400) { // mantissa overflow - h_frac = 0; - h_exp += 0x0400; - if (h_exp >= 0x7C00) h_exp = 0x7C00; // clamp to Inf - } - } - - return (uint16_t)(sign | h_exp | h_frac); -} - // MARK: - SQLite Utils - bool sqlite_system_exists (sqlite3 *db, const char *name, const char *type) { @@ -621,7 +522,7 @@ static size_t vector_type_to_size (vector_type type) { switch (type) { case VECTOR_TYPE_F32: return sizeof(float); case VECTOR_TYPE_F16: return sizeof(uint16_t); - case VECTOR_TYPE_BF16: return sizeof(bfloat16_t); + case VECTOR_TYPE_BF16: return sizeof(uint16_t); case VECTOR_TYPE_U8: return sizeof(uint8_t); case VECTOR_TYPE_I8: return sizeof(int8_t); } @@ -1324,7 +1225,7 @@ static void vector_cleanup (sqlite3_context *context, int argc, sqlite3_value ** // MARK: - -static void *vector_convert_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vector_type type, const char *json, int *size, int dimension) { +static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vector_type type, const char *json, int *size, int dimension) { char *blob = NULL; // skip leading whitespace @@ -1356,7 +1257,7 @@ static void *vector_convert_from_json (sqlite3_context *context, sqlite3_vtab *v uint8_t *uint8_blob = (uint8_t *)blob; int8_t *int8_blob = (int8_t *)blob; uint16_t *uint16_blob = (uint16_t *)blob; - bfloat16_t *bfloat16_blob = (bfloat16_t *)blob; + uint16_t *bfloat16_blob = (uint16_t *)blob; int count = 0; const char *p = json; @@ -1390,7 +1291,7 @@ static void *vector_convert_from_json (sqlite3_context *context, sqlite3_vtab *v break; case VECTOR_TYPE_F16: - uint16_blob[count++] = fp16_ieee_from_fp32_value((float)value); + uint16_blob[count++] = float32_to_float16((float)value); break; case VECTOR_TYPE_BF16: @@ -1451,7 +1352,7 @@ static void *vector_convert_from_json (sqlite3_context *context, sqlite3_vtab *v return blob; } -static void vector_convert (sqlite3_context *context, vector_type type, int argc, sqlite3_value **argv) { +static void vector_as_type (sqlite3_context *context, vector_type type, int argc, sqlite3_value **argv) { sqlite3_value *value = argv[0]; int value_size = sqlite3_value_bytes(value); int value_type = sqlite3_value_type(value); @@ -1485,7 +1386,7 @@ static void vector_convert (sqlite3_context *context, vector_type type, int argc return; } - char *blob = vector_convert_from_json(context, NULL, type, json, &value_size, dimension); + char *blob = vector_from_json(context, NULL, type, json, &value_size, dimension); if (!blob) return; // error is set in the context sqlite3_result_blob(context, (const void *)blob, value_size, sqlite3_free); @@ -1495,24 +1396,24 @@ static void vector_convert (sqlite3_context *context, vector_type type, int argc context_result_error(context, SQLITE_ERROR, "Unsupported input type: only BLOB and TEXT values are accepted (received %s).", sqlite_type_name(value_type)); } -static void vector_convert_f32 (sqlite3_context *context, int argc, sqlite3_value **argv) { - vector_convert(context, VECTOR_TYPE_F32, argc, argv); +static void vector_as_f32 (sqlite3_context *context, int argc, sqlite3_value **argv) { + vector_as_type(context, VECTOR_TYPE_F32, argc, argv); } -static void vector_convert_f16 (sqlite3_context *context, int argc, sqlite3_value **argv) { - vector_convert(context, VECTOR_TYPE_F16, argc, argv); +static void vector_as_f16 (sqlite3_context *context, int argc, sqlite3_value **argv) { + vector_as_type(context, VECTOR_TYPE_F16, argc, argv); } -static void vector_convert_bf16 (sqlite3_context *context, int argc, sqlite3_value **argv) { - vector_convert(context, VECTOR_TYPE_BF16, argc, argv); +static void vector_as_bf16 (sqlite3_context *context, int argc, sqlite3_value **argv) { + vector_as_type(context, VECTOR_TYPE_BF16, argc, argv); } -static void vector_convert_u8 (sqlite3_context *context, int argc, sqlite3_value **argv) { - vector_convert(context, VECTOR_TYPE_U8, argc, argv); +static void vector_as_u8 (sqlite3_context *context, int argc, sqlite3_value **argv) { + vector_as_type(context, VECTOR_TYPE_U8, argc, argv); } -static void vector_convert_i8 (sqlite3_context *context, int argc, sqlite3_value **argv) { - vector_convert(context, VECTOR_TYPE_I8, argc, argv); +static void vector_as_i8 (sqlite3_context *context, int argc, sqlite3_value **argv) { + vector_as_type(context, VECTOR_TYPE_I8, argc, argv); } // MARK: - Modules - @@ -1559,8 +1460,8 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char int vsize = 0; if (sqlite3_value_type(argv[2]) == SQLITE_TEXT) { vsize = sqlite3_value_bytes(argv[2]); - vector = (const void *)vector_convert_from_json(NULL, &vtab->base, t_ctx->options.v_type, (const char *)sqlite3_value_text(argv[2]), &vsize, t_ctx->options.v_dim); - if (!vector) return SQLITE_ERROR; // error already set inside vector_convert_from_json + vector = (const void *)vector_from_json(NULL, &vtab->base, t_ctx->options.v_type, (const char *)sqlite3_value_text(argv[2]), &vsize, t_ctx->options.v_dim); + if (!vector) return SQLITE_ERROR; // error already set inside vector_from_json } else { vector = (const void *)sqlite3_value_blob(argv[2]); vsize = sqlite3_value_bytes(argv[2]); @@ -2089,24 +1990,24 @@ SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const s rc = sqlite3_create_function(db, "vector_cleanup", 2, SQLITE_UTF8, ctx, vector_cleanup, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - rc = sqlite3_create_function(db, "vector_convert_f32", 1, SQLITE_UTF8, ctx, vector_convert_f32, NULL, NULL); - rc = sqlite3_create_function(db, "vector_convert_f32", 2, SQLITE_UTF8, ctx, vector_convert_f32, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_f32", 1, SQLITE_UTF8, ctx, vector_as_f32, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_f32", 2, SQLITE_UTF8, ctx, vector_as_f32, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - rc = sqlite3_create_function(db, "vector_convert_f16", 1, SQLITE_UTF8, ctx, vector_convert_f16, NULL, NULL); - rc = sqlite3_create_function(db, "vector_convert_f16", 2, SQLITE_UTF8, ctx, vector_convert_f16, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_f16", 1, SQLITE_UTF8, ctx, vector_as_f16, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_f16", 2, SQLITE_UTF8, ctx, vector_as_f16, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - rc = sqlite3_create_function(db, "vector_convert_bf16", 1, SQLITE_UTF8, ctx, vector_convert_bf16, NULL, NULL); - rc = sqlite3_create_function(db, "vector_convert_bf16", 2, SQLITE_UTF8, ctx, vector_convert_bf16, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_bf16", 1, SQLITE_UTF8, ctx, vector_as_bf16, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_bf16", 2, SQLITE_UTF8, ctx, vector_as_bf16, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - rc = sqlite3_create_function(db, "vector_convert_i8", 1, SQLITE_UTF8, ctx, vector_convert_i8, NULL, NULL); - rc = sqlite3_create_function(db, "vector_convert_i8", 2, SQLITE_UTF8, ctx, vector_convert_i8, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_i8", 1, SQLITE_UTF8, ctx, vector_as_i8, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_i8", 2, SQLITE_UTF8, ctx, vector_as_i8, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - rc = sqlite3_create_function(db, "vector_convert_u8", 1, SQLITE_UTF8, ctx, vector_convert_u8, NULL, NULL); - rc = sqlite3_create_function(db, "vector_convert_u8", 2, SQLITE_UTF8, ctx, vector_convert_u8, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_u8", 1, SQLITE_UTF8, ctx, vector_as_u8, NULL, NULL); + rc = sqlite3_create_function(db, "vector_as_u8", 2, SQLITE_UTF8, ctx, vector_as_u8, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_module(db, "vector_full_scan", &vFullScanModule, ctx); diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 7bf057f..0154251 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.4" +#define SQLITE_VECTOR_VERSION "0.9.5" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 42462e5224ef5a652f55e4bc2592f1baa5bdf81a Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 22 Aug 2025 14:12:21 +0200 Subject: [PATCH 002/108] Removed check for unsupported types. --- src/sqlite-vector.c | 5 ----- src/sqlite-vector.h | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 218006a..68a1c1f 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -1899,11 +1899,6 @@ static void vector_init (sqlite3_context *context, int argc, sqlite3_value **arg return; } - if ((options.v_type == VECTOR_TYPE_F16) || (options.v_type == VECTOR_TYPE_BF16)) { - context_result_error(context, SQLITE_ERROR, "FLOAT16 and FLOATB16 vector types are currently under development. Please update to the latest version."); - return; - } - // check if table is already loaded vector_context *v_ctx = (vector_context *)sqlite3_user_data(context); table_context *t_ctx = vector_context_lookup(v_ctx, table_name, column_name); diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 0154251..77ebd46 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.5" +#define SQLITE_VECTOR_VERSION "0.9.6" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 96a637775229f99d16c90f7c6964c23225507436 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 22 Aug 2025 14:27:16 +0200 Subject: [PATCH 003/108] Update distance-cpu.h --- src/distance-cpu.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/distance-cpu.h b/src/distance-cpu.h index 83745d2..33a803b 100644 --- a/src/distance-cpu.h +++ b/src/distance-cpu.h @@ -15,13 +15,18 @@ // Detect builtin bit_cast #ifndef HAVE_BUILTIN_BIT_CAST + /* Only use __builtin_bit_cast if the compiler has it AND + we're compiling as C++ (GCC 11+) or as a C standard that supports it (C23+). */ #if defined(__has_builtin) #if __has_builtin(__builtin_bit_cast) - #define HAVE_BUILTIN_BIT_CAST 1 + #if defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) + #define HAVE_BUILTIN_BIT_CAST 1 + #endif #endif #endif - /* GCC 11+ also has __builtin_bit_cast */ - #if !defined(HAVE_BUILTIN_BIT_CAST) && defined(__GNUC__) && !defined(__clang__) + + /* GCC note: in GCC 11–13, __builtin_bit_cast exists for C++ but NOT for C. */ + #if !defined(HAVE_BUILTIN_BIT_CAST) && defined(__GNUC__) && !defined(__clang__) && defined(__cplusplus) #if __GNUC__ >= 11 #define HAVE_BUILTIN_BIT_CAST 1 #endif From 274484d0bd6c5d63fb33c861d3b57a22f578971b Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 22 Aug 2025 14:32:57 +0200 Subject: [PATCH 004/108] Fix SQLite test command to show errors --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5de5ec9..2997440 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ $(BUILD_DIR)/%.o: %.c $(CC) $(CFLAGS) -O3 -fPIC -c $< -o $@ test: $(TARGET) - $(SQLITE3) ":memory:" -cmd ".bail on" ".load ./$<" "SELECT vector_version();" + $(SQLITE3) ":memory:" -cmd ".bail on" ".load ./vector" "SELECT vector_version();" # Clean up generated files clean: From 83c81680a59ce355f3a8043f36eeff4db3a4201f Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 22 Aug 2025 14:34:34 +0200 Subject: [PATCH 005/108] Fix SQLite test command typo --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2997440..8a72f21 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ $(BUILD_DIR)/%.o: %.c $(CC) $(CFLAGS) -O3 -fPIC -c $< -o $@ test: $(TARGET) - $(SQLITE3) ":memory:" -cmd ".bail on" ".load ./vector" "SELECT vector_version();" + $(SQLITE3) ":memory:" -cmd ".bail on" ".load ./dist/vector" "SELECT vector_version();" # Clean up generated files clean: From 470f70ee791a66fca549d01586c04306c1637ef4 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 22 Aug 2025 14:42:36 +0200 Subject: [PATCH 006/108] Add math library flag to Android build in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8a72f21..478969f 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ else ifeq ($(PLATFORM),android) CC = $(BIN)/$(ARCH)-linux-android26-clang TARGET := $(DIST_DIR)/vector.so - LDFLAGS += -shared + LDFLAGS += -lm -shared else ifeq ($(PLATFORM),ios) TARGET := $(DIST_DIR)/vector.dylib SDK := -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0 From 17362f6af6ae6829686662365b8dd56c08738814 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 22 Aug 2025 16:08:03 +0200 Subject: [PATCH 007/108] Update README.md --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 83c6bbc..600e261 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ **SQLite Vector** is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. It works seamlessly on **iOS, Android, Windows, Linux, and macOS**, using just **30MB of memory** by default. With support for **Float32, Float16, BFloat16, Int8, and UInt8**, and **highly optimized distance functions**, it's the ideal solution for **Edge AI** applications. -## 🚀 Highlights +## Highlights -* ✅ **No virtual tables required** – store vectors directly as `BLOB`s in ordinary tables -* ✅ **Blazing fast** – optimized C implementation with SIMD acceleration -* ✅ **Low memory footprint** – defaults to just 30MB of RAM usage -* ✅ **Zero preindexing needed** – no long preprocessing or index-building phases -* ✅ **Works offline** – perfect for on-device, privacy-preserving AI workloads -* ✅ **Plug-and-play** – drop into existing SQLite workflows with minimal effort -* ✅ **Cross-platform** – works out of the box on all major OSes +* **No virtual tables required** – store vectors directly as `BLOB`s in ordinary tables +* **Blazing fast** – optimized C implementation with SIMD acceleration +* **Low memory footprint** – defaults to just 30MB of RAM usage +* **Zero preindexing needed** – no long preprocessing or index-building phases +* **Works offline** – perfect for on-device, privacy-preserving AI workloads +* **Plug-and-play** – drop into existing SQLite workflows with minimal effort +* **Cross-platform** – works out of the box on all major OSes ## Why Use SQLite-Vector? @@ -28,7 +28,7 @@ Unlike other vector databases or extensions that require complex setup, SQLite-Vector **just works** with your existing database schema and tools. -## 📦 Installation +## Installation ### Pre-built Binaries @@ -85,13 +85,13 @@ SELECT e.id, v.distance FROM images AS e ON e.id = v.rowid; ``` -## 📋 Documentation +## Documentation Extensive API documentation can be found in the [API page](https://github.com/sqliteai/sqlite-vector/blob/main/API.md). More information about the quantization process can be found in the [QUANTIZATION document](https://github.com/sqliteai/sqlite-vector/blob/main/QUANTIZATION.md). -## 🧩 Features +## Features ### Instant Vector Search – No Preindexing Required @@ -99,10 +99,10 @@ Unlike other SQLite vector extensions that rely on complex indexing algorithms s This means: -* 🕒 **No waiting time** before your app or service is usable -* 🔄 **Zero-cost updates** – you can add, remove, or modify vectors on the fly without rebuilding any index -* ⚡ **Works directly with BLOB columns** in ordinary SQLite tables – no special schema or virtual table required -* 🌍 **Ideal for edge and mobile use cases**, where preprocessing large datasets is not practical or possible +* **No waiting time** before your app or service is usable +* **Zero-cost updates** – you can add, remove, or modify vectors on the fly without rebuilding any index +* **Works directly with BLOB columns** in ordinary SQLite tables – no special schema or virtual table required +* **Ideal for edge and mobile use cases**, where preprocessing large datasets is not practical or possible By eliminating the need for heavyweight indexing, `sqlite-vector` offers a **simpler, faster, and more developer-friendly** approach to embedding vector search in your applications. @@ -133,7 +133,7 @@ These are implemented in pure C and optimized for SIMD when available, ensuring --- -# 🧠 What Is Vector Search? +# What Is Vector Search? Vector search is the process of finding the closest match(es) to a given vector (a point in high-dimensional space) based on a similarity or distance metric. It is essential for AI and machine learning applications where data is often encoded into vector embeddings. @@ -148,18 +148,18 @@ Vector search is the process of finding the closest match(es) to a given vector In the AI era, embeddings are everywhere – from language models like GPT to vision transformers. Storing and searching them efficiently is the foundation of intelligent applications. -## 🌍 Perfect for Edge AI +## Perfect for Edge AI SQLite-Vector is designed with the **Edge AI** use case in mind: -* 📴 Runs offline – no internet required -* 📱 Works on mobile devices – iOS/Android friendly -* 🔒 Keeps data local – ideal for privacy-focused apps -* ⚡ Extremely fast – real-time performance on device +* Runs offline – no internet required +* Works on mobile devices – iOS/Android friendly +* Keeps data local – ideal for privacy-focused apps +* Extremely fast – real-time performance on device You can deploy powerful similarity search capabilities right inside your app or embedded system – **no cloud needed**. -## 📦 Integrations +## Integrations Use SQLite-AI alongside: From ea2ab42cfe33d94c314b046d94662cba83b2ec5d Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 22 Aug 2025 16:44:27 +0200 Subject: [PATCH 008/108] Fixed an incomplete error message. --- src/sqlite-vector.c | 2 +- src/sqlite-vector.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 68a1c1f..8c866db 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -1471,7 +1471,7 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char char buffer[STATIC_SQL_SIZE]; char *name = generate_quant_table_name(table_name, column_name, buffer); if (!name || !sqlite_table_exists(vtab->db, name)) { - sqlite_vtab_set_error(&vtab->base, "Quantization table not found for table '%s' and column '%s'. Ensure that vector_quantize() has been called before using vector_quantize_scan()."); + sqlite_vtab_set_error(&vtab->base, "Quantization table not found for table '%s' and column '%s'. Ensure that vector_quantize() has been called before using vector_quantize_scan().", table_name, column_name); return SQLITE_ERROR; } } diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 77ebd46..2cf4cc4 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.6" +#define SQLITE_VECTOR_VERSION "0.9.7" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From a3d742e735bd86d95e21b68a6544f869d7e8a870 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 22 Aug 2025 16:52:04 +0200 Subject: [PATCH 009/108] Update LICENSE.md to explicitly grant usage for open-source projects --- LICENSE.md | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 26609a0..038c004 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,26 +1,43 @@ -Elastic License 2.0 +# LICENSE.md + +Elastic License 2.0 (modified for open-source use) Copyright © 2025 SQLite Cloud, Inc. -This software is licensed under the Elastic License 2.0. +This software is licensed under the Elastic License 2.0, with the additional grant described below. -You may not use this file except in compliance with the Elastic License 2.0. +You may not use this file except in compliance with the Elastic License 2.0 and the conditions outlined here. You may obtain a copy of the Elastic License 2.0 at: - https://www.elastic.co/licensing/elastic-license +``` +https://www.elastic.co/licensing/elastic-license +``` Software distributed under the Elastic License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Elastic License 2.0 for the specific language governing permissions and limitations under the license. -The following conditions apply: +--- + +## Additional Grant for Open-Source Projects + +In addition to the permissions granted under the Elastic License 2.0: + +* **Free Use in Open-Source Projects**: + You may use, copy, distribute, and prepare derivative works of the software — in source or object form, with or without modification — freely and without fee, provided the software is incorporated into or used by an **open-source project** licensed under an OSI-approved open-source license. + +--- + +## Conditions + +1. For **open-source projects**, the software may be used, copied, modified, and distributed without restriction or fee. -1. You may use, copy, distribute, and prepare derivative works of the software, in each case in source or object form, with or without modification, **only for non-production use or with a license from SQLite Cloud, Inc. for production use.** +2. For **non–open-source or commercial production use**, you may use, copy, distribute, and prepare derivative works of the software only with a commercial license from SQLite Cloud, Inc. -2. You may not provide the software to third parties as a managed service, such as a hosted or cloud-based service, unless you have a license for that use. +3. You may not provide the software to third parties as a managed service, such as a hosted or cloud-based service, unless you have a license for that use. -3. The software may not be used to circumvent the license grant limitations. +4. The software may not be used to circumvent the license grant limitations. -4. Any permitted use is subject to compliance with the Elastic License 2.0 and applicable law. +5. Any permitted use is subject to compliance with the Elastic License 2.0, this additional grant, and applicable law. From d6a061ac8eea1b25a05b090e852cc5f3e80042c1 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 22 Aug 2025 16:56:41 +0200 Subject: [PATCH 010/108] Update LICENSE.md --- LICENSE.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 038c004..69b13f2 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,3 @@ -# LICENSE.md - Elastic License 2.0 (modified for open-source use) Copyright © 2025 SQLite Cloud, Inc. From 05a975f8390a3162114a5cd2dcf1cdbd725259bf Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Sat, 23 Aug 2025 09:44:27 +0200 Subject: [PATCH 011/108] Fixed several issues related to non FLOAT32 types quantization --- src/sqlite-vector.c | 350 ++++++++++++++++++++++++++++++++++++++------ src/sqlite-vector.h | 2 +- 2 files changed, 305 insertions(+), 47 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 8c866db..9cbe04d 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -53,6 +53,13 @@ SQLITE_EXTENSION_INIT1 #define DEBUG_VECTOR(...) #endif +#define DEBUG_VECTOR_SERIALIZATION 0 +#if DEBUG_VECTOR_SERIALIZATION +#define VECTOR_PRINT(_b,_t,_n) vector_print(_b,_t,_n) +#else +#define VECTOR_PRINT(_b,_t,_n) +#endif + #define SKIP_SPACES(_p) while (*(_p) && isspace((unsigned char)*(_p))) (_p)++ #define TRIM_TRAILING(_start, _len) while ((_len) > 0 && isspace((unsigned char)(_start)[(_len) - 1])) (_len)-- @@ -451,9 +458,31 @@ static int sqlite_unserialize (sqlite3_context *context, table_context *ctx) { return rc; } -// MARK: - General Utils - +// MARK: - Quantization - + +static inline uint8_t q_round_u8 (float s) { + if (!isfinite(s)) { + return (s > 0.0f) ? 255u : 0u; /* NaN -> 0, +Inf -> 255, -Inf -> 0 */ + } + float r = s + 0.5f * (1.0f - 2.0f * (s < 0.0f)); /* half away from zero */ + if (r >= 255.0f) return 255u; + if (r <= 0.0f) return 0u; + int ir = (int)r; /* safe after the clamp above */ + return (uint8_t)ir; +} + +static inline int8_t q_round_s8 (float s) { + if (!isfinite(s)) { + return (s > 0.0f) ? 127 : (s < 0.0f ? -128 : 0); + } + /* half-away-from-zero */ + float r = s + 0.5f * (1.0f - 2.0f * (s < 0.0f)); + if (r >= 127.0f) return 127; + if (r <= -128.0f) return -128; + return (int8_t)(int)r; +} -static inline void quantize_float32_to_unsigned8bit (float *v, uint8_t *q, float offset, float scale, int n) { +static inline void quantize_float32_to_unsigned8bit (const float *v, uint8_t *q, float offset, float scale, int n) { int i = 0; for (; i + 3 < n; i += 4) { float s0 = (v[i] - offset) * scale; @@ -486,7 +515,83 @@ static inline void quantize_float32_to_unsigned8bit (float *v, uint8_t *q, float } } -static inline void quantize_float32_to_signed8bit (float *v, int8_t *q, float offset, float scale, int n) { +static inline void quantize_float16_to_unsigned8bit (const uint16_t *v, uint8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = float16_to_float32(v[i ]); + float x1 = float16_to_float32(v[i + 1]); + float x2 = float16_to_float32(v[i + 2]); + float x3 = float16_to_float32(v[i + 3]); + + q[i ] = q_round_u8((x0 - offset) * scale); + q[i + 1] = q_round_u8((x1 - offset) * scale); + q[i + 2] = q_round_u8((x2 - offset) * scale); + q[i + 3] = q_round_u8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = float16_to_float32(v[i]); + q[i] = q_round_u8((x - offset) * scale); + } +} + +static inline void quantize_bfloat16_to_unsigned8bit (const uint16_t *v, uint8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = bfloat16_to_float32(v[i ]); + float x1 = bfloat16_to_float32(v[i + 1]); + float x2 = bfloat16_to_float32(v[i + 2]); + float x3 = bfloat16_to_float32(v[i + 3]); + + q[i ] = q_round_u8((x0 - offset) * scale); + q[i + 1] = q_round_u8((x1 - offset) * scale); + q[i + 2] = q_round_u8((x2 - offset) * scale); + q[i + 3] = q_round_u8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = bfloat16_to_float32(v[i]); + q[i] = q_round_u8((x - offset) * scale); + } +} + +static inline void quantize_u8_to_unsigned8bit (const uint8_t *v, uint8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = (float)v[i ]; + float x1 = (float)v[i + 1]; + float x2 = (float)v[i + 2]; + float x3 = (float)v[i + 3]; + + q[i ] = q_round_u8((x0 - offset) * scale); + q[i + 1] = q_round_u8((x1 - offset) * scale); + q[i + 2] = q_round_u8((x2 - offset) * scale); + q[i + 3] = q_round_u8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = (float)v[i]; + q[i] = q_round_u8((x - offset) * scale); + } +} + +static inline void quantize_i8_to_unsigned8bit (const int8_t *v, uint8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = (float)v[i ]; + float x1 = (float)v[i + 1]; + float x2 = (float)v[i + 2]; + float x3 = (float)v[i + 3]; + + q[i ] = q_round_u8((x0 - offset) * scale); + q[i + 1] = q_round_u8((x1 - offset) * scale); + q[i + 2] = q_round_u8((x2 - offset) * scale); + q[i + 3] = q_round_u8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = (float)v[i]; + q[i] = q_round_u8((x - offset) * scale); + } +} + +static inline void quantize_float32_to_signed8bit (const float *v, int8_t *q, float offset, float scale, int n) { int i = 0; for (; i + 3 < n; i += 4) { float s0 = (v[i] - offset) * scale; @@ -518,6 +623,109 @@ static inline void quantize_float32_to_signed8bit (float *v, int8_t *q, float of } } +static inline void quantize_float16_to_signed8bit (const uint16_t *v, int8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = float16_to_float32(v[i ]); + float x1 = float16_to_float32(v[i + 1]); + float x2 = float16_to_float32(v[i + 2]); + float x3 = float16_to_float32(v[i + 3]); + + q[i ] = q_round_s8((x0 - offset) * scale); + q[i + 1] = q_round_s8((x1 - offset) * scale); + q[i + 2] = q_round_s8((x2 - offset) * scale); + q[i + 3] = q_round_s8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = float16_to_float32(v[i]); + q[i] = q_round_s8((x - offset) * scale); + } +} + +static inline void quantize_bfloat16_to_signed8bit (const uint16_t *v, int8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = bfloat16_to_float32(v[i ]); + float x1 = bfloat16_to_float32(v[i + 1]); + float x2 = bfloat16_to_float32(v[i + 2]); + float x3 = bfloat16_to_float32(v[i + 3]); + + q[i ] = q_round_s8((x0 - offset) * scale); + q[i + 1] = q_round_s8((x1 - offset) * scale); + q[i + 2] = q_round_s8((x2 - offset) * scale); + q[i + 3] = q_round_s8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = bfloat16_to_float32(v[i]); + q[i] = q_round_s8((x - offset) * scale); + } +} + +static inline void quantize_u8_to_signed8bit (const uint8_t *v, int8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = (float)v[i ]; + float x1 = (float)v[i + 1]; + float x2 = (float)v[i + 2]; + float x3 = (float)v[i + 3]; + + q[i ] = q_round_s8((x0 - offset) * scale); + q[i + 1] = q_round_s8((x1 - offset) * scale); + q[i + 2] = q_round_s8((x2 - offset) * scale); + q[i + 3] = q_round_s8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = (float)v[i]; + q[i] = q_round_s8((x - offset) * scale); + } +} + +static inline void quantize_i8_to_signed8bit (const int8_t *v, int8_t *q, float offset, float scale, int n) { + int i = 0; + for (; i + 3 < n; i += 4) { + float x0 = (float)v[i ]; + float x1 = (float)v[i + 1]; + float x2 = (float)v[i + 2]; + float x3 = (float)v[i + 3]; + + q[i ] = q_round_s8((x0 - offset) * scale); + q[i + 1] = q_round_s8((x1 - offset) * scale); + q[i + 2] = q_round_s8((x2 - offset) * scale); + q[i + 3] = q_round_s8((x3 - offset) * scale); + } + for (; i < n; ++i) { + float x = (float)v[i]; + q[i] = q_round_s8((x - offset) * scale); + } +} + +static inline void quantize_float32 (const float *v, uint8_t *q, float offset, float scale, int dim, vector_qtype qtype) { + if (qtype == VECTOR_QUANT_U8BIT) quantize_float32_to_unsigned8bit(v, q, offset, scale, dim); + else quantize_float32_to_signed8bit(v, (int8_t *)q, offset, scale, dim); +} + +static inline void quantize_float16 (const uint16_t *v, uint8_t *q, float offset, float scale, int dim, vector_qtype qtype) { + if (qtype == VECTOR_QUANT_U8BIT) quantize_float16_to_unsigned8bit(v, q, offset, scale, dim); + else quantize_float16_to_signed8bit(v, (int8_t *)q, offset, scale, dim); +} + +static inline void quantize_bfloat16 (const uint16_t *v, uint8_t *q, float offset, float scale, int dim, vector_qtype qtype) { + if (qtype == VECTOR_QUANT_U8BIT) quantize_bfloat16_to_unsigned8bit(v, q, offset, scale, dim); + else quantize_bfloat16_to_signed8bit(v, (int8_t *)q, offset, scale, dim); +} + +static inline void quantize_u8 (const uint8_t *v, uint8_t *q, float offset, float scale, int dim, vector_qtype qtype) { + if (qtype == VECTOR_QUANT_U8BIT) quantize_u8_to_unsigned8bit(v, q, offset, scale, dim); + else quantize_u8_to_signed8bit(v, (int8_t *)q, offset, scale, dim); +} + +static inline void quantize_i8 (const int8_t *v, uint8_t *q, float offset, float scale, int dim, vector_qtype qtype) { + if (qtype == VECTOR_QUANT_U8BIT) quantize_i8_to_unsigned8bit(v, q, offset, scale, dim); + else quantize_i8_to_signed8bit(v, (int8_t *)q, offset, scale, dim); +} + +// MARK: - General Utils - + static size_t vector_type_to_size (vector_type type) { switch (type) { case VECTOR_TYPE_F32: return sizeof(float); @@ -570,7 +778,7 @@ static vector_distance distance_name_to_type (const char *dname) { const char *vector_distance_to_name (vector_distance type) { switch (type) { case VECTOR_DISTANCE_L2: return "L2"; - case VECTOR_DISTANCE_SQUARED_L2: return "L2 SQUARED"; + case VECTOR_DISTANCE_SQUARED_L2: return "SQUARED_L2"; case VECTOR_DISTANCE_COSINE: return "COSINE"; case VECTOR_DISTANCE_DOT: return "DOT"; case VECTOR_DISTANCE_L1: return "L1"; @@ -578,6 +786,46 @@ const char *vector_distance_to_name (vector_distance type) { return "N/A"; } +#if DEBUG_VECTOR_SERIALIZATION +static void vector_print (void *buf, vector_type type, int n) { + printf("type: %s - dim: %d [", vector_type_to_name(type), n); + for (int i=0; ioptions.q_type = (qtype == VECTOR_QUANT_AUTO) ? VECTOR_QUANT_U8BIT : qtype; + t_ctx->scale = 1.0f; + t_ctx->offset = 0.0f; + return SQLITE_OK; + } } - // max number of vectors that fits in max_memory - uint32_t max_vectors = (uint32_t)(max_memory / q_size); + // max number of vectors that fits in max_memory (per batch; force at least 1) + uint32_t max_vectors = (uint32_t)(max_memory / (uint64_t)q_size); + if (max_vectors == 0) max_vectors = 1; - uint8_t *data = sqlite3_malloc64((sqlite3_uint64)(max_vectors * q_size)); + sqlite3_uint64 out_bytes = (sqlite3_uint64)max_vectors * (sqlite3_uint64)q_size; + uint8_t *data = sqlite3_malloc64(out_bytes); uint8_t *original = data; if (!data) goto vector_rebuild_quantization_cleanup; - tempv = (float *)sqlite3_malloc(sizeof(float) * dim); + sqlite3_uint64 temp_bytes = (sqlite3_uint64)dim * (sqlite3_uint64)sizeof(float); + tempv = (float *)sqlite3_malloc64(temp_bytes); if (!tempv) goto vector_rebuild_quantization_cleanup; // SELECT rowid, embedding FROM table @@ -906,8 +1166,8 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta float min_val = MAXFLOAT; float max_val = -MAXFLOAT; #endif - bool contains_negative = false; + while (1) { rc = sqlite3_step(vm); if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} @@ -915,7 +1175,8 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta const void *blob = (float *)sqlite3_column_blob(vm, 1); int blob_size = sqlite3_column_bytes(vm, 1); - if (!blob || blob_size < dim * vector_type_to_size(type)) { + size_t need_bytes = (size_t)dim * (size_t)vector_type_to_size(type); + if (!blob || blob_size < need_bytes) { context_result_error(context, SQLITE_ERROR, "Invalid or missing vector blob found at rowid %lld.", (long long)sqlite3_column_int64(vm, 0)); rc = SQLITE_ERROR; goto vector_rebuild_quantization_cleanup; @@ -928,7 +1189,7 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta val = ((float *)blob)[i]; break; case VECTOR_TYPE_F16: - val = fp16_ieee_to_fp32_value(((uint16_t *)blob)[i]); + val = float16_to_float32(((uint16_t *)blob)[i]); break; case VECTOR_TYPE_BF16: val = bfloat16_to_float32(((uint16_t *)blob)[i]); @@ -984,40 +1245,23 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta int64_t rowid = (int64_t)sqlite3_column_int64(vm, 0); const void *blob = sqlite3_column_blob(vm, 1); if (n_processed == 0) min_rowid = rowid; - - float *v = tempv; - if (type == VECTOR_TYPE_F32) { - v = (float *)blob; - } else { - for (int i = 0; i < dim; ++i) { - switch (type) { - case VECTOR_TYPE_F32: - break; - case VECTOR_TYPE_F16: - v[i] = fp16_ieee_to_fp32_value(((uint16_t *)blob)[i]); - break; - case VECTOR_TYPE_BF16: - v[i] = bfloat16_to_float32(((uint16_t *)blob)[i]); - break; - case VECTOR_TYPE_U8: - v[i] = (float)(((uint8_t *)blob)[i]); - break; - case VECTOR_TYPE_I8: - v[i] = (float)(((int8_t *)blob)[i]); - break; - } - } - } + VECTOR_PRINT((void *)blob, type, dim); // copy rowid INT64_TO_INT8PTR(rowid, data); data += sizeof(int64_t); // quantize vector - if (qtype == VECTOR_QUANT_U8BIT) quantize_float32_to_unsigned8bit(v, data, offset, scale, dim); - else quantize_float32_to_signed8bit(v, (int8_t *)data, offset, scale, dim); - data += (dim * sizeof(uint8_t)); + switch (type) { + case VECTOR_TYPE_F32: quantize_float32((const float *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_I8: quantize_i8((const int8_t *)blob, data, offset, scale, dim, qtype); break; + } + VECTOR_PRINT((void *)data, (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8, dim); + data += (dim * sizeof(uint8_t)); max_rowid = rowid; ++n_processed; ++tot_processed; @@ -1389,6 +1633,8 @@ static void vector_as_type (sqlite3_context *context, vector_type type, int argc char *blob = vector_from_json(context, NULL, type, json, &value_size, dimension); if (!blob) return; // error is set in the context + VECTOR_PRINT((void *)blob, type, (dimension == 0) ? (value_size / vector_type_to_size(type)) : dimension); + sqlite3_result_blob(context, (const void *)blob, value_size, sqlite3_free); return; } @@ -1466,6 +1712,7 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char vector = (const void *)sqlite3_value_blob(argv[2]); vsize = sqlite3_value_bytes(argv[2]); } + VECTOR_PRINT((void*)vector, t_ctx->options.v_type, t_ctx->options.v_dim); if (check_quant) { char buffer[STATIC_SQL_SIZE]; @@ -1684,6 +1931,7 @@ static int vFullScanRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1 float *v2 = (float *)sqlite3_column_blob(vm, 1); double distance = distance_fn((const void *)v1, (const void *)v2, dimension); + VECTOR_PRINT((void*)v2, vt, dimension); if (distance < c->distance[c->max_index]) { c->distance[c->max_index] = distance; @@ -1747,17 +1995,26 @@ static int vQuantRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1siz uint8_t *v = (uint8_t *)sqlite3_malloc(dimension * sizeof(int8_t)); if (!v) return SQLITE_NOMEM; + // quantize vector vector_qtype qtype = c->table->options.q_type; - if (qtype == VECTOR_QUANT_U8BIT) { - quantize_float32_to_unsigned8bit((float *)v1, v, c->table->offset, c->table->scale, dimension); - } else { - quantize_float32_to_signed8bit((float *)v1, (int8_t *)v, c->table->offset, c->table->scale, dimension); + float offset = c->table->offset; + float scale = c->table->scale; + vector_type type = c->table->options.v_type; + + switch (type) { + case VECTOR_TYPE_F32: quantize_float32((const float *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_I8: quantize_i8((const int8_t *)v1, v, offset, scale, dimension, qtype); break; } + if (c->table->preloaded) { int rc = vQuantRunMemory(c, v, qtype, dimension); if (v) sqlite3_free(v); return rc; } + VECTOR_PRINT((void*)v, (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8, dimension); char sql[STATIC_SQL_SIZE]; generate_select_quant_table(c->table->t_name, c->table->c_name, sql); @@ -1790,6 +2047,7 @@ static int vQuantRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1siz const uint8_t *current_data = data + (i * total_stride); const uint8_t *vector_data = current_data + rowid_size; double distance = (double)distance_fn((const void *)v, (const void *)vector_data, dimension); + VECTOR_PRINT((void*)vector_data, vt, dimension); if (distance < current_max_distance) { c->distance[c->max_index] = distance; diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 2cf4cc4..d42082c 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.7" +#define SQLITE_VECTOR_VERSION "0.9.8" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 41af197d7b68bf97d3fd5733051954d562fce35a Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Sat, 23 Aug 2025 10:21:19 +0200 Subject: [PATCH 012/108] Improved zero rounding --- src/sqlite-vector.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 9cbe04d..544d4c6 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -10,6 +10,7 @@ #include "distance-cpu.h" #include +#include #include #include #include @@ -959,6 +960,10 @@ bool vector_keyvalue_callback (sqlite3_context *context, void *xdata, const char return true; } +static inline int nearly_zero_float32 (float x) { + return fabsf(x) <= 8.0f * FLT_EPSILON; // tweak factor for your use +} + // MARK: - SQL - static char *generate_create_quant_table (const char *table_name, const char *column_name, char sql[STATIC_SQL_SIZE]) { @@ -1930,7 +1935,8 @@ static int vFullScanRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1 if (rc != SQLITE_ROW) goto cleanup; float *v2 = (float *)sqlite3_column_blob(vm, 1); - double distance = distance_fn((const void *)v1, (const void *)v2, dimension); + float distance = distance_fn((const void *)v1, (const void *)v2, dimension); + if (nearly_zero_float32(distance)) distance = 0.0; VECTOR_PRINT((void*)v2, vt, dimension); if (distance < c->distance[c->max_index]) { @@ -1974,7 +1980,8 @@ static int vQuantRunMemory(vFullScanCursor *c, uint8_t *v, vector_qtype qtype, i const uint8_t *vector_data = current_data + rowid_size; float dist = distance_fn((const void *)v, (const void *)vector_data, dim); - + if (nearly_zero_float32(dist)) dist = 0.0; + if (dist < current_max) { distance[max_index] = dist; rowids[max_index] = INT64_FROM_INT8PTR(current_data); @@ -2046,7 +2053,8 @@ static int vQuantRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1siz for (int i=0; i Date: Mon, 25 Aug 2025 09:32:05 +0200 Subject: [PATCH 013/108] NULL vectors are now correctly skipped during scanning --- src/sqlite-vector.c | 16 ++++++++++++++-- src/sqlite-vector.h | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 544d4c6..855b5c2 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -1018,6 +1018,8 @@ void vector_context_free (void *p) { } table_context *vector_context_lookup (vector_context *ctx, const char *table_name, const char *column_name) { + if ((table_name == NULL) || (column_name == NULL)) return NULL; + for (int i=0; itable_count; ++i) { // tname and cname can be NULL after adding vector_cleanup function const char *tname = ctx->tables[i].t_name; @@ -1177,12 +1179,15 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta rc = sqlite3_step(vm); if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} else if (rc != SQLITE_ROW) break; + if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; const void *blob = (float *)sqlite3_column_blob(vm, 1); + if (!blob) continue; + int blob_size = sqlite3_column_bytes(vm, 1); size_t need_bytes = (size_t)dim * (size_t)vector_type_to_size(type); - if (!blob || blob_size < need_bytes) { - context_result_error(context, SQLITE_ERROR, "Invalid or missing vector blob found at rowid %lld.", (long long)sqlite3_column_int64(vm, 0)); + if (blob_size < need_bytes) { + context_result_error(context, SQLITE_ERROR, "Invalid vector blob found at rowid %lld.", (long long)sqlite3_column_int64(vm, 0)); rc = SQLITE_ERROR; goto vector_rebuild_quantization_cleanup; } @@ -1246,9 +1251,12 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta rc = sqlite3_step(vm); if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} else if (rc != SQLITE_ROW) break; + if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; int64_t rowid = (int64_t)sqlite3_column_int64(vm, 0); const void *blob = sqlite3_column_blob(vm, 1); + if (!blob) continue; + if (n_processed == 0) min_rowid = rowid; VECTOR_PRINT((void *)blob, type, dim); @@ -1716,6 +1724,7 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char } else { vector = (const void *)sqlite3_value_blob(argv[2]); vsize = sqlite3_value_bytes(argv[2]); + if (!vector) return sqlite_vtab_set_error(&vtab->base, "%s: input vector cannot be NULL.", fname); } VECTOR_PRINT((void*)vector, t_ctx->options.v_type, t_ctx->options.v_dim); @@ -1933,8 +1942,11 @@ static int vFullScanRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1 rc = sqlite3_step(vm); if (rc == SQLITE_DONE) {rc = SQLITE_OK; goto cleanup;} if (rc != SQLITE_ROW) goto cleanup; + if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; float *v2 = (float *)sqlite3_column_blob(vm, 1); + if (v2 == NULL) continue; + float distance = distance_fn((const void *)v1, (const void *)v2, dimension); if (nearly_zero_float32(distance)) distance = 0.0; VECTOR_PRINT((void*)v2, vt, dimension); diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index d42082c..0d5c58f 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.8" +#define SQLITE_VECTOR_VERSION "0.9.9" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From d6d71bc62cd55a3476168b8315ddca1a66d9e8d3 Mon Sep 17 00:00:00 2001 From: Daniele Briggi Date: Wed, 27 Aug 2025 18:21:01 +0200 Subject: [PATCH 014/108] chore(python): workflow for python package --- .github/workflows/python-package.yml | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..73590ae --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,96 @@ +name: Build and Publish Python Package + +on: + workflow_dispatch: + inputs: + version: + description: "Version to use for the Python package (e.g. 0.9.9)" + required: true + type: string + release: + types: [published] + +jobs: + build-and-publish: + runs-on: ${{ matrix.os }} + permissions: + id-token: write # mandatory for Pypi trusted publishing + strategy: + matrix: + include: + - os: ubuntu-latest + platform: linux + python-version: "3.10" + arch: x86_64 + plat_name: manylinux2014_x86_64 + - os: ubuntu-latest + platform: linux + python-version: "3.10" + arch: arm64 + plat_name: manylinux2014_aarch64 + - os: ubuntu-latest + platform: windows + python-version: "3.10" + arch: x86_64 + plat_name: win_amd64 + - os: ubuntu-latest + platform: macos + python-version: "3.10" + arch: x86_64 + plat_name: macosx_10_9_x86_64 + - os: ubuntu-latest + platform: macos + python-version: "3.10" + arch: arm64 + plat_name: macosx_11_0_arm64 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + with: + submodules: false + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install build dependencies + run: | + cd packages/python + python3 -m pip install --upgrade pip + python3 -m pip install -r requirements-dev.txt + + - name: Get version + id: get_version + run: | + if [[ "${{ github.event_name }}" == "release" ]]; then + VERSION="${{ github.event.release.tag_name }}" + else + VERSION="${{ github.event.inputs.version }}" + fi + VERSION=${VERSION#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Download artifacts for current platform + run: | + cd packages/python + python3 download_artifacts.py "${{ matrix.plat_name }}" "${{ steps.get_version.outputs.version }}" + + - name: Build wheel + env: + PACKAGE_VERSION: ${{ steps.get_version.outputs.version }} + run: | + cd packages/python + python setup.py bdist_wheel --plat-name "${{ matrix.plat_name }}" + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: packages/python/dist + verbose: true + # Avoid workflow to fail if the version has already been published + skip-existing: true + # Upload to Test Pypi for testing + repository-url: https://test.pypi.org/legacy/ From 6c5793ebdcfb46ad223ce64c642e6c3450a37620 Mon Sep 17 00:00:00 2001 From: Daniele Briggi <=> Date: Wed, 27 Aug 2025 18:27:01 +0200 Subject: [PATCH 015/108] feat(python): workflow to build python package --- .github/workflows/python-package.yml | 2 +- README.md | 10 ++ packages/python/MANIFEST.in | 3 + packages/python/README.md | 46 +++++++++ packages/python/download_artifacts.py | 97 +++++++++++++++++++ packages/python/pyproject.toml | 25 +++++ packages/python/requirements-dev.txt | 3 + packages/python/setup.py | 54 +++++++++++ packages/python/src/sqlite-vector/__init__.py | 0 9 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 packages/python/MANIFEST.in create mode 100644 packages/python/README.md create mode 100644 packages/python/download_artifacts.py create mode 100644 packages/python/pyproject.toml create mode 100644 packages/python/requirements-dev.txt create mode 100644 packages/python/setup.py create mode 100644 packages/python/src/sqlite-vector/__init__.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 73590ae..68ddc38 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -93,4 +93,4 @@ jobs: # Avoid workflow to fail if the version has already been published skip-existing: true # Upload to Test Pypi for testing - repository-url: https://test.pypi.org/legacy/ + # repository-url: https://test.pypi.org/legacy/ diff --git a/README.md b/README.md index 600e261..791f6da 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,16 @@ SELECT load_extension('./vector'); Or embed it directly into your application. +### Python Package + +Python developers can quickly get started using the ready-to-use `sqlite-vector` package available on PyPI: + +```bash +pip install sqlite-vector +``` + +For usage details and examples, see the [Python package documentation](./packages/python/README.md). + ## Example Usage ```sql diff --git a/packages/python/MANIFEST.in b/packages/python/MANIFEST.in new file mode 100644 index 0000000..610b692 --- /dev/null +++ b/packages/python/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include LICENSE +recursive-include src/sqlite-vector/binaries * diff --git a/packages/python/README.md b/packages/python/README.md new file mode 100644 index 0000000..cf8910e --- /dev/null +++ b/packages/python/README.md @@ -0,0 +1,46 @@ +## SQLite Vector Python package + +This package provides the sqlite-vector extension prebuilt binaries for multiple platforms and architectures. + +### SQLite Vector + +SQLite Vector is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. It works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default. With support for Float32, Float16, BFloat16, Int8, and UInt8, and highly optimized distance functions, it's the ideal solution for Edge AI applications. + +More details on the official repository [sqliteai/sqlite-vector](https://github.com/sqliteai/sqlite-vector). + +### Documentation + +For detailed information on all available functions, their parameters, and examples, refer to the [comprehensive API Reference](https://github.com/sqliteai/sqlite-vector/blob/main/API.md). + +### Supported Platforms and Architectures + +| Platform | Arch | Subpackage name | Binary name | +| ------------- | ------------ | ------------------------ | ------------ | +| Linux (CPU) | x86_64/arm64 | sqlite-vector.binaries | vector.so | +| Windows (CPU) | x86_64 | sqlite-vector.binaries | vector.dll | +| macOS (CPU) | x86_64/arm64 | sqlite-vector.binaries | vector.dylib | + +## Usage + +> **Note:** Some SQLite installations on certain operating systems may have extension loading disabled by default. +If you encounter issues loading the extension, refer to the [sqlite-extensions-guide](https://github.com/sqliteai/sqlite-extensions-guide/) for platform-specific instructions on enabling and using SQLite extensions. + +```python +import importlib.resources +import sqlite3 + +# Connect to your SQLite database +conn = sqlite3.connect("example.db") + +# Load the sqlite-vector extension +# pip will install the correct binary package for your platform and architecture +ext_path = importlib.resources.files("sqlite-vector.binaries") / "vector" + +conn.enable_load_extension(True) +conn.load_extension(str(ext_path)) +conn.enable_load_extension(False) + + +# Now you can use sqlite-vector features in your SQL queries +print(conn.execute("SELECT vector_version();").fetchone()) +``` \ No newline at end of file diff --git a/packages/python/download_artifacts.py b/packages/python/download_artifacts.py new file mode 100644 index 0000000..95aaf4b --- /dev/null +++ b/packages/python/download_artifacts.py @@ -0,0 +1,97 @@ +import sys +import zipfile +import requests +from pathlib import Path +import shutil + + +# == USAGE == +# python3 download_artifacts.py PLATFORM VERSION +# eg: python3 download_artifacts.py linux_x86_64 "0.5.9" + +REPO = "sqliteai/sqlite-vector" +RELEASE_URL = f"https://github.com/{REPO}/releases/download" + +# Map Python plat_name to artifact names +ARTIFACTS = { + "manylinux2014_x86_64": ["vector-linux-x86_64"], + "manylinux2014_aarch64": [ + "vector-linux-arm64", + ], + "win_amd64": ["vector-windows-x86_64"], + "macosx_10_9_x86_64": ["vector-macos"], + "macosx_11_0_arm64": ["vector-macos"], +} + +BINARY_NAME = { + "manylinux2014_x86_64": "vector.so", + "manylinux2014_aarch64": "vector.so", + "win_amd64": "vector.dll", + "macosx_10_9_x86_64": "vector.dylib", + "macosx_11_0_arm64": "vector.dylib", +} + +BINARIES_DIR = Path(__file__).parent / "src/sqlite-vector/binaries" + + +def download_and_extract(artifact_name, bin_name, version): + artifact = f"{artifact_name}-{version}.zip" + url = f"{RELEASE_URL}/{version}/{artifact}" + print(f"Downloading {url}") + + r = requests.get(url) + if r.status_code != 200: + print(f"Failed to download {artifact}: {r.status_code}") + sys.exit(1) + + zip_path = BINARIES_DIR / artifact + with open(zip_path, "wb") as f: + f.write(r.content) + + out_dir = BINARIES_DIR + out_dir.mkdir(parents=True, exist_ok=True) + + with zipfile.ZipFile(zip_path, "r") as zip_ref: + for member in zip_ref.namelist(): + if member.endswith(bin_name): + zip_ref.extract(member, out_dir) + + # Move to expected name/location + src = out_dir / member + dst = out_dir / bin_name + src.rename(dst) + + print(f"Extracted {dst}") + + zip_path.unlink() + + +def main(): + version = None + platform = None + if len(sys.argv) == 3: + platform = sys.argv[1].lower() + version = sys.argv[2] + + if not version or not platform: + print( + 'Error: Version is not specified.\nUsage: \n python3 download_artifacts.py linux_x86_64 "0.5.9"' + ) + sys.exit(1) + + print(BINARIES_DIR) + if BINARIES_DIR.exists(): + shutil.rmtree(BINARIES_DIR) + BINARIES_DIR.mkdir(parents=True, exist_ok=True) + + platform_artifacts = ARTIFACTS.get(platform, []) + if not platform_artifacts: + print(f"Error: Unknown platform '{platform}'") + sys.exit(1) + + for artifact_name in platform_artifacts: + download_and_extract(artifact_name, BINARY_NAME[platform], version) + + +if __name__ == "__main__": + main() diff --git a/packages/python/pyproject.toml b/packages/python/pyproject.toml new file mode 100644 index 0000000..fa39816 --- /dev/null +++ b/packages/python/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel", "toml"] +build-backend = "setuptools.build_meta" + +[project] +name = "sqlite-vector" +dynamic = ["version"] +description = "Python prebuilt binaries for SQLite Vector extension for all supported platforms and architectures." +authors = [ + { name = "SQLite AI Team" } +] +readme = "README.md" +requires-python = ">=3" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X" +] + +[project.urls] +Homepage = "https://sqlite.ai" +Documentation = "https://github.com/sqliteai/sqlite-vector/blob/main/API.md" +Repository = "https://github.com/sqliteai/sqlite-vector" +Issues = "https://github.com/sqliteai/sqlite-vector/issues" diff --git a/packages/python/requirements-dev.txt b/packages/python/requirements-dev.txt new file mode 100644 index 0000000..6760087 --- /dev/null +++ b/packages/python/requirements-dev.txt @@ -0,0 +1,3 @@ +requests +toml +wheel \ No newline at end of file diff --git a/packages/python/setup.py b/packages/python/setup.py new file mode 100644 index 0000000..baae9de --- /dev/null +++ b/packages/python/setup.py @@ -0,0 +1,54 @@ +import setuptools +import toml +import os +import sys + +usage = """ +Usage: python setup.py bdist_wheel --plat-name +The PACKAGE_VERSION environment variable must be set to the desired version. + +Example: + PACKAGE_VERSION=0.5.9 python setup.py bdist_wheel --plat-name linux_x86_64 +""" + +with open("pyproject.toml", "r") as f: + pyproject = toml.load(f) + +project = pyproject["project"] + +# Get version from environment or default +version = os.environ.get("PACKAGE_VERSION", "") +if not version: + print("PACKAGE_VERSION environment variable is not set.") + print(usage) + sys.exit(1) + +# Get Python platform name from --plat-name argument +plat_name = None +for i, arg in enumerate(sys.argv): + if arg == "--plat-name" and i + 1 < len(sys.argv): + plat_name = sys.argv[i + 1] + break + +if not plat_name: + print("Error: --plat-name argument is required") + print(usage) + sys.exit(1) + +with open("README.md", "r", encoding="utf-8") as f: + long_description = f.read() + +setuptools.setup( + name=project["name"], + version=version, + description=project.get("description", ""), + author=project["authors"][0]["name"], + long_description=long_description, + long_description_content_type="text/markdown", + url=project["urls"]["Homepage"], + packages=setuptools.find_packages(where="src"), + package_dir={"": "src"}, + include_package_data=True, + python_requires=project.get("requires-python", ">=3"), + classifiers=project.get("classifiers", []), +) diff --git a/packages/python/src/sqlite-vector/__init__.py b/packages/python/src/sqlite-vector/__init__.py new file mode 100644 index 0000000..e69de29 From c7cbc58aac60fed31b11479d9611b99e4f465a62 Mon Sep 17 00:00:00 2001 From: Daniele Briggi Date: Thu, 28 Aug 2025 15:33:27 +0200 Subject: [PATCH 016/108] chore(python): change package name --- packages/python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/pyproject.toml b/packages/python/pyproject.toml index fa39816..ddf057f 100644 --- a/packages/python/pyproject.toml +++ b/packages/python/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=61.0", "wheel", "toml"] build-backend = "setuptools.build_meta" [project] -name = "sqlite-vector" +name = "sqliteai-vector" dynamic = ["version"] description = "Python prebuilt binaries for SQLite Vector extension for all supported platforms and architectures." authors = [ From abdd43fe02b1ee24a5d2243fa9a9f9a8a40bc7c4 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 28 Aug 2025 16:06:25 +0200 Subject: [PATCH 017/108] Update API.md --- API.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 340c7a6..6d78dbe 100644 --- a/API.md +++ b/API.md @@ -97,6 +97,8 @@ SELECT vector_init('documents', 'embedding', 'dimension=384,type=FLOAT32,distanc Performs quantization on the specified table and column. This precomputes internal data structures to support fast approximate nearest neighbor (ANN) search. Read more about quantization [here](https://github.com/sqliteai/sqlite-vector/blob/main/QUANTIZATION.md). +If a quantization already exists for the specified table and column, it is replaced. If it was previously loaded into memory using `vector_quantize_preload`, the data is automatically reloaded. + **Parameters:** * `table` (TEXT): Name of the table. @@ -153,7 +155,10 @@ SELECT vector_quantize_preload('documents', 'embedding'); **Returns:** `NULL` **Description:** -Cleans up internal structures related to a previously quantized table/column. Use this if data has changed or quantization is no longer needed. +Releases memory previously allocated by a vector_quantize_preload call and removes all quantization entries associated with the specified table and column. +Use this function when the underlying data has changed or when quantization is no longer required. In some cases, running VACUUM may be necessary to reclaim the freed space from the database. + +This function should only be called when quantization is no longer needed. If the data changes and you invoke vector_quantize, the old quantization data is automatically replaced. **Example:** From a0f72c8521a95479b10f1691ab4540ffd21a94b7 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 28 Aug 2025 16:14:46 +0200 Subject: [PATCH 018/108] Update API.md --- API.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/API.md b/API.md index 6d78dbe..d7c17fd 100644 --- a/API.md +++ b/API.md @@ -56,6 +56,7 @@ SELECT vector_backend(); **Description:** Initializes the vector extension for a given table and column. This is **mandatory** before performing any vector search or quantization. +`vector_init` must be called in every database connection that needs to perform vector operations. **Parameters:** @@ -97,7 +98,7 @@ SELECT vector_init('documents', 'embedding', 'dimension=384,type=FLOAT32,distanc Performs quantization on the specified table and column. This precomputes internal data structures to support fast approximate nearest neighbor (ANN) search. Read more about quantization [here](https://github.com/sqliteai/sqlite-vector/blob/main/QUANTIZATION.md). -If a quantization already exists for the specified table and column, it is replaced. If it was previously loaded into memory using `vector_quantize_preload`, the data is automatically reloaded. +If a quantization already exists for the specified table and column, it is replaced. If it was previously loaded into memory using `vector_quantize_preload`, the data is automatically reloaded. `vector_quantize` should be called once after data insertion. If called multiple times, the previous quantized data is replaced. The resulting quantization is shared across all database connections, so they do not need to call it again. **Parameters:** @@ -139,8 +140,7 @@ SELECT vector_quantize_memory('documents', 'embedding'); **Description:** Loads the quantized representation for the specified table and column into memory. Should be used at startup to ensure optimal query performance. - -Execute it after `vector_quantize()` to reflect changes. +`vector_quantize_preload` should be called once after `vector_quantize`. The preloaded data is also shared across all database connections, so they do not need to call it again. **Example:** From af456aa98289a6bc657172bf28d147cf3c5037bb Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 28 Aug 2025 16:18:52 +0200 Subject: [PATCH 019/108] Update API.md --- API.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API.md b/API.md index d7c17fd..65b7c53 100644 --- a/API.md +++ b/API.md @@ -150,7 +150,7 @@ SELECT vector_quantize_preload('documents', 'embedding'); --- -## `vector_cleanup(table, column)` +## `vector_quantize_cleanup(table, column)` **Returns:** `NULL` @@ -163,7 +163,7 @@ This function should only be called when quantization is no longer needed. If th **Example:** ```sql -SELECT vector_cleanup('documents', 'embedding'); +SELECT vector_quantize_cleanup('documents', 'embedding'); ``` --- From b897268dd3c026ddab5705e412ac951ad78da33b Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 28 Aug 2025 16:33:52 +0200 Subject: [PATCH 020/108] Update API.md --- API.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 65b7c53..1133f40 100644 --- a/API.md +++ b/API.md @@ -92,9 +92,11 @@ SELECT vector_init('documents', 'embedding', 'dimension=384,type=FLOAT32,distanc ## `vector_quantize(table, column, options)` -**Returns:** `NULL` +**Returns:** `INTEGER` **Description:** +Returns the total number of succesfully quantized rows. + Performs quantization on the specified table and column. This precomputes internal data structures to support fast approximate nearest neighbor (ANN) search. Read more about quantization [here](https://github.com/sqliteai/sqlite-vector/blob/main/QUANTIZATION.md). From a7fcb26db680ceb67ece6730d4b246f9fce4f46f Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 28 Aug 2025 16:34:46 +0200 Subject: [PATCH 021/108] Renamed vector_cleanup to vector_quantized_cleanup. --- src/sqlite-vector.c | 179 +++++++++++++++++++++++--------------------- src/sqlite-vector.h | 2 +- 2 files changed, 96 insertions(+), 85 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 855b5c2..08eb651 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -1113,12 +1113,13 @@ static int vector_serialize_quantization (sqlite3 *db, const char *table_name, c return rc; } -static int vector_rebuild_quantization (sqlite3_context *context, const char *table_name, const char *column_name, table_context *t_ctx, vector_qtype qtype, uint64_t max_memory) { +static int vector_rebuild_quantization (sqlite3_context *context, const char *table_name, const char *column_name, table_context *t_ctx, vector_qtype qtype, uint64_t max_memory, uint32_t *count) { int rc = SQLITE_NOMEM; sqlite3_stmt *vm = NULL; char sql[STATIC_SQL_SIZE]; sqlite3 *db = sqlite3_context_db_handle(context); + uint32_t tot_processed = 0; const char *pk_name = t_ctx->pk_name; int dim = t_ctx->options.v_dim; @@ -1245,7 +1246,6 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta // STEP 3 // actual quantization (ONLY 8bit is supported in this version) uint32_t n_processed = 0; - uint32_t tot_processed = 0; int64_t min_rowid = 0, max_rowid = 0; while (1) { rc = sqlite3_step(vm); @@ -1299,16 +1299,86 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta if (original) sqlite3_free(original); if (tempv) sqlite3_free(tempv); if (vm) sqlite3_finalize(vm); + if (count) *count = tot_processed; return rc; } -static void vector_quantize (sqlite3_context *context, const char *table_name, const char *column_name, const char *arg_options) { +static void vector_quantize_preload (sqlite3_context *context, int argc, sqlite3_value **argv) { + int types[] = {SQLITE_TEXT, SQLITE_TEXT}; + if (sanity_check_args(context, "vector_quantize_preload", argc, argv, 2, types) == false) return; + + const char *table_name = (const char *)sqlite3_value_text(argv[0]); + const char *column_name = (const char *)sqlite3_value_text(argv[1]); + + vector_context *v_ctx = (vector_context *)sqlite3_user_data(context); + table_context *t_ctx = vector_context_lookup(v_ctx, table_name, column_name); + if (!t_ctx) { + context_result_error(context, SQLITE_ERROR, "Vector context not found for table '%s' and column '%s'. Ensure that vector_init() has been called before using vector_quantize_preload().", table_name, column_name); + return; + } + + if (t_ctx->preloaded) { + sqlite3_free(t_ctx->preloaded); + t_ctx->preloaded = NULL; + t_ctx->precounter = 0; + } + + char sql[STATIC_SQL_SIZE]; + generate_memory_quant_table(table_name, column_name, sql); + sqlite3 *db = sqlite3_context_db_handle(context); + sqlite3_int64 required = sqlite_read_int64(db, sql); + if (required == 0) { + context_result_error(context, SQLITE_ERROR, "Unable to read data from database. Ensure that vector_quantize() has been called before using vector_quantize_preload()."); + return; + } + + int counter = 0; + void *buffer = (void *)sqlite3_malloc64(required); + if (!buffer) { + context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to allocate %lld bytes for quant buffer.", (long long)required); + return; + } + + generate_select_quant_table(table_name, column_name, sql); + + int rc = SQLITE_NOMEM; + sqlite3_stmt *vm = NULL; + rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL); + if (rc != SQLITE_OK) goto vector_preload_cleanup; + + int seek = 0; + while (1) { + rc = sqlite3_step(vm); + if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} // return error: rebuild must be call (only if first time run) + else if (rc != SQLITE_ROW) goto vector_preload_cleanup; + + int n = sqlite3_column_int(vm, 0); + int bytes = sqlite3_column_bytes(vm, 1); + uint8_t *data = (uint8_t *)sqlite3_column_blob(vm, 1); + + memcpy(buffer+seek, data, bytes); + seek += bytes; + counter += n; + } + rc = SQLITE_OK; + + t_ctx->preloaded = buffer; + t_ctx->precounter = counter; + +vector_preload_cleanup: + if (rc != SQLITE_OK) printf("Error in vector_quantize_preload: %s\n", sqlite3_errmsg(db)); + if (vm) sqlite3_finalize(vm); + return; +} + +static int vector_quantize (sqlite3_context *context, const char *table_name, const char *column_name, const char *arg_options, bool *was_preloaded) { table_context *t_ctx = vector_context_lookup((vector_context *)sqlite3_user_data(context), table_name, column_name); if (!t_ctx) { context_result_error(context, SQLITE_ERROR, "Vector context not found for table '%s' and column '%s'. Ensure that vector_init() has been called before using vector_quantize().", table_name, column_name); - return; + return SQLITE_ERROR; } + uint32_t counter = 0; int rc = SQLITE_ERROR; char sql[STATIC_SQL_SIZE]; sqlite3 *db = sqlite3_context_db_handle(context); @@ -1326,9 +1396,9 @@ static void vector_quantize (sqlite3_context *context, const char *table_name, c vector_options options = vector_options_create(); bool res = parse_keyvalue_string(context, arg_options, vector_keyvalue_callback, &options); - if (res == false) return; + if (res == false) return SQLITE_ERROR; - rc = vector_rebuild_quantization(context, table_name, column_name, t_ctx, options.q_type, options.max_memory); + rc = vector_rebuild_quantization(context, table_name, column_name, t_ctx, options.q_type, options.max_memory, &counter); if (rc != SQLITE_OK) goto quantize_cleanup; rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); @@ -1347,8 +1417,13 @@ static void vector_quantize (sqlite3_context *context, const char *table_name, c printf("%s", sqlite3_errmsg(db)); sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL); sqlite3_result_error_code(context, rc); - return; + return rc; } + + // returns the total number of quantized rows + sqlite3_result_int64(context, (sqlite3_int64)counter); + if (was_preloaded) *was_preloaded = (t_ctx->preloaded != NULL); + return SQLITE_OK; } static void vector_quantize3 (sqlite3_context *context, int argc, sqlite3_value **argv) { @@ -1358,7 +1433,10 @@ static void vector_quantize3 (sqlite3_context *context, int argc, sqlite3_value const char *table_name = (const char *)sqlite3_value_text(argv[0]); const char *column_name = (const char *)sqlite3_value_text(argv[1]); const char *options = (const char *)sqlite3_value_text(argv[2]); - vector_quantize(context, table_name, column_name, options); + + bool was_preloaded = false; + int rc = vector_quantize(context, table_name, column_name, options, &was_preloaded); + if ((rc == SQLITE_OK) && (was_preloaded)) vector_quantize_preload(context, argc, argv); } static void vector_quantize2 (sqlite3_context *context, int argc, sqlite3_value **argv) { @@ -1367,7 +1445,10 @@ static void vector_quantize2 (sqlite3_context *context, int argc, sqlite3_value const char *table_name = (const char *)sqlite3_value_text(argv[0]); const char *column_name = (const char *)sqlite3_value_text(argv[1]); - vector_quantize(context, table_name, column_name, NULL); + + bool was_preloaded = false; + int rc = vector_quantize(context, table_name, column_name, NULL, &was_preloaded); + if ((rc == SQLITE_OK) && (was_preloaded)) vector_quantize_preload(context, argc, argv); } static void vector_quantize_memory (sqlite3_context *context, int argc, sqlite3_value **argv) { @@ -1385,99 +1466,29 @@ static void vector_quantize_memory (sqlite3_context *context, int argc, sqlite3_ sqlite3_result_int64(context, memory); } -static void vector_quantize_preload (sqlite3_context *context, int argc, sqlite3_value **argv) { +static void vector_quantize_cleanup (sqlite3_context *context, int argc, sqlite3_value **argv) { int types[] = {SQLITE_TEXT, SQLITE_TEXT}; - if (sanity_check_args(context, "vector_quantize_preload", argc, argv, 2, types) == false) return; + if (sanity_check_args(context, "vector_quantize_cleanup", argc, argv, 2, types) == false) return; const char *table_name = (const char *)sqlite3_value_text(argv[0]); const char *column_name = (const char *)sqlite3_value_text(argv[1]); vector_context *v_ctx = (vector_context *)sqlite3_user_data(context); table_context *t_ctx = vector_context_lookup(v_ctx, table_name, column_name); - if (!t_ctx) { - context_result_error(context, SQLITE_ERROR, "Vector context not found for table '%s' and column '%s'. Ensure that vector_init() has been called before using vector_quantize_preload().", table_name, column_name); - return; - } + if (!t_ctx) return; // if no table context exists then do nothing + // release any memory used in quantization if (t_ctx->preloaded) { sqlite3_free(t_ctx->preloaded); t_ctx->preloaded = NULL; t_ctx->precounter = 0; } - char sql[STATIC_SQL_SIZE]; - generate_memory_quant_table(table_name, column_name, sql); - sqlite3 *db = sqlite3_context_db_handle(context); - sqlite3_int64 required = sqlite_read_int64(db, sql); - if (required == 0) { - context_result_error(context, SQLITE_ERROR, "Unable to read data from database. Ensure that vector_quantize() has been called before using vector_quantize_preload()."); - return; - } - - int counter = 0; - void *buffer = (void *)sqlite3_malloc64(required); - if (!buffer) { - context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to allocate %lld bytes for quant buffer.", (long long)required); - return; - } - - generate_select_quant_table(table_name, column_name, sql); - - int rc = SQLITE_NOMEM; - sqlite3_stmt *vm = NULL; - rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL); - if (rc != SQLITE_OK) goto vector_preload_cleanup; - - int seek = 0; - while (1) { - rc = sqlite3_step(vm); - if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} // return error: rebuild must be call (only if first time run) - else if (rc != SQLITE_ROW) goto vector_preload_cleanup; - - int n = sqlite3_column_int(vm, 0); - int bytes = sqlite3_column_bytes(vm, 1); - uint8_t *data = (uint8_t *)sqlite3_column_blob(vm, 1); - - memcpy(buffer+seek, data, bytes); - seek += bytes; - counter += n; - } - rc = SQLITE_OK; - - t_ctx->preloaded = buffer; - t_ctx->precounter = counter; - -vector_preload_cleanup: - if (rc != SQLITE_OK) printf("Error in vector_quantize_preload: %s\n", sqlite3_errmsg(db)); - if (vm) sqlite3_finalize(vm); - return; -} - -static void vector_cleanup (sqlite3_context *context, int argc, sqlite3_value **argv) { - int types[] = {SQLITE_TEXT, SQLITE_TEXT}; - if (sanity_check_args(context, "vector_cleanup", argc, argv, 2, types) == false) return; - - const char *table_name = (const char *)sqlite3_value_text(argv[0]); - const char *column_name = (const char *)sqlite3_value_text(argv[1]); - - vector_context *v_ctx = (vector_context *)sqlite3_user_data(context); - table_context *t_ctx = vector_context_lookup(v_ctx, table_name, column_name); - if (!t_ctx) return; // if no table context exists then do nothing - - // release memory - if (t_ctx->t_name) sqlite3_free(t_ctx->t_name); - if (t_ctx->c_name) sqlite3_free(t_ctx->c_name); - if (t_ctx->pk_name) sqlite3_free(t_ctx->pk_name); - if (t_ctx->preloaded) sqlite3_free(t_ctx->preloaded); - memset(t_ctx, 0, sizeof(table_context)); - // drop quant table (if any) char sql[STATIC_SQL_SIZE]; sqlite3 *db = sqlite3_context_db_handle(context); generate_drop_quant_table(table_name, column_name, sql); sqlite3_exec(db, sql, NULL, NULL, NULL); - - // do not decrease v_ctx->table_count } // MARK: - @@ -2260,7 +2271,7 @@ SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const s if (rc != SQLITE_OK) goto cleanup; // table_name, column_name - rc = sqlite3_create_function(db, "vector_cleanup", 2, SQLITE_UTF8, ctx, vector_cleanup, NULL, NULL); + rc = sqlite3_create_function(db, "vector_quantize_cleanup", 2, SQLITE_UTF8, ctx, vector_quantize_cleanup, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_f32", 1, SQLITE_UTF8, ctx, vector_as_f32, NULL, NULL); diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 0d5c58f..4de659c 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.9" +#define SQLITE_VECTOR_VERSION "0.9.10" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 2776e5d48e2d3e55c4c2abcd23665eaa29733285 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni <48024736+Gioee@users.noreply.github.com> Date: Thu, 28 Aug 2025 18:18:30 +0200 Subject: [PATCH 022/108] Bump SQLITE_VECTOR_VERSION to 0.9.11 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 4de659c..bc0c306 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.10" +#define SQLITE_VECTOR_VERSION "0.9.11" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 0d809fd51b1fdbd07b8eb95f9a4e7f8c65c43233 Mon Sep 17 00:00:00 2001 From: Daniele Briggi Date: Thu, 28 Aug 2025 18:29:07 +0200 Subject: [PATCH 023/108] Change release event type from 'published' to 'released' --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 68ddc38..220be6f 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -8,7 +8,7 @@ on: required: true type: string release: - types: [published] + types: [released] jobs: build-and-publish: From 2ab73b8712b1cc4ef1238ba598c95b875d3cfaf9 Mon Sep 17 00:00:00 2001 From: Daniele Briggi Date: Thu, 28 Aug 2025 18:53:07 +0200 Subject: [PATCH 024/108] Update API.md for vector_quantize_cleanup function --- API.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/API.md b/API.md index 1133f40..8466f38 100644 --- a/API.md +++ b/API.md @@ -157,10 +157,10 @@ SELECT vector_quantize_preload('documents', 'embedding'); **Returns:** `NULL` **Description:** -Releases memory previously allocated by a vector_quantize_preload call and removes all quantization entries associated with the specified table and column. -Use this function when the underlying data has changed or when quantization is no longer required. In some cases, running VACUUM may be necessary to reclaim the freed space from the database. +Releases memory previously allocated by a `vector_quantize_preload` call and removes all quantization entries associated with the specified table and column. +Use this function when quantization is no longer required. In some cases, running VACUUM may be necessary to reclaim the freed space from the database. -This function should only be called when quantization is no longer needed. If the data changes and you invoke vector_quantize, the old quantization data is automatically replaced. +If the data changes and you invoke `vector_quantize`, the existing quantization data is automatically replaced. In that case, calling this function is unnecessary. **Example:** From cb47f649d40ce81ddcae6e0b7f9988465b63c101 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 29 Aug 2025 15:42:31 +0200 Subject: [PATCH 025/108] Added preliminary support for vector_full_scan_stream and vector_quantize_scan_stream --- src/sqlite-vector.c | 373 +++++++++++++++++++++++++++++++++++++++++--- src/sqlite-vector.h | 2 +- 2 files changed, 356 insertions(+), 19 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 08eb651..f37844c 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -145,16 +145,34 @@ typedef struct { typedef struct { sqlite3_vtab_cursor base; // Base class - must be first + table_context *table; + + // STREAMING VT INTERFACE + bool is_streaming; + bool is_quantized; + struct { + int64_t rowid; + double distance; + distance_function_t distance_fn; + + sqlite3_stmt *vm; + void *vector; + int vsize; + int vdim; + + void *data; + int dcounter; + int dindex; + int is_eof; + } stream; + // NON-STREAMING VT INTERFACE int64_t *rowids; double *distance; - int size; int max_index; int row_index; int row_count; - - table_context *table; } vFullScanCursor; typedef bool (*keyvalue_callback)(sqlite3_context *context, void *xdata, const char *key, int key_len, const char *value, int value_len); @@ -236,6 +254,15 @@ static char *sqlite_strdup (const char *str) { return result; } +static void *sqlite_memdup (const void *v, int len) { + if (!v) return NULL; + + void *result = (void *)sqlite3_malloc((int)len); + if (result) memcpy(result, v, len); + + return result; +} + bool sqlite_column_exists (sqlite3 *db, const char *table_name, const char *column_name) { char sql[STATIC_SQL_SIZE]; sqlite3_snprintf(sizeof(sql), sql, "SELECT EXISTS(SELECT 1 FROM pragma_table_info('%q') WHERE name = ?1);", table_name); @@ -1688,14 +1715,20 @@ static void vector_as_i8 (sqlite3_context *context, int argc, sqlite3_value **ar // MARK: - Modules - -static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv, const char *fname, vcursor_run_callback run_callback, vcursor_sort_callback sort_callback, bool check_quant) { +static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv, const char *fname, vcursor_run_callback run_callback, vcursor_sort_callback sort_callback, bool quantized) { vFullScanCursor *c = (vFullScanCursor *)cur; vFullScan *vtab = (vFullScan *)cur->pVtab; + bool is_streaming = (sort_callback == NULL); + bool is_quantized = quantized; + c->is_streaming = is_streaming; + c->is_quantized = is_quantized; + // sanity check arguments - if (argc != 4) { - return sqlite_vtab_set_error(&vtab->base, "%s expects %d arguments, but %d were provided.", fname, 4, argc); + int nargs = (is_streaming) ? 3 : 4; + if (argc != nargs) { + return sqlite_vtab_set_error(&vtab->base, "%s expects %d arguments, but %d were provided.", fname, nargs, argc); } // SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT or SQLITE_BLOB, SQLITE_INTEGER @@ -1739,7 +1772,7 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char } VECTOR_PRINT((void*)vector, t_ctx->options.v_type, t_ctx->options.v_dim); - if (check_quant) { + if (quantized) { char buffer[STATIC_SQL_SIZE]; char *name = generate_quant_table_name(table_name, column_name, buffer); if (!name || !sqlite_table_exists(vtab->db, name)) { @@ -1748,9 +1781,13 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char } } - int k = sqlite3_value_int(argv[3]); + c->table = t_ctx; + if (is_streaming) { + return run_callback(vtab->db, c, vector, vsize); + } - // nothing needs to be returned + // non-streaming flow + int k = (is_streaming) ? 0 : sqlite3_value_int(argv[3]); if (k == 0) return SQLITE_DONE; if (c->row_count != k) { @@ -1767,7 +1804,6 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char for (int i=0; idistance[i] = INFINITY; c->size = 0; - c->table = t_ctx; c->row_index = 0; c->row_count = k; @@ -1851,34 +1887,130 @@ static int vFullScanCursorClose (sqlite3_vtab_cursor *cur){ vFullScanCursor *c = (vFullScanCursor *)cur; if (c->rowids) sqlite3_free(c->rowids); if (c->distance) sqlite3_free(c->distance); + if (c->stream.vector) sqlite3_free(c->stream.vector); + if (c->stream.vm) sqlite3_finalize(c->stream.vm); sqlite3_free(c); return SQLITE_OK; } static int vFullScanCursorNext (sqlite3_vtab_cursor *cur){ vFullScanCursor *c = (vFullScanCursor *)cur; - c->row_index++; + + // non-streaming flow + if (!c->is_streaming) { c->row_index++; return SQLITE_OK; } + + // streaming flow + sqlite3_stmt *vm = c->stream.vm; + void *v1 = c->stream.vector; + int dimension = c->stream.vdim; + distance_function_t distance_fn = c->stream.distance_fn; + + // FULL-SCAN + if (!c->is_quantized) { + while (1) { + int rc = sqlite3_step(vm); + if (rc == SQLITE_DONE) { c->stream.is_eof = 1; return SQLITE_OK; } + else if (rc != SQLITE_ROW) return rc; + + // skip NULL values + if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; + + const float *v2 = (const float *)sqlite3_column_blob(vm, 1); + if (v2 == NULL) continue; + + float distance = distance_fn((const void *)v1, (const void *)v2, dimension); + if (nearly_zero_float32(distance)) distance = 0.0f; + + c->stream.distance = distance; + c->stream.rowid = (int64_t)sqlite3_column_int64(vm, 0); + return SQLITE_OK; + } + } + + // QUANTIZATION sizes + const size_t rowid_size = sizeof(int64_t); + const size_t vector_size = (size_t)dimension * sizeof(uint8_t); + const size_t total_stride = rowid_size + vector_size; + + // QUANTIZED IN-MEMORY + if (vm == NULL) { + if ((c->is_quantized == false) || (c->stream.data == NULL)) return SQLITE_MISUSE; + + // EOF if we've already consumed all items + if (c->stream.dindex >= c->stream.dcounter) { + c->stream.is_eof = 1; + return SQLITE_OK; + } + + const uint8_t *data = (const uint8_t *)c->stream.data; + size_t i = (size_t)c->stream.dindex; + + const uint8_t *current_data = data + (i * total_stride); + const uint8_t *vector_data = current_data + rowid_size; + + + // no NULL vectors here by construction + float distance = distance_fn((const void *)v1, (const void *)vector_data, dimension); + if (nearly_zero_float32(distance)) distance = 0.0f; + + c->stream.distance = distance; + c->stream.rowid = INT64_FROM_INT8PTR(current_data); + c->stream.dindex++; + return SQLITE_OK; + } + + // QUANTIZED FROM DISK (chunked) + if (c->stream.dcounter == 0) { + int rc = sqlite3_step(vm); + if (rc == SQLITE_DONE) { c->stream.is_eof = 1; return SQLITE_OK; } + else if (rc != SQLITE_ROW) return rc; + + c->stream.dcounter = sqlite3_column_int(vm, 0); + c->stream.data = (uint8_t *)sqlite3_column_blob(vm, 1); + c->stream.dindex = 0; // reset index for the new chunk + } + + const uint8_t *data = (const uint8_t *)c->stream.data; + size_t i = (size_t)c->stream.dindex; + + const uint8_t *current_data = data + (i * total_stride); + const uint8_t *vector_data = current_data + rowid_size; + + float distance = distance_fn((const void *)v1, (const void *)vector_data, dimension); + if (nearly_zero_float32(distance)) distance = 0.0f; + + c->stream.distance = distance; + c->stream.rowid = INT64_FROM_INT8PTR(current_data); + c->stream.dindex++; + + if (c->stream.dindex == c->stream.dcounter) { + // finished current chunk; force reload on next call + c->stream.dcounter = 0; + c->stream.data = NULL; // clear stale pointer to blob memory + } + return SQLITE_OK; } + static int vFullScanCursorEof (sqlite3_vtab_cursor *cur){ vFullScanCursor *c = (vFullScanCursor *)cur; - return (c->row_index == c->row_count); + return (c->is_streaming) ? c->stream.is_eof : (c->row_index == c->row_count); } static int vFullScanCursorColumn (sqlite3_vtab_cursor *cur, sqlite3_context *context, int iCol) { vFullScanCursor *c = (vFullScanCursor *)cur; if (iCol == VECTOR_COLUMN_ROWID) { - sqlite3_result_int64(context, (sqlite3_int64)c->rowids[c->row_index]); + sqlite3_result_int64(context, (c->is_streaming) ? (sqlite3_int64)c->stream.rowid : (sqlite3_int64)c->rowids[c->row_index]); } else if (iCol == VECTOR_COLUMN_DISTANCE) { - sqlite3_result_double(context, c->distance[c->row_index]); + sqlite3_result_double(context, (c->is_streaming) ? c->stream.distance : c->distance[c->row_index]); } return SQLITE_OK; } static int vFullScanCursorRowid (sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) { vFullScanCursor *c = (vFullScanCursor *)cur; - *pRowid = (sqlite_int64)c->rowids[c->row_index]; + *pRowid = (c->is_streaming) ? (sqlite3_int64)c->stream.rowid : (sqlite_int64)c->rowids[c->row_index]; return SQLITE_OK; } @@ -2102,7 +2234,150 @@ static int vQuantRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1siz static int vQuantCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { return vCursorFilterCommon(cur, idxNum, idxStr, argc, argv, "vector_quantize_scan", vQuantRun, vFullScanSortSlots, true); } - + +// MARK: - Streaming Modules - + +static int vStreamScanBestIndex (sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) { + // Do NOT set p->orderByConsumed; we can’t guarantee order. + // Optionally: if ORDER BY distance is present, bump cost/rows a bit. + pIdxInfo->estimatedCost = 1e8; + pIdxInfo->estimatedRows = 100000; + + const struct sqlite3_index_constraint *pConstraint = pIdxInfo->aConstraint; + for(int i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable == 0 ) continue; + if( pConstraint->op != SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case VECTOR_COLUMN_IDX: + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + case VECTOR_COLUMN_VECTOR: + pIdxInfo->aConstraintUsage[i].argvIndex = 2; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + case VECTOR_COLUMN_K: + pIdxInfo->aConstraintUsage[i].argvIndex = 3; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + case VECTOR_COLUMN_MEMIDX: + pIdxInfo->aConstraintUsage[i].argvIndex = 4; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + } + } + return SQLITE_OK; +} + +static int vStreamScanCursorOpen (sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor) { + int rc = vFullScanCursorOpen(pVtab, ppCursor); + if (rc != SQLITE_OK) return rc; + + vFullScanCursor *c = (vFullScanCursor *)*ppCursor; + c->is_streaming = true; + return SQLITE_OK; +} + +static int vStreamScanCursorRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1size) { + // duplicate input vector (to be later used by the Next callback) + void *v = sqlite_memdup(v1, v1size); + if (!v) return SQLITE_NOMEM; + + const char *pk_name = c->table->pk_name; + const char *col_name = c->table->c_name; + const char *table_name = c->table->t_name; + int dimension = c->table->options.v_dim; + + c->stream.vector = (void *)v; + c->stream.vsize = v1size; + c->stream.vdim = dimension; + + char *sql = sqlite3_mprintf("SELECT %q, %q FROM %q;", pk_name, col_name, table_name); + if (!sql) return SQLITE_NOMEM; + + sqlite3_stmt *vm = NULL; + int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL); + if (rc != SQLITE_OK) goto cleanup; + + // compute distance function + vector_distance vd = c->table->options.v_distance; + vector_type vt = c->table->options.v_type; + distance_function_t distance_fn = dispatch_distance_table[vd][vt]; + + c->stream.distance_fn = distance_fn; + c->stream.vm = vm; + + if (sql) sqlite3_free(sql); + return SQLITE_OK; + +cleanup: + if (sql) sqlite3_free(sql); + if (vm) sqlite3_finalize(vm); + return rc; +} + +static int vStreamQuantCursorRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1size) { + // quantize input vector + int dimension = c->table->options.v_dim; + uint8_t *v = (uint8_t *)sqlite3_malloc(dimension * sizeof(int8_t)); + if (!v) return SQLITE_NOMEM; + + // quantize vector + vector_qtype qtype = c->table->options.q_type; + float offset = c->table->offset; + float scale = c->table->scale; + vector_type type = c->table->options.v_type; + + switch (type) { + case VECTOR_TYPE_F32: quantize_float32((const float *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_I8: quantize_i8((const int8_t *)v1, v, offset, scale, dimension, qtype); break; + } + + c->stream.vector = (void *)v; + c->stream.vsize = (int)(dimension * sizeof(int8_t)); + c->stream.vdim = dimension; + + // compute distance function + vector_distance vd = c->table->options.v_distance; + vector_type vt = (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8; + distance_function_t distance_fn = dispatch_distance_table[vd][vt]; + c->stream.distance_fn = distance_fn; + + // check if quant representation was preloaded + if (c->table->preloaded) { + c->stream.dindex = 0; + c->stream.data = c->table->preloaded; + c->stream.dcounter = c->table->precounter; + return SQLITE_OK; + } + + char sql[STATIC_SQL_SIZE]; + generate_select_quant_table(c->table->t_name, c->table->c_name, sql); + sqlite3_stmt *vm = NULL; + int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL); + if (rc != SQLITE_OK) goto cleanup; + + c->stream.vm = vm; + return SQLITE_OK; + +cleanup: + if (vm) sqlite3_finalize(vm); + return rc; +} + +static int vStreamScanCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { + return vCursorFilterCommon(cur, idxNum, idxStr, argc, argv, "vector_full_scan_stream", vStreamScanCursorRun, NULL, false); +} + +static int vStreamQuantCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { + return vCursorFilterCommon(cur, idxNum, idxStr, argc, argv, "vector_quantize_scan_stream", vStreamQuantCursorRun, NULL, true); +} + +// --------------------------- + static sqlite3_module vFullScanModule = { /* iVersion */ 0, /* xCreate */ 0, @@ -2131,7 +2406,7 @@ static sqlite3_module vFullScanModule = { /* xIntegrity */ 0 }; -static sqlite3_module vQuantModule = { +static sqlite3_module vQuantScanModule = { /* iVersion */ 0, /* xCreate */ 0, /* xConnect */ vFullScanConnect, @@ -2159,6 +2434,62 @@ static sqlite3_module vQuantModule = { /* xIntegrity */ 0 }; +static sqlite3_module vFullScanStreamModule = { + /* iVersion */ 0, + /* xCreate */ 0, + /* xConnect */ vFullScanConnect, + /* xBestIndex */ vStreamScanBestIndex, + /* xDisconnect */ vFullScanDisconnect, + /* xDestroy */ 0, + /* xOpen */ vFullScanCursorOpen, + /* xClose */ vFullScanCursorClose, + /* xFilter */ vStreamScanCursorFilter, + /* xNext */ vFullScanCursorNext, + /* xEof */ vFullScanCursorEof, + /* xColumn */ vFullScanCursorColumn, + /* xRowid */ vFullScanCursorRowid, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindMethod */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0, + /* xShadowName */ 0, + /* xIntegrity */ 0 +}; + +static sqlite3_module vQuantScanStreamModule = { + /* iVersion */ 0, + /* xCreate */ 0, + /* xConnect */ vFullScanConnect, + /* xBestIndex */ vStreamScanBestIndex, + /* xDisconnect */ vFullScanDisconnect, + /* xDestroy */ 0, + /* xOpen */ vFullScanCursorOpen, + /* xClose */ vFullScanCursorClose, + /* xFilter */ vStreamQuantCursorFilter, + /* xNext */ vFullScanCursorNext, + /* xEof */ vFullScanCursorEof, + /* xColumn */ vFullScanCursorColumn, + /* xRowid */ vFullScanCursorRowid, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindMethod */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0, + /* xShadowName */ 0, + /* xIntegrity */ 0 +}; + // MARK: - static void vector_init (sqlite3_context *context, int argc, sqlite3_value **argv) { @@ -2297,7 +2628,13 @@ SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const s rc = sqlite3_create_module(db, "vector_full_scan", &vFullScanModule, ctx); if (rc != SQLITE_OK) goto cleanup; - rc = sqlite3_create_module(db, "vector_quantize_scan", &vQuantModule, ctx); + rc = sqlite3_create_module(db, "vector_quantize_scan", &vQuantScanModule, ctx); + if (rc != SQLITE_OK) goto cleanup; + + rc = sqlite3_create_module(db, "vector_full_scan_stream", &vFullScanStreamModule, ctx); + if (rc != SQLITE_OK) goto cleanup; + + rc = sqlite3_create_module(db, "vector_quantize_scan_stream", &vQuantScanStreamModule, ctx); if (rc != SQLITE_OK) goto cleanup; cleanup: diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index bc0c306..bf3d2cf 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.11" +#define SQLITE_VECTOR_VERSION "0.9.20" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 1a85368e8f67f27106008a082ffcacbd327e2d91 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 29 Aug 2025 22:26:01 +0200 Subject: [PATCH 026/108] Bump SQLITE_VECTOR_VERSION to 0.9.21 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index bf3d2cf..937955a 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.20" +#define SQLITE_VECTOR_VERSION "0.9.21" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 50264f90dc67ee49d5bfd15a910810d982f5ca94 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 29 Aug 2025 22:29:52 +0200 Subject: [PATCH 027/108] fix Makefile indentation --- Makefile | 106 +++++++++++++++++++++++++++---------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index 478969f..4eb4689 100644 --- a/Makefile +++ b/Makefile @@ -7,18 +7,18 @@ SQLITE3 ?= sqlite3 # Set default platform if not specified ifeq ($(OS),Windows_NT) - PLATFORM := windows - HOST := windows - CPUS := $(shell powershell -Command "[Environment]::ProcessorCount") + PLATFORM := windows + HOST := windows + CPUS := $(shell powershell -Command "[Environment]::ProcessorCount") else - HOST = $(shell uname -s | tr '[:upper:]' '[:lower:]') - ifeq ($(HOST),darwin) - PLATFORM := macos - CPUS := $(shell sysctl -n hw.ncpu) - else - PLATFORM := $(HOST) - CPUS := $(shell nproc) - endif + HOST = $(shell uname -s | tr '[:upper:]' '[:lower:]') + ifeq ($(HOST),darwin) + PLATFORM := macos + CPUS := $(shell sysctl -n hw.ncpu) + else + PLATFORM := $(HOST) + CPUS := $(shell nproc) + endif endif # Speed up builds by using all available CPU cores @@ -41,48 +41,48 @@ OBJ_FILES = $(patsubst %.c, $(BUILD_DIR)/%.o, $(notdir $(SRC_FILES))) # Platform-specific settings ifeq ($(PLATFORM),windows) - TARGET := $(DIST_DIR)/vector.dll - LDFLAGS += -shared - # Create .def file for Windows - DEF_FILE := $(BUILD_DIR)/vector.def + TARGET := $(DIST_DIR)/vector.dll + LDFLAGS += -shared + # Create .def file for Windows + DEF_FILE := $(BUILD_DIR)/vector.def else ifeq ($(PLATFORM),macos) - TARGET := $(DIST_DIR)/vector.dylib - LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib -undefined dynamic_lookup - CFLAGS += -arch x86_64 -arch arm64 + TARGET := $(DIST_DIR)/vector.dylib + LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib -undefined dynamic_lookup + CFLAGS += -arch x86_64 -arch arm64 else ifeq ($(PLATFORM),android) - # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH - ifeq ($(filter %,$(ARCH)),) - $(error "Android ARCH must be set to ARCH=x86_64 or ARCH=arm64-v8a") - endif - # Set ANDROID_NDK path to find android build tools - # e.g. on MacOS: export ANDROID_NDK=/Users/username/Library/Android/sdk/ndk/25.2.9519653 - ifeq ($(filter %,$(ANDROID_NDK)),) - $(error "Android NDK must be set") - endif - - BIN = $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(HOST)-x86_64/bin - PATH := $(BIN):$(PATH) - - ifneq (,$(filter $(ARCH),arm64 arm64-v8a)) - override ARCH := aarch64 - endif - - CC = $(BIN)/$(ARCH)-linux-android26-clang - TARGET := $(DIST_DIR)/vector.so - LDFLAGS += -lm -shared + # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH + ifeq ($(filter %,$(ARCH)),) + $(error "Android ARCH must be set to ARCH=x86_64 or ARCH=arm64-v8a") + endif + # Set ANDROID_NDK path to find android build tools + # e.g. on MacOS: export ANDROID_NDK=/Users/username/Library/Android/sdk/ndk/25.2.9519653 + ifeq ($(filter %,$(ANDROID_NDK)),) + $(error "Android NDK must be set") + endif + + BIN = $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(HOST)-x86_64/bin + PATH := $(BIN):$(PATH) + + ifneq (,$(filter $(ARCH),arm64 arm64-v8a)) + override ARCH := aarch64 + endif + + CC = $(BIN)/$(ARCH)-linux-android26-clang + TARGET := $(DIST_DIR)/vector.so + LDFLAGS += -lm -shared else ifeq ($(PLATFORM),ios) - TARGET := $(DIST_DIR)/vector.dylib - SDK := -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0 - LDFLAGS += -dynamiclib $(SDK) - CFLAGS += -arch arm64 $(SDK) + TARGET := $(DIST_DIR)/vector.dylib + SDK := -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0 + LDFLAGS += -dynamiclib $(SDK) + CFLAGS += -arch arm64 $(SDK) else ifeq ($(PLATFORM),isim) - TARGET := $(DIST_DIR)/vector.dylib - SDK := -isysroot $(shell xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0 - LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib $(SDK) - CFLAGS += -arch x86_64 -arch arm64 $(SDK) + TARGET := $(DIST_DIR)/vector.dylib + SDK := -isysroot $(shell xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0 + LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib $(SDK) + CFLAGS += -arch x86_64 -arch arm64 $(SDK) else # linux - TARGET := $(DIST_DIR)/vector.so - LDFLAGS += -shared + TARGET := $(DIST_DIR)/vector.so + LDFLAGS += -shared endif # Windows .def file generation @@ -104,7 +104,7 @@ all: $(TARGET) $(TARGET): $(OBJ_FILES) $(DEF_FILE) $(CC) $(OBJ_FILES) $(DEF_FILE) -o $@ $(LDFLAGS) ifeq ($(PLATFORM),windows) - # Generate import library for Windows + # Generate import library for Windows dlltool -D $@ -d $(DEF_FILE) -l $(DIST_DIR)/vector.lib endif @@ -134,9 +134,9 @@ help: @echo " isim (only on macOS)" @echo "" @echo "Targets:" - @echo " all - Build the extension (default)" - @echo " clean - Remove built files" - @echo " test - Test the extension" - @echo " help - Display this help message" + @echo " all - Build the extension (default)" + @echo " clean - Remove built files" + @echo " test - Test the extension" + @echo " help - Display this help message" .PHONY: all clean test extension help From e57ffe2162f54ab302822c4a4902e143d9c5edc5 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 29 Aug 2025 23:00:32 +0200 Subject: [PATCH 028/108] Update GitHub Actions workflow and Makefile to support Linux MUSL builds, Apple XCFramework builds and sign and notarization process for Apple builds. --- .github/workflows/main.yml | 110 +++++++++++++++++++++++++++---------- Makefile | 95 ++++++++++++++++++++++++++------ 2 files changed, 161 insertions(+), 44 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b03d772..612e2d3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,58 +9,113 @@ permissions: jobs: build: runs-on: ${{ matrix.os }} - name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'isim' && matrix.name != 'ios' && ' + test' || ''}} + container: ${{ matrix.container && matrix.container || '' }} + name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && ' + test' || ''}} timeout-minutes: 20 strategy: fail-fast: false matrix: include: - - os: ubuntu-latest + - os: ubuntu-22.04 arch: x86_64 name: linux - - os: LinuxARM64 + - os: ubuntu-22.04-arm arch: arm64 name: linux - - os: macos-latest + - os: ubuntu-22.04 + arch: x86_64 + name: linux-musl + container: alpine:latest + - os: ubuntu-22.04-arm + arch: arm64 + name: linux-musl + - os: macos-15 name: macos - - os: windows-latest + - os: windows-2022 arch: x86_64 name: windows - - os: ubuntu-latest + - os: ubuntu-22.04 arch: arm64-v8a name: android make: PLATFORM=android ARCH=arm64-v8a - - os: ubuntu-latest + - os: ubuntu-22.04 arch: x86_64 name: android make: PLATFORM=android ARCH=x86_64 sqlite-amalgamation-zip: https://sqlite.org/2025/sqlite-amalgamation-3490100.zip - - os: macos-latest + - os: macos-15 name: ios make: PLATFORM=ios - - os: macos-latest - name: isim - make: PLATFORM=isim + - os: macos-15 + name: ios-sim + make: PLATFORM=ios-sim + - os: macos-15 + name: apple-xcframework + make: xcframework defaults: run: - shell: bash + shell: ${{ matrix.container && 'sh' || 'bash' }} steps: - uses: actions/checkout@v4.2.2 - - name: build sqlite-vector - run: make extension ${{ matrix.make && matrix.make || ''}} - - - name: windows install sqlite3 - if: matrix.os == 'windows-latest' + - name: windows install dependencies + if: matrix.name == 'windows' run: choco install sqlite -y - - name: macos install sqlite3 without SQLITE_OMIT_LOAD_EXTENSION + - name: macos install dependencies if: matrix.name == 'macos' run: brew link sqlite --force + - name: linux-musl x86_64 install dependencies + if: matrix.name == 'linux-musl' && matrix.arch == 'x86_64' + run: apk update && apk add --no-cache gcc make sqlite musl-dev linux-headers + + - name: linux-musl arm64 setup container + if: matrix.name == 'linux-musl' && matrix.arch == 'arm64' + run: | + docker run -d --name alpine \ + --platform linux/arm64 \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + alpine:latest \ + tail -f /dev/null + docker exec alpine sh -c "apk update && apk add --no-cache gcc make sqlite musl-dev linux-headers" + + - name: build sqlite-vector + run: ${{ matrix.name == 'linux-musl' && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make extension ${{ matrix.make && matrix.make || ''}} + + - name: create keychain for codesign + if: matrix.os == 'macos-15' + run: | + echo "${{ secrets.APPLE_CERTIFICATE }}" | base64 --decode > certificate.p12 + security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + security import certificate.p12 -k build.keychain -P "${{ secrets.CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + + - name: codesign dylib + if: matrix.os == 'macos-15' && matrix.name != 'apple-xcframework' + run: codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime dist/vector.dylib + + - name: codesign and notarize xcframework + if: matrix.name == 'apple-xcframework' + run: | + find dist/vector.xcframework -name "*.framework" -exec echo "Signing: {}" \; -exec codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime {} \; # Sign each individual framework FIRST + codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime dist/vector.xcframework # Then sign the xcframework wrapper + ditto -c -k --keepParent dist/vector.xcframework dist/vector.xcframework.zip + xcrun notarytool submit dist/vector.xcframework.zip --apple-id "${{ secrets.APPLE_ID }}" --password "${{ secrets.APPLE_PASSWORD }}" --team-id "${{ secrets.APPLE_TEAM_ID }}" --wait + rm dist/vector.xcframework.zip + + - name: cleanup keychain for codesign + if: matrix.os == 'macos-15' + run: | + rm certificate.p12 + security delete-keychain build.keychain + - name: android setup test environment if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' run: | @@ -77,7 +132,7 @@ jobs: export ${{ matrix.make }} $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/${{ matrix.arch }}-linux-android26-clang sqlite-amalgamation-*/shell.c sqlite-amalgamation-*/sqlite3.c -o sqlite3 -ldl # remove unused folders to save up space - rm -rf sqlite-amalgamation-*.zip sqlite-amalgamation-* openssl + rm -rf sqlite-amalgamation-*.zip sqlite-amalgamation-* echo "::endgroup::" echo "::group::prepare the test script" @@ -102,8 +157,8 @@ jobs: adb shell "sh /data/local/tmp/commands.sh" - name: test sqlite-vector - if: matrix.name == 'linux' || matrix.name == 'windows' || matrix.name == 'macos' - run: make test + if: contains(matrix.name, 'linux') || matrix.name == 'windows' || matrix.name == 'macos' + run: ${{ matrix.name == 'linux-musl' && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make test ${{ matrix.make && matrix.make || ''}} - uses: actions/upload-artifact@v4.6.2 if: always() @@ -113,7 +168,7 @@ jobs: if-no-files-found: error release: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 name: release needs: build if: github.ref == 'refs/heads/main' @@ -132,8 +187,7 @@ jobs: - name: release tag version from sqlite-vector.h id: tag run: | - FILE="src/sqlite-vector.h" - VERSION=$(grep -oP '#define SQLITE_VECTOR_VERSION\s+"\K[^"]+' "$FILE") + VERSION=$(make version) if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then LATEST=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.name') if [[ "$VERSION" != "$LATEST" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then @@ -151,9 +205,10 @@ jobs: for folder in "artifacts"/*; do if [ -d "$folder" ]; then name=$(basename "$folder") - zip -jq "${name}-${{ steps.tag.outputs.version }}.zip" "$folder"/* - tar -cJf "${name}-${{ steps.tag.outputs.version }}.tar.xz" -C "$folder" . - tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" . + if [[ "$name" != "vector-apple-xcframework" ]]; then + tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" . + fi + (cd "$folder" && zip -rq "../../${name}-${{ steps.tag.outputs.version }}.zip" .) fi done @@ -164,6 +219,5 @@ jobs: tag_name: ${{ steps.tag.outputs.version }} files: | vector-*-${{ steps.tag.outputs.version }}.zip - vector-*-${{ steps.tag.outputs.version }}.tar.xz vector-*-${{ steps.tag.outputs.version }}.tar.gz make_latest: true diff --git a/Makefile b/Makefile index 4eb4689..2cd1630 100644 --- a/Makefile +++ b/Makefile @@ -45,44 +45,43 @@ ifeq ($(PLATFORM),windows) LDFLAGS += -shared # Create .def file for Windows DEF_FILE := $(BUILD_DIR)/vector.def + STRIP = strip --strip-unneeded $@ else ifeq ($(PLATFORM),macos) TARGET := $(DIST_DIR)/vector.dylib LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib -undefined dynamic_lookup CFLAGS += -arch x86_64 -arch arm64 + STRIP = strip -x -S $@ else ifeq ($(PLATFORM),android) - # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH - ifeq ($(filter %,$(ARCH)),) + ifndef ARCH # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH $(error "Android ARCH must be set to ARCH=x86_64 or ARCH=arm64-v8a") endif - # Set ANDROID_NDK path to find android build tools - # e.g. on MacOS: export ANDROID_NDK=/Users/username/Library/Android/sdk/ndk/25.2.9519653 - ifeq ($(filter %,$(ANDROID_NDK)),) + ifndef ANDROID_NDK # Set ANDROID_NDK path to find android build tools; e.g. on MacOS: export ANDROID_NDK=/Users/username/Library/Android/sdk/ndk/25.2.9519653 $(error "Android NDK must be set") endif - BIN = $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(HOST)-x86_64/bin - PATH := $(BIN):$(PATH) - ifneq (,$(filter $(ARCH),arm64 arm64-v8a)) override ARCH := aarch64 endif - CC = $(BIN)/$(ARCH)-linux-android26-clang TARGET := $(DIST_DIR)/vector.so LDFLAGS += -lm -shared + STRIP = $(BIN)/llvm-strip --strip-unneeded $@ else ifeq ($(PLATFORM),ios) TARGET := $(DIST_DIR)/vector.dylib SDK := -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0 LDFLAGS += -dynamiclib $(SDK) CFLAGS += -arch arm64 $(SDK) -else ifeq ($(PLATFORM),isim) + STRIP = strip -x -S $@ +else ifeq ($(PLATFORM),ios-sim) TARGET := $(DIST_DIR)/vector.dylib SDK := -isysroot $(shell xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0 LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib $(SDK) CFLAGS += -arch x86_64 -arch arm64 $(SDK) + STRIP = strip -x -S $@ else # linux TARGET := $(DIST_DIR)/vector.so LDFLAGS += -shared + STRIP = strip --strip-unneeded $@ endif # Windows .def file generation @@ -107,6 +106,8 @@ ifeq ($(PLATFORM),windows) # Generate import library for Windows dlltool -D $@ -d $(DEF_FILE) -l $(DIST_DIR)/vector.lib endif + # Strip debug symbols + $(STRIP) # Object files $(BUILD_DIR)/%.o: %.c @@ -119,6 +120,67 @@ test: $(TARGET) clean: rm -rf $(BUILD_DIR)/* $(DIST_DIR)/* *.gcda *.gcno *.gcov *.sqlite +.NOTPARALLEL: %.dylib +%.dylib: + rm -rf $(BUILD_DIR) && $(MAKE) PLATFORM=$* + mv $(DIST_DIR)/vector.dylib $(DIST_DIR)/$@ + +define PLIST +\ +\ +\ +\ +CFBundleDevelopmentRegion\ +en\ +CFBundleExecutable\ +vector\ +CFBundleIdentifier\ +ai.sqlite.vector\ +CFBundleInfoDictionaryVersion\ +6.0\ +CFBundlePackageType\ +FMWK\ +CFBundleSignature\ +????\ +CFBundleVersion\ +$(shell make version)\ +CFBundleShortVersionString\ +$(shell make version)\ +MinimumOSVersion\ +11.0\ +\ + +endef + +define MODULEMAP +framework module vector {\ + umbrella header \"sqlite-vector.h\"\ + export *\ +} +endef + +LIB_NAMES = ios.dylib ios-sim.dylib macos.dylib +FMWK_NAMES = ios-arm64 ios-arm64_x86_64-simulator macos-arm64_x86_64 +$(DIST_DIR)/%.xcframework: $(LIB_NAMES) + @$(foreach i,1 2 3,\ + lib=$(word $(i),$(LIB_NAMES)); \ + fmwk=$(word $(i),$(FMWK_NAMES)); \ + mkdir -p $(DIST_DIR)/$$fmwk/vector.framework/Headers; \ + mkdir -p $(DIST_DIR)/$$fmwk/vector.framework/Modules; \ + cp src/sqlite-vector.h $(DIST_DIR)/$$fmwk/vector.framework/Headers; \ + printf "$(PLIST)" > $(DIST_DIR)/$$fmwk/vector.framework/Info.plist; \ + printf "$(MODULEMAP)" > $(DIST_DIR)/$$fmwk/vector.framework/Modules/module.modulemap; \ + mv $(DIST_DIR)/$$lib $(DIST_DIR)/$$fmwk/vector.framework/vector; \ + install_name_tool -id "@rpath/vector.framework/vector" $(DIST_DIR)/$$fmwk/vector.framework/vector; \ + ) + xcodebuild -create-xcframework $(foreach fmwk,$(FMWK_NAMES),-framework $(DIST_DIR)/$(fmwk)/vector.framework) -output $@ + rm -rf $(foreach fmwk,$(FMWK_NAMES),$(DIST_DIR)/$(fmwk)) + +xcframework: $(DIST_DIR)/vector.xcframework + +version: + @echo $(shell sed -n 's/^#define SQLITE_VECTOR_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' src/sqlite-vector.h) + # Help message help: @echo "SQLite Vector Extension Makefile" @@ -131,12 +193,13 @@ help: @echo " windows (default on Windows)" @echo " android (needs ARCH to be set to x86_64 or arm64-v8a and ANDROID_NDK to be set)" @echo " ios (only on macOS)" - @echo " isim (only on macOS)" + @echo " ios-sim (only on macOS)" @echo "" @echo "Targets:" - @echo " all - Build the extension (default)" - @echo " clean - Remove built files" - @echo " test - Test the extension" - @echo " help - Display this help message" + @echo " all - Build the extension (default)" + @echo " clean - Remove built files" + @echo " test - Test the extension" + @echo " help - Display this help message" + @echo " xcframework - Build the Apple XCFramework" -.PHONY: all clean test extension help +.PHONY: all clean test extension help version xcframework From 774ba0f474a3f7a465822c71d02bd3677e09cad6 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 29 Aug 2025 23:53:36 +0200 Subject: [PATCH 029/108] Fix strcasestr function definition for compatibility with Linux MUSL --- src/sqlite-vector.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index f37844c..e327b9c 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -21,7 +21,7 @@ #include #include -#ifdef _WIN32 +#if defined(_WIN32) || ((defined(__linux__) && !defined(__GLIBC__) && !defined(__ANDROID__))) char *strcasestr(const char *haystack, const char *needle) { if (!haystack || !needle) return NULL; if (!*needle) return (char *)haystack; From c9bc16def1c2d48899bf29b0527edcfe73227ac8 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 1 Sep 2025 19:16:56 +0200 Subject: [PATCH 030/108] Add support for WebAssembly compatibility --- src/distance-cpu.c | 20 +++++++++++++------- src/sqlite-vector.c | 6 +++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/distance-cpu.c b/src/distance-cpu.c index fae0ab9..73d722d 100644 --- a/src/distance-cpu.c +++ b/src/distance-cpu.c @@ -732,15 +732,21 @@ float int8_distance_l1_cpu (const void *v1, const void *v2, int n) { return true; } #else - #include - #include - bool cpu_supports_neon (void) { - #ifdef AT_HWCAP - return (getauxval(AT_HWCAP) & HWCAP_NEON) != 0; + #ifdef SQLITE_WASM_EXTRA_INIT + bool cpu_supports_neon (void) { + return false; + } #else - return false; + #include + #include + bool cpu_supports_neon (void) { + #ifdef AT_HWCAP + return (getauxval(AT_HWCAP) & HWCAP_NEON) != 0; + #else + return false; + #endif + } #endif - } #endif #endif diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index e327b9c..ca06925 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -21,7 +21,11 @@ #include #include -#if defined(_WIN32) || ((defined(__linux__) && !defined(__GLIBC__) && !defined(__ANDROID__))) +#if defined(_WIN32) || ((defined(__linux__) && !defined(__GLIBC__) && !defined(__ANDROID__))) || defined(SQLITE_WASM_EXTRA_INIT) +// Provide strcasestr function implementation for environments that lack it: +// - Windows (MinGW, MSVC, etc.) +// - Linux with non-glibc C libraries (musl, uclibc, etc.) +// - WebAssembly builds char *strcasestr(const char *haystack, const char *needle) { if (!haystack || !needle) return NULL; if (!*needle) return (char *)haystack; From d615b01d7e7d0a62861d94ab585e007a8127c4cb Mon Sep 17 00:00:00 2001 From: Daniele Briggi <=> Date: Tue, 2 Sep 2025 15:46:29 +0200 Subject: [PATCH 031/108] fix(workflow): python package triggered when main completes --- .github/workflows/main.yml | 2 +- .github/workflows/python-package.yml | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 612e2d3..3ca0a02 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: build, test and release sqlite-vector +name: Build, Test and Release on: push: workflow_dispatch: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 220be6f..d6062b6 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -7,11 +7,16 @@ on: description: "Version to use for the Python package (e.g. 0.9.9)" required: true type: string - release: - types: [released] + workflow_run: + workflows: ["Build, Test and Release"] + types: + - completed jobs: build-and-publish: + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') runs-on: ${{ matrix.os }} permissions: id-token: write # mandatory for Pypi trusted publishing @@ -65,8 +70,13 @@ jobs: - name: Get version id: get_version run: | - if [[ "${{ github.event_name }}" == "release" ]]; then - VERSION="${{ github.event.release.tag_name }}" + if [[ "${{ github.event_name }}" == "workflow_run" ]]; then + # Fetch latest published release tag from GitHub API + VERSION=$(curl -s "https://api.github.com/repos/${{ github.repository }}/releases/latest" | jq -r '.tag_name') + if [ "$VERSION" = "null" ] || [ -z "$VERSION" ]; then + echo "Error: Failed to get latest release version" + exit 1 + fi else VERSION="${{ github.event.inputs.version }}" fi From 513070d354d2fab003943e0bc8b90fdff1ee4942 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 8 Sep 2025 17:31:57 +0200 Subject: [PATCH 032/108] chore(release): bump sqlite-vector version to 0.9.22 and update release workflow for sqlite-wasm --- .github/workflows/main.yml | 22 ++++++++++++++++++++++ src/sqlite-vector.h | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ca0a02..00afb4a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -199,8 +199,30 @@ jobs: fi echo "❌ SQLITE_VECTOR_VERSION not found in sqlite-vector.h" exit 1 + + - uses: actions/checkout@v4.2.2 + if: steps.tag.outputs.version != '' + with: + repository: sqliteai/sqlite-wasm + path: sqlite-wasm + submodules: recursive + token: ${{ secrets.PAT }} + + - name: release sqlite-wasm + if: steps.tag.outputs.version != '' + run: | + cd sqlite-wasm + git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" + git config --global user.name "$GITHUB_ACTOR" + cd modules/sqlite-vector + git checkout ${{ github.sha }} + cd ../.. + git add modules/sqlite-vector + git commit -m "Bump sqlite-vector version to ${{ steps.tag.outputs.version }}" + git push origin main - name: zip artifacts + if: steps.tag.outputs.version != '' run: | for folder in "artifacts"/*; do if [ -d "$folder" ]; then diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 937955a..4f6ee37 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.21" +#define SQLITE_VECTOR_VERSION "0.9.22" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From afe0757d1cc7ff0f1cdef28f72e4a5483ce10261 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni <48024736+Gioee@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:03:37 +0200 Subject: [PATCH 033/108] Document WASM version download link in README Added information about the WASM version of SQLite. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 791f6da..bd3d8d4 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ Download the appropriate pre-built binary for your platform from the official [R - Android - iOS +### WASM Version + +You can download the WebAssembly (WASM) version of SQLite with the SQLite Vector extension enabled from: https://www.npmjs.com/package/@sqliteai/sqlite-wasm + ### Loading the Extension ```sql From f22c7fd0a2e11f248b9defc54d05aa036e6dd11b Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Tue, 9 Sep 2025 14:58:44 +0200 Subject: [PATCH 034/108] Fixed a possible issue during quantization --- src/sqlite-vector.c | 12 ++---------- src/sqlite-vector.h | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index ca06925..23a6496 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -1247,6 +1247,7 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta rc = SQLITE_ERROR; goto vector_rebuild_quantization_cleanup; } + if (val < min_val) min_val = val; if (val > max_val) max_val = val; if (val < 0.0) contains_negative = true; @@ -1425,7 +1426,7 @@ static int vector_quantize (sqlite3_context *context, const char *table_name, co rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto quantize_cleanup; - vector_options options = vector_options_create(); + vector_options options = t_ctx->options; // t_ctx guarantees to exist bool res = parse_keyvalue_string(context, arg_options, vector_keyvalue_callback, &options); if (res == false) return SQLITE_ERROR; @@ -2273,15 +2274,6 @@ static int vStreamScanBestIndex (sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo return SQLITE_OK; } -static int vStreamScanCursorOpen (sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor) { - int rc = vFullScanCursorOpen(pVtab, ppCursor); - if (rc != SQLITE_OK) return rc; - - vFullScanCursor *c = (vFullScanCursor *)*ppCursor; - c->is_streaming = true; - return SQLITE_OK; -} - static int vStreamScanCursorRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1size) { // duplicate input vector (to be later used by the Next callback) void *v = sqlite_memdup(v1, v1size); diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 4f6ee37..7ed4429 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.22" +#define SQLITE_VECTOR_VERSION "0.9.23" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 84deabbf1016a428ee7b7f6d7cb836aedbf4f4e9 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Sun, 14 Sep 2025 06:46:25 +0200 Subject: [PATCH 035/108] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd3d8d4..b54664e 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Or embed it directly into your application. Python developers can quickly get started using the ready-to-use `sqlite-vector` package available on PyPI: ```bash -pip install sqlite-vector +pip install sqliteai-vector ``` For usage details and examples, see the [Python package documentation](./packages/python/README.md). From 1016d58539bb4a03ec49f947aebcae79489ff6f0 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 17 Sep 2025 18:31:59 +0200 Subject: [PATCH 036/108] Add Swift Package support --- .gitignore | 3 +- Package.swift | 33 +++++++++++++++ README.md | 23 ++++++++++ packages/swift/extension/vector.swift | 19 +++++++++ packages/swift/plugin/vector.swift | 60 +++++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 Package.swift create mode 100644 packages/swift/extension/vector.swift create mode 100644 packages/swift/plugin/vector.swift diff --git a/.gitignore b/.gitignore index 06667c1..a45654f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ /dist *.sqlite *.a -.vscode \ No newline at end of file +.vscode +.build \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..ddea5a6 --- /dev/null +++ b/Package.swift @@ -0,0 +1,33 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "vector", + platforms: [.macOS(.v11), .iOS(.v11)], + products: [ + // Products can be used to vend plugins, making them visible to other packages. + .plugin( + name: "vectorPlugin", + targets: ["vectorPlugin"]), + .library( + name: "vector", + targets: ["vector"]) + ], + targets: [ + // Build tool plugin that invokes the Makefile + .plugin( + name: "vectorPlugin", + capability: .buildTool(), + path: "packages/swift/plugin" + ), + // vector library target + .target( + name: "vector", + dependencies: [], + path: "packages/swift/extension", + plugins: ["vectorPlugin"] + ), + ] +) \ No newline at end of file diff --git a/README.md b/README.md index b54664e..2aaf3ba 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,29 @@ Download the appropriate pre-built binary for your platform from the official [R You can download the WebAssembly (WASM) version of SQLite with the SQLite Vector extension enabled from: https://www.npmjs.com/package/@sqliteai/sqlite-wasm +### Swift Package + +You can [add this repository as a package dependency to your Swift project](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app#Add-a-package-dependency). After adding the package, you'll need to set up SQLite with extension loading by following steps 4 and 5 of [this guide](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/platforms/ios.md#4-set-up-sqlite-with-extension-loading). + +Here's an example of how to use the package: +```swift +import vector + +... + +var db: OpaquePointer? +sqlite3_open(":memory:", &db) +sqlite3_enable_load_extension(db, 1) +var errMsg: UnsafeMutablePointer? = nil +sqlite3_load_extension(db, vector.path, nil, &errMsg) +var stmt: OpaquePointer? +sqlite3_prepare_v2(db, "SELECT vector_version()", -1, &stmt, nil) +defer { sqlite3_finalize(stmt) } +sqlite3_step(stmt) +log("vector_version(): \(String(cString: sqlite3_column_text(stmt, 0)))") +sqlite3_close(db) +``` + ### Loading the Extension ```sql diff --git a/packages/swift/extension/vector.swift b/packages/swift/extension/vector.swift new file mode 100644 index 0000000..a98e548 --- /dev/null +++ b/packages/swift/extension/vector.swift @@ -0,0 +1,19 @@ +// vector.swift +// This file serves as a placeholder for the vector target. +// The actual SQLite extension is built using the Makefile through the build plugin. + +import Foundation + +/// Placeholder structure for vector +public struct vector { + /// Returns the path to the built vector dylib inside the XCFramework + public static var path: String { + #if os(macOS) + return "vector.xcframework/macos-arm64_x86_64/vector.framework/vector" + #elseif targetEnvironment(simulator) + return "vector.xcframework/ios-arm64_x86_64-simulator/vector.framework/vector" + #else + return "vector.xcframework/ios-arm64/vector.framework/vector" + #endif + } +} \ No newline at end of file diff --git a/packages/swift/plugin/vector.swift b/packages/swift/plugin/vector.swift new file mode 100644 index 0000000..a51c1b3 --- /dev/null +++ b/packages/swift/plugin/vector.swift @@ -0,0 +1,60 @@ +import PackagePlugin +import Foundation + +@main +struct vector: BuildToolPlugin { + /// Entry point for creating build commands for targets in Swift packages. + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + let packageDirectory = context.package.directoryURL + let outputDirectory = context.pluginWorkDirectoryURL + return createvectorBuildCommands(packageDirectory: packageDirectory, outputDirectory: outputDirectory) + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension vector: XcodeBuildToolPlugin { + // Entry point for creating build commands for targets in Xcode projects. + func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { + let outputDirectory = context.pluginWorkDirectoryURL + return createvectorBuildCommands(packageDirectory: nil, outputDirectory: outputDirectory) + } +} + +#endif + +/// Shared function to create vector build commands +func createvectorBuildCommands(packageDirectory: URL?, outputDirectory: URL) -> [Command] { + + // For Xcode projects, use current directory; for Swift packages, use provided packageDirectory + let workingDirectory = packageDirectory?.path ?? "$(pwd)" + let packageDirInfo = packageDirectory != nil ? "Package directory: \(packageDirectory!.path)" : "Working directory: $(pwd)" + + return [ + .prebuildCommand( + displayName: "Building vector XCFramework", + executable: URL(fileURLWithPath: "/bin/bash"), + arguments: [ + "-c", + """ + set -e + echo "Starting vector XCFramework prebuild..." + echo "\(packageDirInfo)" + + # Clean and create output directory + rm -rf "\(outputDirectory.path)" + mkdir -p "\(outputDirectory.path)" + + # Build directly from source directory with custom output paths + cd "\(workingDirectory)" && \ + echo "Building XCFramework with native network..." && \ + make xcframework NATIVE_NETWORK=ON DIST_DIR="\(outputDirectory.path)" BUILD_RELEASE="\(outputDirectory.path)/build/release" BUILD_TEST="\(outputDirectory.path)/build/test" && \ + rm -rf "\(outputDirectory.path)/build" && \ + echo "XCFramework build completed successfully!" + """ + ], + outputFilesDirectory: outputDirectory + ) + ] +} \ No newline at end of file From c7ac7cf76c613a8ce90f6bc787ff4beeb0f698a5 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 17 Sep 2025 18:38:18 +0200 Subject: [PATCH 037/108] fix(swift-package): build directory path --- packages/swift/plugin/vector.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/swift/plugin/vector.swift b/packages/swift/plugin/vector.swift index a51c1b3..667d56f 100644 --- a/packages/swift/plugin/vector.swift +++ b/packages/swift/plugin/vector.swift @@ -48,8 +48,8 @@ func createvectorBuildCommands(packageDirectory: URL?, outputDirectory: URL) -> # Build directly from source directory with custom output paths cd "\(workingDirectory)" && \ - echo "Building XCFramework with native network..." && \ - make xcframework NATIVE_NETWORK=ON DIST_DIR="\(outputDirectory.path)" BUILD_RELEASE="\(outputDirectory.path)/build/release" BUILD_TEST="\(outputDirectory.path)/build/test" && \ + echo "Building XCFramework..." && \ + make xcframework DIST_DIR="\(outputDirectory.path)" BUILD_DIR="\(outputDirectory.path)/build" && \ rm -rf "\(outputDirectory.path)/build" && \ echo "XCFramework build completed successfully!" """ From 2539b1f06e3ef385fd8f82dccd1d5875fb2a5a0a Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 17 Sep 2025 18:40:18 +0200 Subject: [PATCH 038/108] Bump version to 0.9.24 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 7ed4429..ebed36a 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.23" +#define SQLITE_VECTOR_VERSION "0.9.24" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 4790f8d0203b7df016057add73a6e356a66fb64d Mon Sep 17 00:00:00 2001 From: Daniele Briggi <=> Date: Tue, 16 Sep 2025 17:19:56 +0200 Subject: [PATCH 039/108] chore(python): upgrade flow to python -m build The solution python setup.py bdist_wheel --plat-name is deprecated. Modernize the build flow but keeping the platform specific tag for the wheel and specific classifier. chore(package): binaries moved to `sqlite_vector.binaries` (with underscore) chore(license): using the right one --- .github/workflows/python-package.yml | 5 +- packages/python/LICENSE.md | 1 + packages/python/MANIFEST.in | 2 +- packages/python/README.md | 2 +- packages/python/pyproject.toml | 23 ++-- packages/python/requirements-dev.txt | 4 +- packages/python/setup.py | 108 +++++++++--------- .../__init__.py | 0 packages/python/src/sqlite_vector/_version.py | 3 + 9 files changed, 81 insertions(+), 67 deletions(-) create mode 120000 packages/python/LICENSE.md rename packages/python/src/{sqlite-vector => sqlite_vector}/__init__.py (100%) create mode 100644 packages/python/src/sqlite_vector/_version.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d6062b6..e05c78b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -91,9 +91,10 @@ jobs: - name: Build wheel env: PACKAGE_VERSION: ${{ steps.get_version.outputs.version }} + PLAT_NAME: ${{ matrix.plat_name }} run: | cd packages/python - python setup.py bdist_wheel --plat-name "${{ matrix.plat_name }}" + python -m build --wheel - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 @@ -103,4 +104,4 @@ jobs: # Avoid workflow to fail if the version has already been published skip-existing: true # Upload to Test Pypi for testing - # repository-url: https://test.pypi.org/legacy/ + #repository-url: https://test.pypi.org/legacy/ diff --git a/packages/python/LICENSE.md b/packages/python/LICENSE.md new file mode 120000 index 0000000..f0608a6 --- /dev/null +++ b/packages/python/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/packages/python/MANIFEST.in b/packages/python/MANIFEST.in index 610b692..3921982 100644 --- a/packages/python/MANIFEST.in +++ b/packages/python/MANIFEST.in @@ -1,3 +1,3 @@ include README.md -include LICENSE +include LICENSE.md recursive-include src/sqlite-vector/binaries * diff --git a/packages/python/README.md b/packages/python/README.md index cf8910e..40de2a9 100644 --- a/packages/python/README.md +++ b/packages/python/README.md @@ -34,7 +34,7 @@ conn = sqlite3.connect("example.db") # Load the sqlite-vector extension # pip will install the correct binary package for your platform and architecture -ext_path = importlib.resources.files("sqlite-vector.binaries") / "vector" +ext_path = importlib.resources.files("sqlite_vector.binaries") / "vector" conn.enable_load_extension(True) conn.load_extension(str(ext_path)) diff --git a/packages/python/pyproject.toml b/packages/python/pyproject.toml index ddf057f..f514d71 100644 --- a/packages/python/pyproject.toml +++ b/packages/python/pyproject.toml @@ -1,25 +1,32 @@ [build-system] -requires = ["setuptools>=61.0", "wheel", "toml"] +requires = ["setuptools>=61.0", "build", "wheel"] build-backend = "setuptools.build_meta" [project] name = "sqliteai-vector" -dynamic = ["version"] +dynamic = ["version", "classifiers"] description = "Python prebuilt binaries for SQLite Vector extension for all supported platforms and architectures." authors = [ { name = "SQLite AI Team" } ] readme = "README.md" +license = "LicenseRef-Elastic-2.0-Modified-For-Open-Source-Use" +license-files = ["LICENSE.md"] requires-python = ">=3" -classifiers = [ - "Programming Language :: Python :: 3", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS :: MacOS X" -] [project.urls] Homepage = "https://sqlite.ai" Documentation = "https://github.com/sqliteai/sqlite-vector/blob/main/API.md" Repository = "https://github.com/sqliteai/sqlite-vector" Issues = "https://github.com/sqliteai/sqlite-vector/issues" + +[tool.setuptools] +packages = {find = {where = ["src"]}} +include-package-data = true + +[tool.setuptools.dynamic] +version = {attr = "sqlite_vector._version.__version__"} + +[tool.bdist_wheel] +# Force platform-specific wheels +universal = false diff --git a/packages/python/requirements-dev.txt b/packages/python/requirements-dev.txt index 6760087..055c918 100644 --- a/packages/python/requirements-dev.txt +++ b/packages/python/requirements-dev.txt @@ -1,3 +1,3 @@ requests -toml -wheel \ No newline at end of file +wheel +build diff --git a/packages/python/setup.py b/packages/python/setup.py index baae9de..1759174 100644 --- a/packages/python/setup.py +++ b/packages/python/setup.py @@ -1,54 +1,56 @@ -import setuptools -import toml import os -import sys - -usage = """ -Usage: python setup.py bdist_wheel --plat-name -The PACKAGE_VERSION environment variable must be set to the desired version. - -Example: - PACKAGE_VERSION=0.5.9 python setup.py bdist_wheel --plat-name linux_x86_64 -""" - -with open("pyproject.toml", "r") as f: - pyproject = toml.load(f) - -project = pyproject["project"] - -# Get version from environment or default -version = os.environ.get("PACKAGE_VERSION", "") -if not version: - print("PACKAGE_VERSION environment variable is not set.") - print(usage) - sys.exit(1) - -# Get Python platform name from --plat-name argument -plat_name = None -for i, arg in enumerate(sys.argv): - if arg == "--plat-name" and i + 1 < len(sys.argv): - plat_name = sys.argv[i + 1] - break - -if not plat_name: - print("Error: --plat-name argument is required") - print(usage) - sys.exit(1) - -with open("README.md", "r", encoding="utf-8") as f: - long_description = f.read() - -setuptools.setup( - name=project["name"], - version=version, - description=project.get("description", ""), - author=project["authors"][0]["name"], - long_description=long_description, - long_description_content_type="text/markdown", - url=project["urls"]["Homepage"], - packages=setuptools.find_packages(where="src"), - package_dir={"": "src"}, - include_package_data=True, - python_requires=project.get("requires-python", ">=3"), - classifiers=project.get("classifiers", []), -) +from setuptools import setup +from setuptools.command.bdist_wheel import bdist_wheel + + +class PlatformSpecificWheel(bdist_wheel): + """Custom bdist_wheel to force platform-specific wheel.""" + + def finalize_options(self): + bdist_wheel.finalize_options(self) + # Force platform-specific wheel + self.root_is_pure = False + + # Set platform name from environment if provided + plat_name = os.environ.get("PLAT_NAME") + if plat_name: + self.plat_name = plat_name + + def get_tag(self): + # Force platform-specific tags with broader compatibility + python_tag, abi_tag, platform_tag = bdist_wheel.get_tag(self) + + # Override platform tag if specified + plat_name = os.environ.get("PLAT_NAME") + if plat_name: + platform_tag = plat_name + + # Use py3 for broader Python compatibility since we have pre-built binaries + python_tag = "py3" + abi_tag = "none" + + return python_tag, abi_tag, platform_tag + + +def get_platform_classifiers(): + """Get platform-specific classifiers based on PLAT_NAME environment variable.""" + classifier_map = { + "manylinux2014_x86_64": ["Operating System :: POSIX :: Linux"], + "manylinux2014_aarch64": ["Operating System :: POSIX :: Linux"], + "win_amd64": ["Operating System :: Microsoft :: Windows"], + "macosx_10_9_x86_64": ["Operating System :: MacOS"], + "macosx_11_0_arm64": ["Operating System :: MacOS"], + } + + plat_name = os.environ.get("PLAT_NAME") + if plat_name and plat_name in classifier_map: + return ["Programming Language :: Python :: 3", classifier_map[plat_name][0]] + + raise ValueError(f"Unsupported or missing PLAT_NAME: {plat_name}") + + +if __name__ == "__main__": + setup( + cmdclass={"bdist_wheel": PlatformSpecificWheel}, + classifiers=get_platform_classifiers(), + ) diff --git a/packages/python/src/sqlite-vector/__init__.py b/packages/python/src/sqlite_vector/__init__.py similarity index 100% rename from packages/python/src/sqlite-vector/__init__.py rename to packages/python/src/sqlite_vector/__init__.py diff --git a/packages/python/src/sqlite_vector/_version.py b/packages/python/src/sqlite_vector/_version.py new file mode 100644 index 0000000..99f716b --- /dev/null +++ b/packages/python/src/sqlite_vector/_version.py @@ -0,0 +1,3 @@ +import os + +__version__ = os.environ.get("PACKAGE_VERSION", "0.0.0") \ No newline at end of file From 717cc913bb8b520bc2934e288ac9b6ac3c70b4b7 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Fri, 26 Sep 2025 12:57:44 -0400 Subject: [PATCH 040/108] Update feature comparison in README.md Just a suggestion on how to make the presentation better. --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2aaf3ba..7c2d68e 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,15 @@ ## Why Use SQLite-Vector? -| Feature | SQLite-Vector | Traditional Solutions | -| -------------------------- | ------------- | ------------------------------------------ | -| Works with ordinary tables | ✅ | ❌ (usually require special virtual tables) | -| Requires preindexing | ❌ | ✅ (can take hours for large datasets) | -| Requires external server | ❌ | ✅ (often needs Redis/FAISS/Weaviate/etc.) | -| Memory-efficient | ✅ | ❌ | -| Easy to use SQL | ✅ | ❌ (often complex JOINs, subqueries) | -| Offline/Edge ready | ✅ | ❌ | -| Cross-platform | ✅ | ❌ | +| Feature | SQLite-Vector | Traditional Solutions | +| ---------------------------- | ------------- | ------------------------------------------ | +| Works with ordinary tables | ✅ | ❌ (usually require special virtual tables) | +| Doesn't need preindexing | ✅ | ❌ (can take hours for large datasets) | +| Doesn't need external server | ✅ | ❌ (often needs Redis/FAISS/Weaviate/etc.) | +| Memory-efficient | ✅ | ❌ | +| Easy to use SQL | ✅ | ❌ (often complex JOINs, subqueries) | +| Offline/Edge ready | ✅ | ❌ | +| Cross-platform | ✅ | ❌ | Unlike other vector databases or extensions that require complex setup, SQLite-Vector **just works** with your existing database schema and tools. From 422dbacbac59b1ba6f011b8c657c65c262c48cea Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 29 Sep 2025 14:25:33 +0200 Subject: [PATCH 041/108] fix(release): notarize apple dylib extensions --- .github/workflows/main.yml | 8 ++++++-- src/sqlite-vector.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 00afb4a..993c2f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -97,9 +97,13 @@ jobs: security import certificate.p12 -k build.keychain -P "${{ secrets.CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain - - name: codesign dylib + - name: codesign and notarize dylib if: matrix.os == 'macos-15' && matrix.name != 'apple-xcframework' - run: codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime dist/vector.dylib + run: | + codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime dist/vector.dylib + ditto -c -k dist/vector.dylib dist/vector.zip + xcrun notarytool submit dist/vector.zip --apple-id "${{ secrets.APPLE_ID }}" --password "${{ secrets.APPLE_PASSWORD }}" --team-id "${{ secrets.APPLE_TEAM_ID }}" --wait + rm dist/vector.zip - name: codesign and notarize xcframework if: matrix.name == 'apple-xcframework' diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index ebed36a..77476dc 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.24" +#define SQLITE_VECTOR_VERSION "0.9.25" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From ed21d055bcef97abb61afe37a2fb2daa9e3632c7 Mon Sep 17 00:00:00 2001 From: Daniele Briggi <=> Date: Mon, 29 Sep 2025 15:49:51 +0200 Subject: [PATCH 042/108] fix(package): binaries destination folder --- .github/workflows/python-package.yml | 8 +++++++- packages/python/MANIFEST.in | 2 +- packages/python/README.md | 6 +++--- packages/python/download_artifacts.py | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e05c78b..5cbe71a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -7,6 +7,12 @@ on: description: "Version to use for the Python package (e.g. 0.9.9)" required: true type: string + test-pypi: + description: "Publish to Test PyPI" + required: false + type: boolean + default: false + workflow_run: workflows: ["Build, Test and Release"] types: @@ -104,4 +110,4 @@ jobs: # Avoid workflow to fail if the version has already been published skip-existing: true # Upload to Test Pypi for testing - #repository-url: https://test.pypi.org/legacy/ + repository-url: ${{ github.event.inputs.test-pypi == 'true' && 'https://test.pypi.org/legacy/' || '' }} diff --git a/packages/python/MANIFEST.in b/packages/python/MANIFEST.in index 3921982..eed0b21 100644 --- a/packages/python/MANIFEST.in +++ b/packages/python/MANIFEST.in @@ -1,3 +1,3 @@ include README.md include LICENSE.md -recursive-include src/sqlite-vector/binaries * +recursive-include src/sqlite_vector/binaries * diff --git a/packages/python/README.md b/packages/python/README.md index 40de2a9..a2a76ab 100644 --- a/packages/python/README.md +++ b/packages/python/README.md @@ -16,9 +16,9 @@ For detailed information on all available functions, their parameters, and examp | Platform | Arch | Subpackage name | Binary name | | ------------- | ------------ | ------------------------ | ------------ | -| Linux (CPU) | x86_64/arm64 | sqlite-vector.binaries | vector.so | -| Windows (CPU) | x86_64 | sqlite-vector.binaries | vector.dll | -| macOS (CPU) | x86_64/arm64 | sqlite-vector.binaries | vector.dylib | +| Linux (CPU) | x86_64/arm64 | sqlite_vector.binaries | vector.so | +| Windows (CPU) | x86_64 | sqlite_vector.binaries | vector.dll | +| macOS (CPU) | x86_64/arm64 | sqlite_vector.binaries | vector.dylib | ## Usage diff --git a/packages/python/download_artifacts.py b/packages/python/download_artifacts.py index 95aaf4b..1474ab2 100644 --- a/packages/python/download_artifacts.py +++ b/packages/python/download_artifacts.py @@ -31,7 +31,7 @@ "macosx_11_0_arm64": "vector.dylib", } -BINARIES_DIR = Path(__file__).parent / "src/sqlite-vector/binaries" +BINARIES_DIR = Path(__file__).parent / "src/sqlite_vector/binaries" def download_and_extract(artifact_name, bin_name, version): From 0e94af245f051635badd4fec16cd56fb4d05f724 Mon Sep 17 00:00:00 2001 From: Daniele Briggi <=> Date: Mon, 29 Sep 2025 16:34:52 +0200 Subject: [PATCH 043/108] chore(version): bump to 0.9.26 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 77476dc..299e26a 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.25" +#define SQLITE_VECTOR_VERSION "0.9.26" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 901a3d6bba5930d8d36401259ff635cabe98a09e Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 29 Sep 2025 17:58:36 +0200 Subject: [PATCH 044/108] feat(android): add support for building Android AAR package --- .github/workflows/main.yml | 24 ++++++++-- .gitignore | 30 +++++++++--- Makefile | 11 ++++- packages/android/build.gradle | 48 +++++++++++++++++++ packages/android/gradle.properties | 2 + packages/android/src/main/AndroidManifest.xml | 4 ++ src/sqlite-vector.h | 2 +- 7 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 packages/android/build.gradle create mode 100644 packages/android/gradle.properties create mode 100644 packages/android/src/main/AndroidManifest.xml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 993c2f2..d80c6c8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,6 +52,9 @@ jobs: - os: macos-15 name: apple-xcframework make: xcframework + - os: ubuntu-22.04 + name: android-aar + make: aar defaults: run: @@ -231,19 +234,30 @@ jobs: for folder in "artifacts"/*; do if [ -d "$folder" ]; then name=$(basename "$folder") - if [[ "$name" != "vector-apple-xcframework" ]]; then + if [[ "$name" != "vector-apple-xcframework" && "$name" != "vector-android-aar" ]]; then tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" . fi - (cd "$folder" && zip -rq "../../${name}-${{ steps.tag.outputs.version }}.zip" .) + if [[ "$name" != "vector-android-aar" ]]; then + (cd "$folder" && zip -rq "../../${name}-${{ steps.tag.outputs.version }}.zip" .) + else + cp "$folder"/*.aar "${name}-${{ steps.tag.outputs.version }}.aar" + fi fi done + - name: release android aar to maven central + if: false # maven central namespace needs to be verified first steps.tag.outputs.version != '' + run: cd packages/android && gradle publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + - uses: softprops/action-gh-release@v2.2.1 if: steps.tag.outputs.version != '' with: generate_release_notes: true tag_name: ${{ steps.tag.outputs.version }} - files: | - vector-*-${{ steps.tag.outputs.version }}.zip - vector-*-${{ steps.tag.outputs.version }}.tar.gz + files: vector-*-${{ steps.tag.outputs.version }}.* make_latest: true diff --git a/.gitignore b/.gitignore index a45654f..243743c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,29 @@ -.DS_Store +# Build artifacts +build/ +/dist +.build +*.a +*.sqlite + +# iOS/macOS *.xcworkspacedata *.xcuserstate *.xcbkptlist *.plist -/build -/dist -*.sqlite -*.a + +# Android +.gradle/ +*.aar +local.properties +jniLibs/ +*.apk +*.ap_ +*.dex + +# IDE .vscode -.build \ No newline at end of file +.idea/ +*.iml + +# System +.DS_Store \ No newline at end of file diff --git a/Makefile b/Makefile index 2cd1630..ea2aa14 100644 --- a/Makefile +++ b/Makefile @@ -178,6 +178,14 @@ $(DIST_DIR)/%.xcframework: $(LIB_NAMES) xcframework: $(DIST_DIR)/vector.xcframework +aar: + $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=arm64-v8a + mv $(DIST_DIR)/vector.so packages/android/src/main/jniLibs/arm64-v8a/ + $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=x86_64 + mv $(DIST_DIR)/vector.so packages/android/src/main/jniLibs/x86_64/ + cd packages/android && gradle assembleRelease + mv packages/android/build/outputs/aar/*.aar $(DIST_DIR)/vector.aar + version: @echo $(shell sed -n 's/^#define SQLITE_VECTOR_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' src/sqlite-vector.h) @@ -201,5 +209,6 @@ help: @echo " test - Test the extension" @echo " help - Display this help message" @echo " xcframework - Build the Apple XCFramework" + @echo " aar - Build the Android AAR package" -.PHONY: all clean test extension help version xcframework +.PHONY: all clean test extension help version xcframework aar diff --git a/packages/android/build.gradle b/packages/android/build.gradle new file mode 100644 index 0000000..0152119 --- /dev/null +++ b/packages/android/build.gradle @@ -0,0 +1,48 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.5.2' + } +} + +apply plugin: 'com.android.library' + +android { + namespace 'ai.sqlite.vector' + compileSdk 34 + + defaultConfig { + minSdk 26 + targetSdk 34 + } + + buildTypes { + release { + minifyEnabled false + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main { + jniLibs.srcDirs = ['src/main/jniLibs'] + } + } +} + +repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } +} + +dependencies { + api 'com.github.requery:sqlite-android:3.49.0' +} \ No newline at end of file diff --git a/packages/android/gradle.properties b/packages/android/gradle.properties new file mode 100644 index 0000000..ca684b2 --- /dev/null +++ b/packages/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +android.useAndroidX=true \ No newline at end of file diff --git a/packages/android/src/main/AndroidManifest.xml b/packages/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/packages/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 299e26a..43bfccd 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.26" +#define SQLITE_VECTOR_VERSION "0.9.27" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 49689cdf943afd4e93245c4259628a7bd98fc6f3 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 29 Sep 2025 18:05:52 +0200 Subject: [PATCH 045/108] fix(AAR build): missing mkdir build folder --- .github/workflows/main.yml | 2 +- Makefile | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d80c6c8..870b159 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ${{ matrix.os }} container: ${{ matrix.container && matrix.container || '' }} - name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && ' + test' || ''}} + name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && matrix.name != 'android-aar' && ' + test' || ''}} timeout-minutes: 20 strategy: fail-fast: false diff --git a/Makefile b/Makefile index ea2aa14..7b47344 100644 --- a/Makefile +++ b/Makefile @@ -178,11 +178,14 @@ $(DIST_DIR)/%.xcframework: $(LIB_NAMES) xcframework: $(DIST_DIR)/vector.xcframework +AAR_ARM = packages/android/src/main/jniLibs/arm64-v8a/ +AAR_X86 = packages/android/src/main/jniLibs/x86_64/ aar: + mkdir -p $(AAR_ARM) $(AAR_X86) $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=arm64-v8a - mv $(DIST_DIR)/vector.so packages/android/src/main/jniLibs/arm64-v8a/ + mv $(DIST_DIR)/vector.so $(AAR_ARM) $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=x86_64 - mv $(DIST_DIR)/vector.so packages/android/src/main/jniLibs/x86_64/ + mv $(DIST_DIR)/vector.so $(AAR_X86) cd packages/android && gradle assembleRelease mv packages/android/build/outputs/aar/*.aar $(DIST_DIR)/vector.aar From bc6cf44ad97a50d8c06e5f93a92b705e5b761d17 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 29 Sep 2025 18:11:25 +0200 Subject: [PATCH 046/108] fix(AAR build): missing java setup --- .github/workflows/main.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 870b159..b7d535a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,13 @@ jobs: - uses: actions/checkout@v4.2.2 + - name: android setup java + if: matrix.name == 'android-aar' + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + - name: windows install dependencies if: matrix.name == 'windows' run: choco install sqlite -y From fe007082f25efa889a50c930bc6891a07e20f139 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 16:28:41 +0200 Subject: [PATCH 047/108] feat(android): add JitPack configuration for building AAR package --- jitpack.yml | 6 ++++++ packages/android/build.gradle | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..0ec7c76 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,6 @@ +jdk: + - openjdk17 +install: + - make aar ANDROID_NDK=$ANDROID_HOME/ndk-bundle + - cd packages/android + - ./gradlew publishToMavenLocal -PVERSION=$JITPACK_VERSION \ No newline at end of file diff --git a/packages/android/build.gradle b/packages/android/build.gradle index 0152119..640eaed 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -9,6 +9,7 @@ buildscript { } apply plugin: 'com.android.library' +apply plugin: 'maven-publish' android { namespace 'ai.sqlite.vector' @@ -44,5 +45,18 @@ repositories { } dependencies { - api 'com.github.requery:sqlite-android:3.49.0' +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + groupId = 'ai.sqlite' + artifactId = 'vector' + version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() + + artifact bundleReleaseAar + } + } + } } \ No newline at end of file From ed6ea31f876d1b621e1fb7c10bd26e66e79834bc Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 17:09:01 +0200 Subject: [PATCH 048/108] fix(android AAR package): missing gradle building scripts --- Makefile | 4 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45457 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + packages/android/gradlew | 248 ++++++++++++++++++ packages/android/gradlew.bat | 93 +++++++ 5 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 packages/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 packages/android/gradlew create mode 100644 packages/android/gradlew.bat diff --git a/Makefile b/Makefile index 7b47344..4d4a07d 100644 --- a/Makefile +++ b/Makefile @@ -186,8 +186,8 @@ aar: mv $(DIST_DIR)/vector.so $(AAR_ARM) $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=x86_64 mv $(DIST_DIR)/vector.so $(AAR_X86) - cd packages/android && gradle assembleRelease - mv packages/android/build/outputs/aar/*.aar $(DIST_DIR)/vector.aar + cd packages/android && ./gradlew clean assembleRelease + cp packages/android/build/outputs/aar/android-release.aar $(DIST_DIR)/vector.aar version: @echo $(shell sed -n 's/^#define SQLITE_VECTOR_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' src/sqlite-vector.h) diff --git a/packages/android/gradle/wrapper/gradle-wrapper.jar b/packages/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8bdaf60c75ab801e22807dde59e12a8735a34077 GIT binary patch literal 45457 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 c82=K%QXLKFx%@O{wJjEi6Y56o#$)Bpeg literal 0 HcmV?d00001 diff --git a/packages/android/gradle/wrapper/gradle-wrapper.properties b/packages/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df97d72 --- /dev/null +++ b/packages/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/android/gradlew b/packages/android/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/packages/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/packages/android/gradlew.bat b/packages/android/gradlew.bat new file mode 100644 index 0000000..e509b2d --- /dev/null +++ b/packages/android/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From ef778bf6261b1a9fec5cf63293675b5009e23544 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 17:26:51 +0200 Subject: [PATCH 049/108] fix(android): wrong jitpack env version --- jitpack.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jitpack.yml b/jitpack.yml index 0ec7c76..36d9147 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,6 +1,6 @@ jdk: - openjdk17 install: + - echo "JITPACK_VERSION is $JITPACK_VERSION" - make aar ANDROID_NDK=$ANDROID_HOME/ndk-bundle - - cd packages/android - - ./gradlew publishToMavenLocal -PVERSION=$JITPACK_VERSION \ No newline at end of file + - cd packages/android && ./gradlew publishToMavenLocal -PVERSION=$JITPACK_VERSION \ No newline at end of file From 7545370c360a5cb6db0da7394a527aba723a55f5 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 17:48:15 +0200 Subject: [PATCH 050/108] fix(android): update version retrieval method in jitpack build script --- jitpack.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jitpack.yml b/jitpack.yml index 36d9147..fed6266 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,6 +1,5 @@ jdk: - openjdk17 install: - - echo "JITPACK_VERSION is $JITPACK_VERSION" - make aar ANDROID_NDK=$ANDROID_HOME/ndk-bundle - - cd packages/android && ./gradlew publishToMavenLocal -PVERSION=$JITPACK_VERSION \ No newline at end of file + - cd packages/android && ./gradlew publishToMavenLocal -PVERSION=$(make -C ../.. version) \ No newline at end of file From 4014ac9206aed1328aa6c3cbcf62ed3969c6d9b9 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 18:00:23 +0200 Subject: [PATCH 051/108] fix(android): hardcode version 0.9.27 in jitpack gradle publish command --- jitpack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index fed6266..95fedc9 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,4 +2,4 @@ jdk: - openjdk17 install: - make aar ANDROID_NDK=$ANDROID_HOME/ndk-bundle - - cd packages/android && ./gradlew publishToMavenLocal -PVERSION=$(make -C ../.. version) \ No newline at end of file + - cd packages/android && ./gradlew publishToMavenLocal -PVERSION="0.9.27" \ No newline at end of file From f34edd73525580bb86dfe6bf206e0d2541d9ca02 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 18:17:44 +0200 Subject: [PATCH 052/108] fix(android): update version retrieval method in jitpack.yml --- jitpack.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index 95fedc9..4e8afbc 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,4 +2,5 @@ jdk: - openjdk17 install: - make aar ANDROID_NDK=$ANDROID_HOME/ndk-bundle - - cd packages/android && ./gradlew publishToMavenLocal -PVERSION="0.9.27" \ No newline at end of file + - export VERSION=$(make version 2>/dev/null | tail -1) + - cd packages/android && ./gradlew publishToMavenLocal -PVERSION="$VERSION" \ No newline at end of file From 352521fd86f362916c12db7744e4259c711d5c91 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 18:33:22 +0200 Subject: [PATCH 053/108] Bump version to 0.9.28 for the new Android package release --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 43bfccd..56d62f9 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.27" +#define SQLITE_VECTOR_VERSION "0.9.28" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 0c70846de5c898afa0b3435b1cdc224e0eb72a4f Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 19:06:54 +0200 Subject: [PATCH 054/108] feat(android): publish package to Maven Central --- .github/workflows/main.yml | 8 +++--- packages/android/build.gradle | 53 +++++++++++++++++++++++++++++++++++ src/sqlite-vector.h | 2 +- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b7d535a..ea04e96 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -253,11 +253,11 @@ jobs: done - name: release android aar to maven central - if: false # maven central namespace needs to be verified first steps.tag.outputs.version != '' - run: cd packages/android && gradle publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} + if: steps.tag.outputs.version != '' + run: cd packages/android && ./gradlew publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} diff --git a/packages/android/build.gradle b/packages/android/build.gradle index 640eaed..fe4ca00 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -10,6 +10,7 @@ buildscript { apply plugin: 'com.android.library' apply plugin: 'maven-publish' +apply plugin: 'signing' android { namespace 'ai.sqlite.vector' @@ -56,7 +57,59 @@ afterEvaluate { version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() artifact bundleReleaseAar + + pom { + name = 'SQLite Vector' + description = 'A cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. Works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default.' + url = 'https://github.com/sqliteai/sqlite-vector' + + licenses { + license { + name = 'Elastic License 2.0' + url = 'https://www.elastic.co/licensing/elastic-license' + } + } + + developers { + developer { + id = 'sqliteai' + name = 'SQLite Cloud, Inc.' + email = 'info@sqlitecloud.io' + organization = 'SQLite Cloud, Inc.' + organizationUrl = 'https://sqlite.ai' + } + } + + scm { + connection = 'scm:git:git://github.com/sqliteai/sqlite-vector.git' + developerConnection = 'scm:git:ssh://github.com/sqliteai/sqlite-vector.git' + url = 'https://github.com/sqliteai/sqlite-vector' + } + } } } + + repositories { + maven { + name = "OSSRH" + url = project.hasProperty('VERSION') ? + "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" : + "https://s01.oss.sonatype.org/content/repositories/snapshots/" + credentials { + username = project.findProperty("MAVEN_CENTRAL_USERNAME") ?: "" + password = project.findProperty("MAVEN_CENTRAL_TOKEN") ?: "" + } + } + } + } +} + +signing { + def signingKey = project.findProperty("SIGNING_KEY") + def signingPassword = project.findProperty("SIGNING_PASSWORD") + + if (signingKey && signingPassword) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign publishing.publications.release } } \ No newline at end of file diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 56d62f9..18fb487 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.28" +#define SQLITE_VECTOR_VERSION "0.9.29" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 42b2813dd60ec5195af63ca22687704884c66c53 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 19:14:06 +0200 Subject: [PATCH 055/108] fix(android/maven-central): add Java 17 setup for Android release process --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ea04e96..92edb0f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -252,6 +252,11 @@ jobs: fi done + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + - name: release android aar to maven central if: steps.tag.outputs.version != '' run: cd packages/android && ./gradlew publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} From 476ddd9f8c2b7a4b5be8950541f10099b44dbaad Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 19:22:11 +0200 Subject: [PATCH 056/108] fix(android): update Gradle publish command to use Maven Central and its endpoints --- .github/workflows/main.yml | 2 +- packages/android/build.gradle | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92edb0f..cd21c68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -259,7 +259,7 @@ jobs: - name: release android aar to maven central if: steps.tag.outputs.version != '' - run: cd packages/android && ./gradlew publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} + run: cd packages/android && ./gradlew publishReleasePublicationToCentralRepository -PVERSION=${{ steps.tag.outputs.version }} env: MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} diff --git a/packages/android/build.gradle b/packages/android/build.gradle index fe4ca00..3f57b2a 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -91,10 +91,8 @@ afterEvaluate { repositories { maven { - name = "OSSRH" - url = project.hasProperty('VERSION') ? - "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" : - "https://s01.oss.sonatype.org/content/repositories/snapshots/" + name = "Central" + url = "https://central.sonatype.com/api/v1/publisher/upload" credentials { username = project.findProperty("MAVEN_CENTRAL_USERNAME") ?: "" password = project.findProperty("MAVEN_CENTRAL_TOKEN") ?: "" From fc3339a29e502d2bc0c052b46046696e2336f7b0 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 19:27:09 +0200 Subject: [PATCH 057/108] fix(android): update release command and repository URL for Maven Central --- .github/workflows/main.yml | 6 +++++- packages/android/build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cd21c68..124eb79 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -259,7 +259,11 @@ jobs: - name: release android aar to maven central if: steps.tag.outputs.version != '' - run: cd packages/android && ./gradlew publishReleasePublicationToCentralRepository -PVERSION=${{ steps.tag.outputs.version }} + run: | + echo "Maven Central Username length: ${#MAVEN_CENTRAL_USERNAME}" + echo "Maven Central Token length: ${#MAVEN_CENTRAL_TOKEN}" + echo "Signing key length: ${#SIGNING_KEY}" + cd packages/android && ./gradlew publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} env: MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} diff --git a/packages/android/build.gradle b/packages/android/build.gradle index 3f57b2a..cecdcae 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -91,8 +91,8 @@ afterEvaluate { repositories { maven { - name = "Central" - url = "https://central.sonatype.com/api/v1/publisher/upload" + name = "OSSRH" + url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" credentials { username = project.findProperty("MAVEN_CENTRAL_USERNAME") ?: "" password = project.findProperty("MAVEN_CENTRAL_TOKEN") ?: "" From c6ae1785d6f9fcea5bc17d3226f77bdb054f62ae Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 19:43:01 +0200 Subject: [PATCH 058/108] fix(android): update Gradle publish command to use publishToCentralPortal for Maven Central --- .github/workflows/main.yml | 2 +- packages/android/build.gradle | 80 +++++++++++++++++------------------ 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 124eb79..ae4fdfa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -263,7 +263,7 @@ jobs: echo "Maven Central Username length: ${#MAVEN_CENTRAL_USERNAME}" echo "Maven Central Token length: ${#MAVEN_CENTRAL_TOKEN}" echo "Signing key length: ${#SIGNING_KEY}" - cd packages/android && ./gradlew publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} + cd packages/android && ./gradlew publishToCentralPortal -PVERSION=${{ steps.tag.outputs.version }} env: MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} diff --git a/packages/android/build.gradle b/packages/android/build.gradle index cecdcae..348a0a9 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -5,12 +5,14 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.5.2' + classpath 'com.gradleup.central:central-publishing:0.4.0' } } apply plugin: 'com.android.library' apply plugin: 'maven-publish' apply plugin: 'signing' +apply plugin: 'com.gradleup.central.publish' android { namespace 'ai.sqlite.vector' @@ -48,6 +50,44 @@ repositories { dependencies { } +centralPublishing { + username = project.findProperty("MAVEN_CENTRAL_USERNAME") ?: "" + password = project.findProperty("MAVEN_CENTRAL_TOKEN") ?: "" + + pom { + groupId = 'ai.sqlite' + artifactId = 'vector' + version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() + + name = 'SQLite Vector' + description = 'A cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. Works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default.' + url = 'https://github.com/sqliteai/sqlite-vector' + + licenses { + license { + name = 'Elastic License 2.0' + url = 'https://www.elastic.co/licensing/elastic-license' + } + } + + developers { + developer { + id = 'sqliteai' + name = 'SQLite Cloud, Inc.' + email = 'info@sqlitecloud.io' + organization = 'SQLite Cloud, Inc.' + organizationUrl = 'https://sqlite.ai' + } + } + + scm { + connection = 'scm:git:git://github.com/sqliteai/sqlite-vector.git' + developerConnection = 'scm:git:ssh://github.com/sqliteai/sqlite-vector.git' + url = 'https://github.com/sqliteai/sqlite-vector' + } + } +} + afterEvaluate { publishing { publications { @@ -57,46 +97,6 @@ afterEvaluate { version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() artifact bundleReleaseAar - - pom { - name = 'SQLite Vector' - description = 'A cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. Works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default.' - url = 'https://github.com/sqliteai/sqlite-vector' - - licenses { - license { - name = 'Elastic License 2.0' - url = 'https://www.elastic.co/licensing/elastic-license' - } - } - - developers { - developer { - id = 'sqliteai' - name = 'SQLite Cloud, Inc.' - email = 'info@sqlitecloud.io' - organization = 'SQLite Cloud, Inc.' - organizationUrl = 'https://sqlite.ai' - } - } - - scm { - connection = 'scm:git:git://github.com/sqliteai/sqlite-vector.git' - developerConnection = 'scm:git:ssh://github.com/sqliteai/sqlite-vector.git' - url = 'https://github.com/sqliteai/sqlite-vector' - } - } - } - } - - repositories { - maven { - name = "OSSRH" - url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - credentials { - username = project.findProperty("MAVEN_CENTRAL_USERNAME") ?: "" - password = project.findProperty("MAVEN_CENTRAL_TOKEN") ?: "" - } } } } From 84ca515f2cbf1aa47b167fcf63ebd325b7654dc4 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 22:19:01 +0200 Subject: [PATCH 059/108] Revert to commit 352521fd86f362916c12db7744e4259c711d5c91 --- .github/workflows/main.yml | 17 +++--------- packages/android/build.gradle | 51 ----------------------------------- 2 files changed, 4 insertions(+), 64 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae4fdfa..b7d535a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -252,21 +252,12 @@ jobs: fi done - - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - name: release android aar to maven central - if: steps.tag.outputs.version != '' - run: | - echo "Maven Central Username length: ${#MAVEN_CENTRAL_USERNAME}" - echo "Maven Central Token length: ${#MAVEN_CENTRAL_TOKEN}" - echo "Signing key length: ${#SIGNING_KEY}" - cd packages/android && ./gradlew publishToCentralPortal -PVERSION=${{ steps.tag.outputs.version }} + if: false # maven central namespace needs to be verified first steps.tag.outputs.version != '' + run: cd packages/android && gradle publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} diff --git a/packages/android/build.gradle b/packages/android/build.gradle index 348a0a9..640eaed 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -5,14 +5,11 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.5.2' - classpath 'com.gradleup.central:central-publishing:0.4.0' } } apply plugin: 'com.android.library' apply plugin: 'maven-publish' -apply plugin: 'signing' -apply plugin: 'com.gradleup.central.publish' android { namespace 'ai.sqlite.vector' @@ -50,44 +47,6 @@ repositories { dependencies { } -centralPublishing { - username = project.findProperty("MAVEN_CENTRAL_USERNAME") ?: "" - password = project.findProperty("MAVEN_CENTRAL_TOKEN") ?: "" - - pom { - groupId = 'ai.sqlite' - artifactId = 'vector' - version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() - - name = 'SQLite Vector' - description = 'A cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. Works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default.' - url = 'https://github.com/sqliteai/sqlite-vector' - - licenses { - license { - name = 'Elastic License 2.0' - url = 'https://www.elastic.co/licensing/elastic-license' - } - } - - developers { - developer { - id = 'sqliteai' - name = 'SQLite Cloud, Inc.' - email = 'info@sqlitecloud.io' - organization = 'SQLite Cloud, Inc.' - organizationUrl = 'https://sqlite.ai' - } - } - - scm { - connection = 'scm:git:git://github.com/sqliteai/sqlite-vector.git' - developerConnection = 'scm:git:ssh://github.com/sqliteai/sqlite-vector.git' - url = 'https://github.com/sqliteai/sqlite-vector' - } - } -} - afterEvaluate { publishing { publications { @@ -100,14 +59,4 @@ afterEvaluate { } } } -} - -signing { - def signingKey = project.findProperty("SIGNING_KEY") - def signingPassword = project.findProperty("SIGNING_PASSWORD") - - if (signingKey && signingPassword) { - useInMemoryPgpKeys(signingKey, signingPassword) - sign publishing.publications.release - } } \ No newline at end of file From b649b16f8700afda4f70cc5b4637cc1060fbcbe0 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 22:31:57 +0200 Subject: [PATCH 060/108] fix(packages/android): update Gradle configuration for Maven Central publishing --- .github/workflows/main.yml | 14 +++++++--- packages/android/build.gradle | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b7d535a..232e8b8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -252,12 +252,18 @@ jobs: fi done + - uses: actions/setup-java@v4 + if: steps.tag.outputs.version != '' + with: + distribution: 'temurin' + java-version: '17' + - name: release android aar to maven central - if: false # maven central namespace needs to be verified first steps.tag.outputs.version != '' - run: cd packages/android && gradle publishReleasePublicationToOSSRHRepository -PVERSION=${{ steps.tag.outputs.version }} + if: steps.tag.outputs.version != '' + run: cd packages/android && ./gradlew publishAllPublicationsToMavenCentral -PVERSION=${{ steps.tag.outputs.version }} env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + SONATYPE_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} diff --git a/packages/android/build.gradle b/packages/android/build.gradle index 640eaed..04231d0 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -8,6 +8,10 @@ buildscript { } } +plugins { + id 'com.gradleup.nmcp' version '1.2.0' +} + apply plugin: 'com.android.library' apply plugin: 'maven-publish' @@ -56,7 +60,54 @@ afterEvaluate { version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() artifact bundleReleaseAar + + pom { + name = 'SQLite Vector' + description = 'A cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. Works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default.' + url = 'https://github.com/sqliteai/sqlite-vector' + + licenses { + license { + name = 'Elastic License 2.0' + url = 'https://www.elastic.co/licensing/elastic-license' + } + } + + developers { + developer { + id = 'sqliteai' + name = 'SQLite Cloud, Inc.' + email = 'info@sqlitecloud.io' + organization = 'SQLite Cloud, Inc.' + organizationUrl = 'https://sqlite.ai' + } + } + + scm { + connection = 'scm:git:git://github.com/sqliteai/sqlite-vector.git' + developerConnection = 'scm:git:ssh://github.com/sqliteai/sqlite-vector.git' + url = 'https://github.com/sqliteai/sqlite-vector' + } + } } } } +} + +signing { + def signingKey = project.findProperty("SIGNING_KEY") ?: System.getenv("SIGNING_KEY") + def signingPassword = project.findProperty("SIGNING_PASSWORD") ?: System.getenv("SIGNING_PASSWORD") + + if (signingKey && signingPassword) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign publishing.publications.release + } +} + +nmcp { + publishAllPublications { + username = project.findProperty("SONATYPE_USERNAME") ?: System.getenv("SONATYPE_USERNAME") + password = project.findProperty("SONATYPE_PASSWORD") ?: System.getenv("SONATYPE_PASSWORD") + publicationType = "USER_MANAGED" + } } \ No newline at end of file From c2ca2b4f16ff0c9fb20dcecded50a6bc50395e7f Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 1 Oct 2025 22:57:30 +0200 Subject: [PATCH 061/108] fix(packages/android): update Gradle nmcp publish command --- .github/workflows/main.yml | 2 +- packages/android/build.gradle | 33 ++++++++++++++++++++------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 232e8b8..5d67130 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -260,7 +260,7 @@ jobs: - name: release android aar to maven central if: steps.tag.outputs.version != '' - run: cd packages/android && ./gradlew publishAllPublicationsToMavenCentral -PVERSION=${{ steps.tag.outputs.version }} + run: cd packages/android && ./gradlew publishToCentralPortal -PVERSION=${{ steps.tag.outputs.version }} env: SONATYPE_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN }} diff --git a/packages/android/build.gradle b/packages/android/build.gradle index 04231d0..d0dc9dd 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -14,6 +14,7 @@ plugins { apply plugin: 'com.android.library' apply plugin: 'maven-publish' +apply plugin: 'signing' android { namespace 'ai.sqlite.vector' @@ -59,10 +60,11 @@ afterEvaluate { artifactId = 'vector' version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() - artifact bundleReleaseAar + artifact tasks.named("bundleReleaseAar").get().outputs.files.singleFile + // Maven Central metadata pom { - name = 'SQLite Vector' + name = 'sqlite-vector' description = 'A cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. Works seamlessly on iOS, Android, Windows, Linux, and macOS, using just 30MB of memory by default.' url = 'https://github.com/sqliteai/sqlite-vector' @@ -85,8 +87,8 @@ afterEvaluate { scm { connection = 'scm:git:git://github.com/sqliteai/sqlite-vector.git' - developerConnection = 'scm:git:ssh://github.com/sqliteai/sqlite-vector.git' - url = 'https://github.com/sqliteai/sqlite-vector' + developerConnection = 'scm:git:ssh://github.com:sqliteai/sqlite-vector.git' + url = 'https://github.com/sqliteai/sqlite-vector/tree/main' } } } @@ -94,20 +96,25 @@ afterEvaluate { } } +// Signing configuration for Maven Central signing { - def signingKey = project.findProperty("SIGNING_KEY") ?: System.getenv("SIGNING_KEY") - def signingPassword = project.findProperty("SIGNING_PASSWORD") ?: System.getenv("SIGNING_PASSWORD") - - if (signingKey && signingPassword) { - useInMemoryPgpKeys(signingKey, signingPassword) + required { project.hasProperty("SIGNING_KEY") } + if (project.hasProperty("SIGNING_KEY")) { + useInMemoryPgpKeys( + project.property("SIGNING_KEY").toString(), + project.property("SIGNING_PASSWORD").toString() + ) sign publishing.publications.release } } +// Maven Central publishing via NMCP nmcp { - publishAllPublications { - username = project.findProperty("SONATYPE_USERNAME") ?: System.getenv("SONATYPE_USERNAME") - password = project.findProperty("SONATYPE_PASSWORD") ?: System.getenv("SONATYPE_PASSWORD") - publicationType = "USER_MANAGED" + if (project.hasProperty("SONATYPE_USERNAME") && project.hasProperty("SONATYPE_PASSWORD")) { + publishAllPublications { + username = project.property("SONATYPE_USERNAME") + password = project.property("SONATYPE_PASSWORD") + publicationType = "AUTOMATIC" + } } } \ No newline at end of file From 0395ed2b8323f140b4813355be1a34655b9f951f Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 2 Oct 2025 10:41:21 +0200 Subject: [PATCH 062/108] fix(packages/android): update Gradle publish command secrets and variables usage --- .github/workflows/main.yml | 7 +------ packages/android/build.gradle | 33 +++++++++++++++++---------------- src/sqlite-vector.h | 2 +- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5d67130..7e9af4b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -260,12 +260,7 @@ jobs: - name: release android aar to maven central if: steps.tag.outputs.version != '' - run: cd packages/android && ./gradlew publishToCentralPortal -PVERSION=${{ steps.tag.outputs.version }} - env: - SONATYPE_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + run: make aar && cd packages/android && ./gradlew publishAggregationToCentralPortal -PSIGNING_KEY="${{ secrets.SIGNING_KEY }}" -PSIGNING_PASSWORD="${{ secrets.SIGNING_PASSWORD }}" -PSONATYPE_USERNAME="${{ secrets.MAVEN_CENTRAL_USERNAME }}" -PSONATYPE_PASSWORD="${{ secrets.MAVEN_CENTRAL_TOKEN }}" -PVERSION="${{ steps.tag.outputs.version }}" - uses: softprops/action-gh-release@v2.2.1 if: steps.tag.outputs.version != '' diff --git a/packages/android/build.gradle b/packages/android/build.gradle index d0dc9dd..b2ef639 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -9,7 +9,7 @@ buildscript { } plugins { - id 'com.gradleup.nmcp' version '1.2.0' + id 'com.gradleup.nmcp.aggregation' version '1.2.0' } apply plugin: 'com.android.library' @@ -60,7 +60,7 @@ afterEvaluate { artifactId = 'vector' version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() - artifact tasks.named("bundleReleaseAar").get().outputs.files.singleFile + artifact("$buildDir/outputs/aar/android-release.aar") // Maven Central metadata pom { @@ -94,27 +94,28 @@ afterEvaluate { } } } -} -// Signing configuration for Maven Central -signing { - required { project.hasProperty("SIGNING_KEY") } - if (project.hasProperty("SIGNING_KEY")) { - useInMemoryPgpKeys( - project.property("SIGNING_KEY").toString(), - project.property("SIGNING_PASSWORD").toString() - ) - sign publishing.publications.release + // Signing configuration for Maven Central + signing { + required { project.hasProperty("SIGNING_KEY") } + if (project.hasProperty("SIGNING_KEY")) { + useInMemoryPgpKeys( + project.property("SIGNING_KEY").toString(), + project.property("SIGNING_PASSWORD").toString() + ) + sign publishing.publications.release + } } } -// Maven Central publishing via NMCP -nmcp { +// Maven Central publishing via NMCP aggregation +nmcpAggregation { if (project.hasProperty("SONATYPE_USERNAME") && project.hasProperty("SONATYPE_PASSWORD")) { - publishAllPublications { + centralPortal { username = project.property("SONATYPE_USERNAME") password = project.property("SONATYPE_PASSWORD") - publicationType = "AUTOMATIC" + publishingType = "AUTOMATIC" } + publishAllProjectsProbablyBreakingProjectIsolation() } } \ No newline at end of file diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 18fb487..7a8d488 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.29" +#define SQLITE_VECTOR_VERSION "0.9.32" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 4a60ec0fd436753fb1f9e1ad696a47c9bf6aa6de Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 2 Oct 2025 11:26:17 +0200 Subject: [PATCH 063/108] feat(docs): update README with Android package integration --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7c2d68e..a4ef15c 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,18 @@ Download the appropriate pre-built binary for your platform from the official [R - Android - iOS +### Loading the Extension + +```sql +-- In SQLite CLI +.load ./vector + +-- In SQL +SELECT load_extension('./vector'); +``` + +Or embed it directly into your application. + ### WASM Version You can download the WebAssembly (WASM) version of SQLite with the SQLite Vector extension enabled from: https://www.npmjs.com/package/@sqliteai/sqlite-wasm @@ -67,17 +79,63 @@ log("vector_version(): \(String(cString: sqlite3_column_text(stmt, 0)))") sqlite3_close(db) ``` -### Loading the Extension +### Android Package -```sql --- In SQLite CLI -.load ./vector +You can [add this project as a dependency to your Android project](https://central.sonatype.com/artifact/ai.sqlite/vector). +```gradle +repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } +} --- In SQL -SELECT load_extension('./vector'); +dependencies { + + ... + + implementation 'com.github.requery:sqlite-android:3.49.0' + implementation 'ai.sqlite:vector:0.9.32' // or 'com.github.sqliteai:sqlite-vector:0.9.32' +} ``` -Or embed it directly into your application. +After adding the package, you'll need to [enable extractNativeLibs](https://github.com/sqliteai/sqlite-extensions-guide/blob/18acfc56d6af8791928f3ac8df7dc0e6a9741dd4/examples/android/src/main/AndroidManifest.xml#L6). + +Here's an example of how to use the package: +```java +import android.database.Cursor; +import io.requery.android.database.sqlite.SQLiteCustomExtension; +import io.requery.android.database.sqlite.SQLiteDatabase; +import io.requery.android.database.sqlite.SQLiteDatabaseConfiguration; +import java.util.Collections; + +... + + private void vectorExtension() { + try { + SQLiteCustomExtension vectorExtension = new SQLiteCustomExtension(getApplicationInfo().nativeLibraryDir + "/vector", null); + SQLiteDatabaseConfiguration config = new SQLiteDatabaseConfiguration( + getCacheDir().getPath() + "/vector_test.db", + SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.OPEN_READWRITE, + Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList(vectorExtension) + ); + + SQLiteDatabase db = SQLiteDatabase.openDatabase(config, null, null); + + Cursor cursor = db.rawQuery("SELECT vector_version()", null); + if (cursor.moveToFirst()) { + String version = cursor.getString(0); + resultTextView.setText("vector_version(): " + version); + } + cursor.close(); + db.close(); + + } catch (Exception e) { + resultTextView.setText("❌ Error: " + e.getMessage()); + } + } +``` ### Python Package From 2ec1937e5f30ec8d49f1b938fbe2c512c07f6fde Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 2 Oct 2025 12:28:03 +0200 Subject: [PATCH 064/108] feat(ci): add artifact size comparison for releases --- .github/workflows/main.yml | 82 +++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e9af4b..76e7790 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -198,14 +198,77 @@ jobs: with: path: artifacts + - name: zip artifacts + run: | + VERSION=$(make version) + for folder in "artifacts"/*; do + if [ -d "$folder" ]; then + name=$(basename "$folder") + if [[ "$name" != "vector-apple-xcframework" && "$name" != "vector-android-aar" ]]; then + tar -czf "${name}-${VERSION}.tar.gz" -C "$folder" . + fi + if [[ "$name" != "vector-android-aar" ]]; then + (cd "$folder" && zip -rq "../../${name}-${VERSION}.zip" .) + else + cp "$folder"/*.aar "${name}-${VERSION}.aar" + fi + fi + done + - name: release tag version from sqlite-vector.h id: tag run: | VERSION=$(make version) if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - LATEST=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.name') + LATEST_RELEASE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest) + LATEST=$(echo "$LATEST_RELEASE" | jq -r '.name') if [[ "$VERSION" != "$LATEST" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Check artifact sizes against previous release + if [ -n "$LATEST" ] && [ "$LATEST" != "null" ]; then + echo "Checking artifact sizes against previous release: $LATEST" + FAILED=0 + + for artifact in vector-*-${VERSION}.*; do + if [ ! -f "$artifact" ]; then + continue + fi + + # Get current artifact size + NEW_SIZE=$(stat -c%s "$artifact" 2>/dev/null || stat -f%z "$artifact") + + # Get artifact name for previous release + ARTIFACT_NAME=$(echo "$artifact" | sed "s/${VERSION}/${LATEST}/") + + # Get previous artifact size from GitHub API + OLD_SIZE=$(echo "$LATEST_RELEASE" | jq -r ".assets[] | select(.name == \"$(basename "$ARTIFACT_NAME")\") | .size") + + if [ -z "$OLD_SIZE" ] || [ "$OLD_SIZE" = "null" ]; then + echo "⚠️ Previous artifact not found: $(basename "$ARTIFACT_NAME"), skipping comparison" + continue + fi + + # Calculate percentage increase + INCREASE=$(awk "BEGIN {printf \"%.2f\", (($NEW_SIZE - $OLD_SIZE) / $OLD_SIZE) * 100}") + + echo "📦 $artifact: $OLD_SIZE → $NEW_SIZE bytes (${INCREASE}% change)" + + # Check if increase is more than 5% + if (( $(echo "$INCREASE > 5" | bc -l) )); then + echo "❌ ERROR: $artifact size increased by ${INCREASE}% (limit: 5%)" + FAILED=1 + fi + done + + if [ $FAILED -eq 1 ]; then + echo "" + echo "❌ One or more artifacts exceeded the 5% size increase limit" + exit 1 + fi + + echo "✅ All artifacts within 5% size increase limit" + fi else echo "::warning file=src/sqlite-vector.h::To release a new version, please update the SQLITE_VECTOR_VERSION in src/sqlite-vector.h to be different than the latest $LATEST" fi @@ -234,23 +297,6 @@ jobs: git add modules/sqlite-vector git commit -m "Bump sqlite-vector version to ${{ steps.tag.outputs.version }}" git push origin main - - - name: zip artifacts - if: steps.tag.outputs.version != '' - run: | - for folder in "artifacts"/*; do - if [ -d "$folder" ]; then - name=$(basename "$folder") - if [[ "$name" != "vector-apple-xcframework" && "$name" != "vector-android-aar" ]]; then - tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" . - fi - if [[ "$name" != "vector-android-aar" ]]; then - (cd "$folder" && zip -rq "../../${name}-${{ steps.tag.outputs.version }}.zip" .) - else - cp "$folder"/*.aar "${name}-${{ steps.tag.outputs.version }}.aar" - fi - fi - done - uses: actions/setup-java@v4 if: steps.tag.outputs.version != '' From d2bdaf5d86c17d6405eb5fbd316deb178834bb91 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 2 Oct 2025 12:37:03 +0200 Subject: [PATCH 065/108] fix(ci): improve artifact size comparison logic in release workflow --- .github/workflows/main.yml | 71 +++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 76e7790..1fecbe9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -222,53 +222,54 @@ jobs: if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then LATEST_RELEASE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest) LATEST=$(echo "$LATEST_RELEASE" | jq -r '.name') - if [[ "$VERSION" != "$LATEST" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then - echo "version=$VERSION" >> $GITHUB_OUTPUT - - # Check artifact sizes against previous release - if [ -n "$LATEST" ] && [ "$LATEST" != "null" ]; then - echo "Checking artifact sizes against previous release: $LATEST" - FAILED=0 - for artifact in vector-*-${VERSION}.*; do - if [ ! -f "$artifact" ]; then - continue - fi + # Check artifact sizes against previous release + if [ -n "$LATEST" ] && [ "$LATEST" != "null" ]; then + echo "Checking artifact sizes against previous release: $LATEST" + FAILED=0 - # Get current artifact size - NEW_SIZE=$(stat -c%s "$artifact" 2>/dev/null || stat -f%z "$artifact") + for artifact in vector-*-${VERSION}.*; do + if [ ! -f "$artifact" ]; then + continue + fi - # Get artifact name for previous release - ARTIFACT_NAME=$(echo "$artifact" | sed "s/${VERSION}/${LATEST}/") + # Get current artifact size + NEW_SIZE=$(stat -c%s "$artifact" 2>/dev/null || stat -f%z "$artifact") - # Get previous artifact size from GitHub API - OLD_SIZE=$(echo "$LATEST_RELEASE" | jq -r ".assets[] | select(.name == \"$(basename "$ARTIFACT_NAME")\") | .size") + # Get artifact name for previous release + ARTIFACT_NAME=$(echo "$artifact" | sed "s/${VERSION}/${LATEST}/") - if [ -z "$OLD_SIZE" ] || [ "$OLD_SIZE" = "null" ]; then - echo "⚠️ Previous artifact not found: $(basename "$ARTIFACT_NAME"), skipping comparison" - continue - fi + # Get previous artifact size from GitHub API + OLD_SIZE=$(echo "$LATEST_RELEASE" | jq -r ".assets[] | select(.name == \"$(basename "$ARTIFACT_NAME")\") | .size") - # Calculate percentage increase - INCREASE=$(awk "BEGIN {printf \"%.2f\", (($NEW_SIZE - $OLD_SIZE) / $OLD_SIZE) * 100}") + if [ -z "$OLD_SIZE" ] || [ "$OLD_SIZE" = "null" ]; then + echo "⚠️ Previous artifact not found: $(basename "$ARTIFACT_NAME"), skipping comparison" + continue + fi - echo "📦 $artifact: $OLD_SIZE → $NEW_SIZE bytes (${INCREASE}% change)" + # Calculate percentage increase + INCREASE=$(awk "BEGIN {printf \"%.2f\", (($NEW_SIZE - $OLD_SIZE) / $OLD_SIZE) * 100}") - # Check if increase is more than 5% - if (( $(echo "$INCREASE > 5" | bc -l) )); then - echo "❌ ERROR: $artifact size increased by ${INCREASE}% (limit: 5%)" - FAILED=1 - fi - done + echo "📦 $artifact: $OLD_SIZE → $NEW_SIZE bytes (${INCREASE}% change)" - if [ $FAILED -eq 1 ]; then - echo "" - echo "❌ One or more artifacts exceeded the 5% size increase limit" - exit 1 + # Check if increase is more than 5% + if (( $(echo "$INCREASE > 5" | bc -l) )); then + echo "❌ ERROR: $artifact size increased by ${INCREASE}% (limit: 5%)" + FAILED=1 fi + done - echo "✅ All artifacts within 5% size increase limit" + if [ $FAILED -eq 1 ]; then + echo "" + echo "❌ One or more artifacts exceeded the 5% size increase limit" + exit 1 fi + + echo "✅ All artifacts within 5% size increase limit" + fi + + if [[ "$VERSION" != "$LATEST" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + echo "version=$VERSION" >> $GITHUB_OUTPUT else echo "::warning file=src/sqlite-vector.h::To release a new version, please update the SQLITE_VECTOR_VERSION in src/sqlite-vector.h to be different than the latest $LATEST" fi From 32a65fe2f666892ed68427b08c1bed5925821021 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 2 Oct 2025 12:43:24 +0200 Subject: [PATCH 066/108] feat(docs): enhance Android package integration instructions in README --- README.md | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a4ef15c..376109d 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ sqlite3_close(db) ### Android Package You can [add this project as a dependency to your Android project](https://central.sonatype.com/artifact/ai.sqlite/vector). + +**build.gradle (Groovy):** ```gradle repositories { google() @@ -90,11 +92,32 @@ repositories { } dependencies { + // ... - ... - + // Use requery's SQLite instead of Android's built-in SQLite to support loading custom extensions implementation 'com.github.requery:sqlite-android:3.49.0' - implementation 'ai.sqlite:vector:0.9.32' // or 'com.github.sqliteai:sqlite-vector:0.9.32' + // Both packages below are identical - use either one + implementation 'ai.sqlite:vector:0.9.32' // Maven Central + // implementation 'com.github.sqliteai:sqlite-vector:0.9.32' // JitPack (alternative) +} +``` + +**build.gradle.kts (Kotlin):** +```kotlin +repositories { + google() + mavenCentral() + maven(url = "https://jitpack.io") +} + +dependencies { + // ... + + // Use requery's SQLite instead of Android's built-in SQLite to support loading custom extensions + implementation("com.github.requery:sqlite-android:3.49.0") + // Both packages below are identical - use either one + implementation("ai.sqlite:vector:0.9.32") // Maven Central + // implementation("com.github.sqliteai:sqlite-vector:0.9.32") // JitPack (alternative) } ``` @@ -103,6 +126,7 @@ After adding the package, you'll need to [enable extractNativeLibs](https://gith Here's an example of how to use the package: ```java import android.database.Cursor; +import android.util.Log; import io.requery.android.database.sqlite.SQLiteCustomExtension; import io.requery.android.database.sqlite.SQLiteDatabase; import io.requery.android.database.sqlite.SQLiteDatabaseConfiguration; @@ -122,17 +146,17 @@ import java.util.Collections; ); SQLiteDatabase db = SQLiteDatabase.openDatabase(config, null, null); - + Cursor cursor = db.rawQuery("SELECT vector_version()", null); if (cursor.moveToFirst()) { String version = cursor.getString(0); - resultTextView.setText("vector_version(): " + version); + Log.i("sqlite-vector", "vector_version(): " + version); } cursor.close(); db.close(); } catch (Exception e) { - resultTextView.setText("❌ Error: " + e.getMessage()); + Log.e("sqlite-vector", "Error: " + e.getMessage()); } } ``` From f8aeb5f6faf58430767f59dabdc44eec37a37f8d Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 2 Oct 2025 12:59:02 +0200 Subject: [PATCH 067/108] Bump version to 0.9.33 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 7a8d488..73700de 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.32" +#define SQLITE_VECTOR_VERSION "0.9.33" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 37910136ac76a8345eaac277ab9ed1dc29915b31 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 2 Oct 2025 13:45:47 +0200 Subject: [PATCH 068/108] feat(docs): update language instructions for Android in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 376109d..e90cf25 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ sqlite3_close(db) You can [add this project as a dependency to your Android project](https://central.sonatype.com/artifact/ai.sqlite/vector). -**build.gradle (Groovy):** +**Groovy:** ```gradle repositories { google() @@ -102,7 +102,7 @@ dependencies { } ``` -**build.gradle.kts (Kotlin):** +**Kotlin:** ```kotlin repositories { google() From a72b4bbbc1533c4dd0606e5c68a2eb74ba289107 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 3 Oct 2025 17:10:38 +0200 Subject: [PATCH 069/108] fix(release): skip duplicate AAR build --- .github/workflows/main.yml | 2 +- packages/android/build.gradle | 2 +- src/sqlite-vector.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1fecbe9..f4bb685 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -307,7 +307,7 @@ jobs: - name: release android aar to maven central if: steps.tag.outputs.version != '' - run: make aar && cd packages/android && ./gradlew publishAggregationToCentralPortal -PSIGNING_KEY="${{ secrets.SIGNING_KEY }}" -PSIGNING_PASSWORD="${{ secrets.SIGNING_PASSWORD }}" -PSONATYPE_USERNAME="${{ secrets.MAVEN_CENTRAL_USERNAME }}" -PSONATYPE_PASSWORD="${{ secrets.MAVEN_CENTRAL_TOKEN }}" -PVERSION="${{ steps.tag.outputs.version }}" + run: cd packages/android && ./gradlew publishAggregationToCentralPortal -PSIGNING_KEY="${{ secrets.SIGNING_KEY }}" -PSIGNING_PASSWORD="${{ secrets.SIGNING_PASSWORD }}" -PSONATYPE_USERNAME="${{ secrets.MAVEN_CENTRAL_USERNAME }}" -PSONATYPE_PASSWORD="${{ secrets.MAVEN_CENTRAL_TOKEN }}" -PVERSION="${{ steps.tag.outputs.version }}" -PAAR_PATH="../../artifacts/vector-android-aar/vector.aar" - uses: softprops/action-gh-release@v2.2.1 if: steps.tag.outputs.version != '' diff --git a/packages/android/build.gradle b/packages/android/build.gradle index b2ef639..a566ba7 100644 --- a/packages/android/build.gradle +++ b/packages/android/build.gradle @@ -60,7 +60,7 @@ afterEvaluate { artifactId = 'vector' version = project.hasProperty('VERSION') ? project.VERSION : ['make', 'version'].execute(null, file('../..')).text.trim() - artifact("$buildDir/outputs/aar/android-release.aar") + artifact(project.hasProperty('AAR_PATH') ? project.AAR_PATH : "$buildDir/outputs/aar/android-release.aar") // Maven Central metadata pom { diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 73700de..dfecc44 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.33" +#define SQLITE_VECTOR_VERSION "0.9.34" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 56b5b9598e85ebb9bb25afc72cc4c1edef60d42e Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 3 Oct 2025 18:28:47 +0200 Subject: [PATCH 070/108] docs: compact Android package section --- README.md | 85 +++++++++---------------------------------------------- 1 file changed, 13 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index e90cf25..efe2f01 100644 --- a/README.md +++ b/README.md @@ -81,86 +81,27 @@ sqlite3_close(db) ### Android Package -You can [add this project as a dependency to your Android project](https://central.sonatype.com/artifact/ai.sqlite/vector). +Add the [following](https://central.sonatype.com/artifact/ai.sqlite/vector) to your Gradle dependencies: -**Groovy:** ```gradle -repositories { - google() - mavenCentral() - maven { url 'https://jitpack.io' } -} - -dependencies { - // ... - - // Use requery's SQLite instead of Android's built-in SQLite to support loading custom extensions - implementation 'com.github.requery:sqlite-android:3.49.0' - // Both packages below are identical - use either one - implementation 'ai.sqlite:vector:0.9.32' // Maven Central - // implementation 'com.github.sqliteai:sqlite-vector:0.9.32' // JitPack (alternative) -} +implementation 'ai.sqlite:vector:0.9.34' ``` -**Kotlin:** -```kotlin -repositories { - google() - mavenCentral() - maven(url = "https://jitpack.io") -} - -dependencies { - // ... - - // Use requery's SQLite instead of Android's built-in SQLite to support loading custom extensions - implementation("com.github.requery:sqlite-android:3.49.0") - // Both packages below are identical - use either one - implementation("ai.sqlite:vector:0.9.32") // Maven Central - // implementation("com.github.sqliteai:sqlite-vector:0.9.32") // JitPack (alternative) -} -``` - -After adding the package, you'll need to [enable extractNativeLibs](https://github.com/sqliteai/sqlite-extensions-guide/blob/18acfc56d6af8791928f3ac8df7dc0e6a9741dd4/examples/android/src/main/AndroidManifest.xml#L6). - Here's an example of how to use the package: ```java -import android.database.Cursor; -import android.util.Log; -import io.requery.android.database.sqlite.SQLiteCustomExtension; -import io.requery.android.database.sqlite.SQLiteDatabase; -import io.requery.android.database.sqlite.SQLiteDatabaseConfiguration; -import java.util.Collections; - -... - - private void vectorExtension() { - try { - SQLiteCustomExtension vectorExtension = new SQLiteCustomExtension(getApplicationInfo().nativeLibraryDir + "/vector", null); - SQLiteDatabaseConfiguration config = new SQLiteDatabaseConfiguration( - getCacheDir().getPath() + "/vector_test.db", - SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.OPEN_READWRITE, - Collections.emptyList(), - Collections.emptyList(), - Collections.singletonList(vectorExtension) - ); - - SQLiteDatabase db = SQLiteDatabase.openDatabase(config, null, null); - - Cursor cursor = db.rawQuery("SELECT vector_version()", null); - if (cursor.moveToFirst()) { - String version = cursor.getString(0); - Log.i("sqlite-vector", "vector_version(): " + version); - } - cursor.close(); - db.close(); - - } catch (Exception e) { - Log.e("sqlite-vector", "Error: " + e.getMessage()); - } - } +SQLiteCustomExtension vectorExtension = new SQLiteCustomExtension(getApplicationInfo().nativeLibraryDir + "/vector", null); +SQLiteDatabaseConfiguration config = new SQLiteDatabaseConfiguration( + getCacheDir().getPath() + "/vector_test.db", + SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.OPEN_READWRITE, + Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList(vectorExtension) +); +SQLiteDatabase db = SQLiteDatabase.openDatabase(config, null, null); ``` +**Note:** Additional settings and configuration are required for a complete setup. For full implementation details, see the [complete Android example](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/examples/android/README.md). + ### Python Package Python developers can quickly get started using the ready-to-use `sqlite-vector` package available on PyPI: From 09bcf4cf3d958f150cf172f3c5967e493d185ce8 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 6 Oct 2025 10:42:17 +0200 Subject: [PATCH 071/108] feat(release): add separate architecture macOS builds --- .github/workflows/main.yml | 12 ++++++++++-- Makefile | 10 ++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f4bb685..2823f92 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ${{ matrix.os }} container: ${{ matrix.container && matrix.container || '' }} - name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && matrix.name != 'android-aar' && ' + test' || ''}} + name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && matrix.name != 'android-aar' && ( matrix.name == 'macos' && matrix.arch != 'x86_64' ) && ' + test' || ''}} timeout-minutes: 20 strategy: fail-fast: false @@ -31,6 +31,14 @@ jobs: name: linux-musl - os: macos-15 name: macos + - os: macos-15 + arch: x86_64 + name: macos + make: ARCH=x86_64 + - os: macos-15 + arch: arm64 + name: macos + make: ARCH=arm64 - os: windows-2022 arch: x86_64 name: windows @@ -171,7 +179,7 @@ jobs: adb shell "sh /data/local/tmp/commands.sh" - name: test sqlite-vector - if: contains(matrix.name, 'linux') || matrix.name == 'windows' || matrix.name == 'macos' + if: contains(matrix.name, 'linux') || matrix.name == 'windows' || ( matrix.name == 'macos' && matrix.arch != 'x86_64' ) run: ${{ matrix.name == 'linux-musl' && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make test ${{ matrix.make && matrix.make || ''}} - uses: actions/upload-artifact@v4.6.2 diff --git a/Makefile b/Makefile index 4d4a07d..ccaa3d3 100644 --- a/Makefile +++ b/Makefile @@ -48,8 +48,14 @@ ifeq ($(PLATFORM),windows) STRIP = strip --strip-unneeded $@ else ifeq ($(PLATFORM),macos) TARGET := $(DIST_DIR)/vector.dylib - LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib -undefined dynamic_lookup - CFLAGS += -arch x86_64 -arch arm64 + ifndef ARCH + LDFLAGS += -arch x86_64 -arch arm64 + CFLAGS += -arch x86_64 -arch arm64 + else + LDFLAGS += -arch $(ARCH) + CFLAGS += -arch $(ARCH) + endif + LDFLAGS += -dynamiclib -undefined dynamic_lookup STRIP = strip -x -S $@ else ifeq ($(PLATFORM),android) ifndef ARCH # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH From 9b300250385bed3dcf46a54aeee55cbcfd0bcbef Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 6 Oct 2025 10:47:57 +0200 Subject: [PATCH 072/108] fix(workflow): correct conditional logic for macOS build naming --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2823f92..b8eeb71 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ${{ matrix.os }} container: ${{ matrix.container && matrix.container || '' }} - name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && matrix.name != 'android-aar' && ( matrix.name == 'macos' && matrix.arch != 'x86_64' ) && ' + test' || ''}} + name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && matrix.name != 'android-aar' && ( matrix.name != 'macos' || matrix.arch != 'x86_64' ) && ' + test' || ''}} timeout-minutes: 20 strategy: fail-fast: false From 4eab7499fe05f7666f6bafa30d9ce67d66f3a0e4 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 6 Oct 2025 11:09:29 +0200 Subject: [PATCH 073/108] fix(build): add headerpad_max_install_names to LDFLAGS for Apple platforms --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ccaa3d3..e02aaba 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ else ifeq ($(PLATFORM),macos) LDFLAGS += -arch $(ARCH) CFLAGS += -arch $(ARCH) endif - LDFLAGS += -dynamiclib -undefined dynamic_lookup + LDFLAGS += -dynamiclib -undefined dynamic_lookup -headerpad_max_install_names STRIP = strip -x -S $@ else ifeq ($(PLATFORM),android) ifndef ARCH # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH @@ -75,13 +75,13 @@ else ifeq ($(PLATFORM),android) else ifeq ($(PLATFORM),ios) TARGET := $(DIST_DIR)/vector.dylib SDK := -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0 - LDFLAGS += -dynamiclib $(SDK) + LDFLAGS += -dynamiclib $(SDK) -headerpad_max_install_names CFLAGS += -arch arm64 $(SDK) STRIP = strip -x -S $@ else ifeq ($(PLATFORM),ios-sim) TARGET := $(DIST_DIR)/vector.dylib SDK := -isysroot $(shell xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0 - LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib $(SDK) + LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib $(SDK) -headerpad_max_install_names CFLAGS += -arch x86_64 -arch arm64 $(SDK) STRIP = strip -x -S $@ else # linux From 3a580e27aa85826fb7beaa2f67da66ca5a9fdb09 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 6 Oct 2025 11:19:59 +0200 Subject: [PATCH 074/108] Bump version to 0.9.35 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index dfecc44..e8b9e15 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.34" +#define SQLITE_VECTOR_VERSION "0.9.35" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 1de9b1543384ff700b87a166050fe56e57127f1c Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 9 Oct 2025 11:14:54 +0200 Subject: [PATCH 075/108] Added more protection to some critical functions --- src/sqlite-vector.c | 80 +++++++++++++++++++++++++++++++-------------- src/sqlite-vector.h | 2 +- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 23a6496..2522fe6 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -186,6 +186,8 @@ typedef int (*vcursor_sort_callback)(vFullScanCursor *c); extern distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX]; extern char *distance_backend_name; +static sqlite3_mutex *qmutex; + // MARK: - SQLite Utils - bool sqlite_system_exists (sqlite3 *db, const char *name, const char *type) { @@ -1349,11 +1351,14 @@ static void vector_quantize_preload (sqlite3_context *context, int argc, sqlite3 return; } + // free previous preload (if any) + sqlite3_mutex_enter(qmutex); if (t_ctx->preloaded) { sqlite3_free(t_ctx->preloaded); t_ctx->preloaded = NULL; t_ctx->precounter = 0; } + sqlite3_mutex_leave(qmutex); char sql[STATIC_SQL_SIZE]; generate_memory_quant_table(table_name, column_name, sql); @@ -1371,36 +1376,43 @@ static void vector_quantize_preload (sqlite3_context *context, int argc, sqlite3 return; } - generate_select_quant_table(table_name, column_name, sql); - - int rc = SQLITE_NOMEM; sqlite3_stmt *vm = NULL; - rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL); - if (rc != SQLITE_OK) goto vector_preload_cleanup; + generate_select_quant_table(table_name, column_name, sql); + int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL); + if (rc != SQLITE_OK) { + context_result_error(context, rc, "Internal statement error: %s", sqlite3_errmsg(db)); + sqlite3_finalize(vm); + sqlite3_free(buffer); + return; + } int seek = 0; while (1) { rc = sqlite3_step(vm); if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} // return error: rebuild must be call (only if first time run) - else if (rc != SQLITE_ROW) goto vector_preload_cleanup; + else if (rc != SQLITE_ROW) {break;} int n = sqlite3_column_int(vm, 0); int bytes = sqlite3_column_bytes(vm, 1); uint8_t *data = (uint8_t *)sqlite3_column_blob(vm, 1); + // no check here because I am sure quantization was performed only on non NULL data memcpy(buffer+seek, data, bytes); seek += bytes; counter += n; } - rc = SQLITE_OK; + sqlite3_finalize(vm); + + if (rc != SQLITE_OK) { + sqlite3_free(buffer); + context_result_error(context, rc, "vector_quantize_preload failed: %s", sqlite3_errmsg(db)); + return; + } + sqlite3_mutex_enter(qmutex); t_ctx->preloaded = buffer; t_ctx->precounter = counter; - -vector_preload_cleanup: - if (rc != SQLITE_OK) printf("Error in vector_quantize_preload: %s\n", sqlite3_errmsg(db)); - if (vm) sqlite3_finalize(vm); - return; + sqlite3_mutex_leave(qmutex); } static int vector_quantize (sqlite3_context *context, const char *table_name, const char *column_name, const char *arg_options, bool *was_preloaded) { @@ -1415,8 +1427,10 @@ static int vector_quantize (sqlite3_context *context, const char *table_name, co char sql[STATIC_SQL_SIZE]; sqlite3 *db = sqlite3_context_db_handle(context); - rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); + bool savepoint_open = false; + rc = sqlite3_exec(db, "SAVEPOINT quantize;", NULL, NULL, NULL); if (rc != SQLITE_OK) goto quantize_cleanup; + savepoint_open = true; generate_drop_quant_table(table_name, column_name, sql); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); @@ -1428,12 +1442,11 @@ static int vector_quantize (sqlite3_context *context, const char *table_name, co vector_options options = t_ctx->options; // t_ctx guarantees to exist bool res = parse_keyvalue_string(context, arg_options, vector_keyvalue_callback, &options); - if (res == false) return SQLITE_ERROR; + if (res == false) {rc = SQLITE_ERROR; goto quantize_cleanup;} + sqlite3_mutex_enter(qmutex); rc = vector_rebuild_quantization(context, table_name, column_name, t_ctx, options.q_type, options.max_memory, &counter); - if (rc != SQLITE_OK) goto quantize_cleanup; - - rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); + sqlite3_mutex_leave(qmutex); if (rc != SQLITE_OK) goto quantize_cleanup; // serialize quantization options @@ -1444,18 +1457,26 @@ static int vector_quantize (sqlite3_context *context, const char *table_name, co rc = sqlite_serialize(context, table_name, column_name, SQLITE_FLOAT, OPTION_KEY_QUANTOFFSET, 0, t_ctx->offset); if (rc != SQLITE_OK) goto quantize_cleanup; -quantize_cleanup: - if (rc != SQLITE_OK) { - printf("%s", sqlite3_errmsg(db)); - sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL); - sqlite3_result_error_code(context, rc); - return rc; - } + rc = sqlite3_exec(db, "RELEASE quantize;", NULL, NULL, NULL); + if (rc != SQLITE_OK) goto quantize_cleanup; + savepoint_open = false; - // returns the total number of quantized rows + // success: returns the total number of quantized rows sqlite3_result_int64(context, (sqlite3_int64)counter); if (was_preloaded) *was_preloaded = (t_ctx->preloaded != NULL); return SQLITE_OK; + +quantize_cleanup: { + const char *errmsg = sqlite3_errmsg(db); + if (savepoint_open) { + sqlite3_exec(db, "ROLLBACK TO quantize;", NULL, NULL, NULL); + sqlite3_exec(db, "RELEASE quantize;", NULL, NULL, NULL); + } + + sqlite3_result_error(context, errmsg, -1); + sqlite3_result_error_code(context, rc); + return rc; + } } static void vector_quantize3 (sqlite3_context *context, int argc, sqlite3_value **argv) { @@ -2558,6 +2579,15 @@ SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const s #endif int rc = SQLITE_OK; + // there's no built-in way to verify if sqlite3_vector_init has already been called for this specific database connection + // the workaround is to attempt to execute vector_version and check for an error + // an error indicates that initialization has not been performed + if (sqlite3_exec(db, "SELECT vector_version();", NULL, NULL, NULL) == SQLITE_OK) return SQLITE_OK; + + // get an app global static mutex + qmutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_APP1); + + // init internal distance functions (do not force CPU) init_distance_functions(false); // create internal table diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 18fb487..6e15eb9 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.29" +#define SQLITE_VECTOR_VERSION "0.9.30" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From a08565f8ed5c9c80883e91903288786395b52715 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 10 Oct 2025 07:49:30 +0200 Subject: [PATCH 076/108] Fixed WASM compilation --- src/sqlite-vector.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index 2522fe6..b2bbcd0 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -46,6 +46,13 @@ char *strcasestr(const char *haystack, const char *needle) { #include #endif +#ifdef SQLITE_WASM_EXTRA_INIT +#define sqlite3_mutex_alloc(_type) NULL +#define sqlite3_mutex_enter(_mutex) +#define sqlite3_mutex_leave(_mutex) +#define sqlite3_mutex void +#endif + #ifndef SQLITE_CORE SQLITE_EXTENSION_INIT1 #endif From 1dac2ae3bc29df4d9536a47a84776e365995738d Mon Sep 17 00:00:00 2001 From: Gioele Cantoni <48024736+Gioee@users.noreply.github.com> Date: Fri, 10 Oct 2025 08:08:20 +0200 Subject: [PATCH 077/108] Bump version to 0.9.37 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index bc52df7..5a000d6 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.36" +#define SQLITE_VECTOR_VERSION "0.9.37" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 64d09eeb1401fa8a3dff411a26326aae191e927a Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Wed, 15 Oct 2025 09:02:57 +0200 Subject: [PATCH 078/108] Update README.md --- README.md | 66 +++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index efe2f01..57513dc 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,39 @@ Or embed it directly into your application. You can download the WebAssembly (WASM) version of SQLite with the SQLite Vector extension enabled from: https://www.npmjs.com/package/@sqliteai/sqlite-wasm +## Example Usage + +```sql +-- Create a regular SQLite table +CREATE TABLE images ( + id INTEGER PRIMARY KEY, + embedding BLOB, -- store Float32/UInt8/etc. + label TEXT +); + +-- Insert a BLOB vector (Float32, 384 dimensions) using bindings +INSERT INTO images (embedding, label) VALUES (?, 'cat'); + +-- Insert a JSON vector (Float32, 384 dimensions) +INSERT INTO images (embedding, label) VALUES (vector_as_f32('[0.3, 1.0, 0.9, 3.2, 1.4,...]'), 'dog'); + +-- Initialize the vector. By default, the distance function is L2. +-- To use a different metric, specify one of the following options: +-- distance=L1, distance=COSINE, distance=DOT, or distance=SQUARED_L2. +SELECT vector_init('images', 'embedding', 'type=FLOAT32,dimension=384'); + +-- Quantize vector +SELECT vector_quantize('images', 'embedding'); + +-- Optional preload quantized version in memory (for a 4x/5x speedup) +SELECT vector_quantize_preload('images', 'embedding'); + +-- Run a nearest neighbor query on the quantized version (returns top 20 closest vectors) +SELECT e.id, v.distance FROM images AS e + JOIN vector_quantize_scan('images', 'embedding', ?, 20) AS v + ON e.id = v.rowid; +``` + ### Swift Package You can [add this repository as a package dependency to your Swift project](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app#Add-a-package-dependency). After adding the package, you'll need to set up SQLite with extension loading by following steps 4 and 5 of [this guide](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/platforms/ios.md#4-set-up-sqlite-with-extension-loading). @@ -112,39 +145,6 @@ pip install sqliteai-vector For usage details and examples, see the [Python package documentation](./packages/python/README.md). -## Example Usage - -```sql --- Create a regular SQLite table -CREATE TABLE images ( - id INTEGER PRIMARY KEY, - embedding BLOB, -- store Float32/UInt8/etc. - label TEXT -); - --- Insert a BLOB vector (Float32, 384 dimensions) using bindings -INSERT INTO images (embedding, label) VALUES (?, 'cat'); - --- Insert a JSON vector (Float32, 384 dimensions) -INSERT INTO images (embedding, label) VALUES (vector_as_f32('[0.3, 1.0, 0.9, 3.2, 1.4,...]'), 'dog'); - --- Initialize the vector. By default, the distance function is L2. --- To use a different metric, specify one of the following options: --- distance=L1, distance=COSINE, distance=DOT, or distance=SQUARED_L2. -SELECT vector_init('images', 'embedding', 'type=FLOAT32,dimension=384'); - --- Quantize vector -SELECT vector_quantize('images', 'embedding'); - --- Optional preload quantized version in memory (for a 4x/5x speedup) -SELECT vector_quantize_preload('images', 'embedding'); - --- Run a nearest neighbor query on the quantized version (returns top 20 closest vectors) -SELECT e.id, v.distance FROM images AS e - JOIN vector_quantize_scan('images', 'embedding', ?, 20) AS v - ON e.id = v.rowid; -``` - ## Documentation Extensive API documentation can be found in the [API page](https://github.com/sqliteai/sqlite-vector/blob/main/API.md). From e159d6d557d76d239203468405623b15c150cb88 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Wed, 15 Oct 2025 09:17:33 +0200 Subject: [PATCH 079/108] Added new streaming interface --- API.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/API.md b/API.md index 8466f38..59015bc 100644 --- a/API.md +++ b/API.md @@ -262,3 +262,52 @@ FROM vector_quantize_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0. ``` --- + +## 🔁 Streaming Interfaces + +### `vector_full_scan_stream` and `vector_quantize_scan_stream` + +**Returns:** `Virtual Table (rowid, distance)` + +**Description:** +These streaming interfaces provide the same functionality as `vector_full_scan` and `vector_quantize_scan`, respectively, but are designed for incremental or filtered processing of results. +Unlike their non-streaming counterparts, these functions **omit the fourth parameter (`k`)** and allow you to use standard SQL clauses such as `WHERE` and `LIMIT` to control filtering and result count. + +This makes them ideal for combining vector search with additional query conditions or progressive result consumption in streaming applications. + +**Parameters:** + +* `table` (TEXT): Name of the target table. +* `column` (TEXT): Column containing vectors. +* `vector` (BLOB or JSON): The query vector. + +**Key Differences from Non-Streaming Variants:** + +| Function | Equivalent To | Requires `k` | Supports `WHERE` | Supports `LIMIT` | +| ----------------------------- | ---------------------- | ------------ | ---------------- | ---------------- | +| `vector_full_scan_stream` | `vector_full_scan` | ❌ | ✅ | ✅ | +| `vector_quantize_scan_stream` | `vector_quantize_scan` | ❌ | ✅ | ✅ | + +**Examples:** + +```sql +-- Perform a filtered full scan +SELECT rowid, distance +FROM vector_full_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) +WHERE category = 'science' +LIMIT 5; +``` + +```sql +-- Perform a filtered approximate scan using quantized data +SELECT rowid, distance +FROM vector_quantize_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) +WHERE score > 0.8 +LIMIT 10; +``` + +**Usage Notes:** + +* These interfaces return rows progressively and can efficiently combine vector similarity with SQL-level filters. +* The `LIMIT` clause can be used to control how many rows are read or returned. +* The query planner integrates the streaming virtual table into the overall SQL execution plan, enabling hybrid filtering and ranking operations. From fdb6a5f1bf882e2b6cdf288a08866ecf31100130 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Wed, 15 Oct 2025 09:31:01 +0200 Subject: [PATCH 080/108] Clarify how to access additional columns --- API.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/API.md b/API.md index 59015bc..9f4ebe4 100644 --- a/API.md +++ b/API.md @@ -58,6 +58,10 @@ SELECT vector_backend(); Initializes the vector extension for a given table and column. This is **mandatory** before performing any vector search or quantization. `vector_init` must be called in every database connection that needs to perform vector operations. +The target table must have a **`rowid`** (an integer primary key, either explicit or implicit). +If the table was created using `WITHOUT ROWID`, it must have **exactly one primary key column of type `INTEGER`**. +This ensures that each vector can be uniquely identified and efficiently referenced during search and quantization. + **Parameters:** * `table` (TEXT): Name of the table containing vector data. @@ -214,7 +218,8 @@ INSERT INTO compressed_vectors(embedding) VALUES(vector_as_u8(X'010203')); **Returns:** `Virtual Table (rowid, distance)` **Description:** -Performs a brute-force nearest neighbor search using the given vector. Despite its brute-force nature, this function is highly optimized and useful for small datasets or validation. +Performs a brute-force nearest neighbor search using the given vector. Despite its brute-force nature, this function is highly optimized and useful for small datasets (rows < 1000000) or validation. +Since this interface only returns rowid and distance, if you need to access additional columns from the original table, you must use a SELF JOIN. **Parameters:** @@ -237,7 +242,7 @@ FROM vector_full_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]') **Returns:** `Virtual Table (rowid, distance)` **Description:** -Performs a fast approximate nearest neighbor search using the pre-quantized data. This is the **recommended query method** for large datasets due to its excellent speed/recall/memory trade-off. +Performs a fast approximate nearest neighbor search using the pre-quantized data. This is the **recommended query method** for large datasets due to its excellent speed/recall/memory trade-off. Since this interface only returns rowid and distance, if you need to access additional columns from the original table, you must use a SELF JOIN. You **must run `vector_quantize()`** before using `vector_quantize_scan()` and when data initialized for vectors changes. @@ -271,7 +276,8 @@ FROM vector_quantize_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0. **Description:** These streaming interfaces provide the same functionality as `vector_full_scan` and `vector_quantize_scan`, respectively, but are designed for incremental or filtered processing of results. -Unlike their non-streaming counterparts, these functions **omit the fourth parameter (`k`)** and allow you to use standard SQL clauses such as `WHERE` and `LIMIT` to control filtering and result count. + +Unlike their non-streaming counterparts, these functions **omit the fourth parameter (`k`)** and allow you to use standard SQL clauses such as `WHERE` and `LIMIT` to control filtering and result count. Since this interface only returns rowid and distance, if you need to access additional columns from the original table, you must use a SELF JOIN. This makes them ideal for combining vector search with additional query conditions or progressive result consumption in streaming applications. @@ -306,6 +312,20 @@ WHERE score > 0.8 LIMIT 10; ``` +**Accessing Additional Columns:** + +```sql +-- Perform a filtered full scan with additional columns +SELECT + v.rowid AS sentence_id, + row_number() OVER (ORDER BY v.distance) AS rank_number, + v.distance +FROM vector_full_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) AS v + JOIN sentences ON sentences.rowid = v.rowid +WHERE sentences.chunk_id = 297 +LIMIT 3; +``` + **Usage Notes:** * These interfaces return rows progressively and can efficiently combine vector similarity with SQL-level filters. From e1972c4d6da5cb6608676828ff41f8ea9acb8b40 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Wed, 15 Oct 2025 09:50:06 +0200 Subject: [PATCH 081/108] Update API.md --- API.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/API.md b/API.md index 9f4ebe4..9571228 100644 --- a/API.md +++ b/API.md @@ -300,7 +300,6 @@ This makes them ideal for combining vector search with additional query conditio -- Perform a filtered full scan SELECT rowid, distance FROM vector_full_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) -WHERE category = 'science' LIMIT 5; ``` @@ -308,7 +307,6 @@ LIMIT 5; -- Perform a filtered approximate scan using quantized data SELECT rowid, distance FROM vector_quantize_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) -WHERE score > 0.8 LIMIT 10; ``` @@ -317,13 +315,13 @@ LIMIT 10; ```sql -- Perform a filtered full scan with additional columns SELECT - v.rowid AS sentence_id, + v.rowid, row_number() OVER (ORDER BY v.distance) AS rank_number, v.distance FROM vector_full_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) AS v - JOIN sentences ON sentences.rowid = v.rowid -WHERE sentences.chunk_id = 297 -LIMIT 3; + JOIN documents ON documents.rowid = v.rowid +WHERE documents.category = 'science' +LIMIT 10; ``` **Usage Notes:** From f167659df033a7d15029d6aa09649747185ad21d Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 15 Oct 2025 22:45:28 +0200 Subject: [PATCH 082/108] Update release notes for WASM builds and bump version to 0.9.38 --- .github/workflows/main.yml | 4 ++++ src/sqlite-vector.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8eeb71..67ead72 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -320,6 +320,10 @@ jobs: - uses: softprops/action-gh-release@v2.2.1 if: steps.tag.outputs.version != '' with: + body: | + # WASM Releases + **WASM repository**: [sqlite-wasm](https://github.com/sqliteai/sqlite-wasm) + **WASM package** is available on npm: [@sqliteai/sqlite-wasm](https://www.npmjs.com/package/@sqliteai/sqlite-wasm) generate_release_notes: true tag_name: ${{ steps.tag.outputs.version }} files: vector-*-${{ steps.tag.outputs.version }}.* diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 5a000d6..a8fb18e 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.37" +#define SQLITE_VECTOR_VERSION "0.9.38" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 72890a1f6f72a6c94475672a30f2f885ea987313 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 11:20:55 +0200 Subject: [PATCH 083/108] fix(workflow): missing bump sqlite-wasm version in package.json --- .github/workflows/main.yml | 4 ++++ src/sqlite-vector.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 67ead72..b13de0a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -304,6 +304,10 @@ jobs: git checkout ${{ github.sha }} cd ../.. git add modules/sqlite-vector + PKG=sqlite-wasm/package.json + TMP=sqlite-wasm/package.tmp.json + jq --arg version "$(cat modules/sqlite/VERSION)-sync.$(cd modules/sqlite-sync && make version)-vector.$(cd modules/sqlite-vector && make version)" '.version = $version' "$PKG" > "$TMP" && mv "$TMP" "$PKG" + git add "$PKG" git commit -m "Bump sqlite-vector version to ${{ steps.tag.outputs.version }}" git push origin main diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index a8fb18e..f6a3cf3 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.38" +#define SQLITE_VECTOR_VERSION "0.9.39" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 4ad6b280dbb2b76bb38d9f3efe9a34406c1b80c0 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 14:51:03 +0200 Subject: [PATCH 084/108] feat(packages/node): add platform-specific package generation and @sqliteai/sqlite-vector package initial commit --- .github/workflows/main.yml | 76 ++++- packages/node/.gitignore | 33 ++ packages/node/.npmignore | 23 ++ packages/node/LICENSE.md | 1 + packages/node/README.md | 326 ++++++++++++++++++++ packages/node/generate-platform-packages.js | 255 +++++++++++++++ packages/node/package.json | 71 +++++ packages/node/src/index.test.ts | 109 +++++++ packages/node/src/index.ts | 133 ++++++++ packages/node/src/platform.ts | 148 +++++++++ packages/node/tsconfig.json | 25 ++ packages/node/tsup.config.ts | 14 + src/sqlite-vector.h | 2 +- 13 files changed, 1214 insertions(+), 2 deletions(-) create mode 100644 packages/node/.gitignore create mode 100644 packages/node/.npmignore create mode 120000 packages/node/LICENSE.md create mode 100644 packages/node/README.md create mode 100755 packages/node/generate-platform-packages.js create mode 100644 packages/node/package.json create mode 100644 packages/node/src/index.test.ts create mode 100644 packages/node/src/index.ts create mode 100644 packages/node/src/platform.ts create mode 100644 packages/node/tsconfig.json create mode 100644 packages/node/tsup.config.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b13de0a..a059ed5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,6 +5,7 @@ on: permissions: contents: write + # id-token: write # TODO: Enable after first release for npm provenance with OIDC jobs: build: @@ -321,13 +322,86 @@ jobs: if: steps.tag.outputs.version != '' run: cd packages/android && ./gradlew publishAggregationToCentralPortal -PSIGNING_KEY="${{ secrets.SIGNING_KEY }}" -PSIGNING_PASSWORD="${{ secrets.SIGNING_PASSWORD }}" -PSONATYPE_USERNAME="${{ secrets.MAVEN_CENTRAL_USERNAME }}" -PSONATYPE_PASSWORD="${{ secrets.MAVEN_CENTRAL_TOKEN }}" -PVERSION="${{ steps.tag.outputs.version }}" -PAAR_PATH="../../artifacts/vector-android-aar/vector.aar" + - uses: actions/setup-node@v4 + if: steps.tag.outputs.version != '' + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: build and publish npm packages + if: steps.tag.outputs.version != '' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + cd packages/node + + # Generate platform packages + echo "Generating platform packages..." + node generate-platform-packages.js "${{ steps.tag.outputs.version }}" "../../artifacts" "./platform-packages" + echo "✓ Generated 7 platform packages" + ls -la platform-packages/ + + # Build main package + echo "Building main package..." + npm install + npm run build + npm test + echo "✓ Main package built and tested" + + # Publish platform packages + echo "Publishing platform packages to npm..." + cd platform-packages + for platform_dir in */; do + platform_name=$(basename "$platform_dir") + echo " Publishing @sqliteai/sqlite-vector-${platform_name}..." + cd "$platform_dir" + npm publish --access public + # TODO: Add --provenance flag after switching to OIDC (requires package to exist first) + cd .. + echo " ✓ Published @sqliteai/sqlite-vector-${platform_name}" + done + cd .. + + # Publish main package + echo "Publishing main package to npm..." + npm publish --access public + # TODO: Add --provenance flag after switching to OIDC (requires package to exist first) + echo "✓ Published @sqliteai/sqlite-vector@${{ steps.tag.outputs.version }}" + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ Successfully published 8 packages to npm" + echo " Main: @sqliteai/sqlite-vector@${{ steps.tag.outputs.version }}" + echo " Platform packages: 7" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + - uses: softprops/action-gh-release@v2.2.1 if: steps.tag.outputs.version != '' with: body: | + # Node.js/npm Package + + **Main package**: [@sqliteai/sqlite-vector](https://www.npmjs.com/package/@sqliteai/sqlite-vector) + + ```bash + npm install @sqliteai/sqlite-vector + ``` + + Platform-specific packages are installed automatically based on your OS. + + --- + # WASM Releases + **WASM repository**: [sqlite-wasm](https://github.com/sqliteai/sqlite-wasm) - **WASM package** is available on npm: [@sqliteai/sqlite-wasm](https://www.npmjs.com/package/@sqliteai/sqlite-wasm) + **WASM package**: [@sqliteai/sqlite-wasm](https://www.npmjs.com/package/@sqliteai/sqlite-wasm) + + ```bash + npm install @sqliteai/sqlite-wasm + ``` + + --- + generate_release_notes: true tag_name: ${{ steps.tag.outputs.version }} files: vector-*-${{ steps.tag.outputs.version }}.* diff --git a/packages/node/.gitignore b/packages/node/.gitignore new file mode 100644 index 0000000..8f2a530 --- /dev/null +++ b/packages/node/.gitignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules/ + +# Build outputs +dist/ +*.tsbuildinfo + +# Test coverage +coverage/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# OS files +.DS_Store +Thumbs.db + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Generated platform packages (created dynamically in GitHub Actions) +platform-packages/ + +# Test outputs +test-artifacts/ +test-output/ +test-platform-packages/ diff --git a/packages/node/.npmignore b/packages/node/.npmignore new file mode 100644 index 0000000..cfa4c54 --- /dev/null +++ b/packages/node/.npmignore @@ -0,0 +1,23 @@ +# Development and build files +src/ +*.test.ts +*.test.js +tsconfig.json +tsup.config.ts + +# Documentation (not needed in published package) +IMPLEMENTATION.md +WORKFLOW_STEPS.md +README.md + +# Scripts (only for repo/CI) +generate-platform-packages.js + +# Development files +node_modules/ +coverage/ +*.log + +# Git +.git/ +.gitignore diff --git a/packages/node/LICENSE.md b/packages/node/LICENSE.md new file mode 120000 index 0000000..f0608a6 --- /dev/null +++ b/packages/node/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/packages/node/README.md b/packages/node/README.md new file mode 100644 index 0000000..35049a7 --- /dev/null +++ b/packages/node/README.md @@ -0,0 +1,326 @@ +# @sqliteai/sqlite-vector + +[![npm version](https://badge.fury.io/js/@sqliteai%2Fsqlite-vector.svg)](https://www.npmjs.com/package/@sqliteai/sqlite-vector) +[![License](https://img.shields.io/badge/license-Elastic%202.0-blue.svg)](../../../LICENSE.md) + +> SQLite Vector extension for Node.js - Cross-platform vector embeddings and similarity search + +**SQLite Vector** brings powerful vector search capabilities to SQLite in Node.js. Perfect for Edge AI, semantic search, RAG applications, and similarity matching - all running locally without external dependencies. + +## Features + +- ✅ **Cross-platform** - Works on macOS, Linux (glibc/musl), and Windows +- ✅ **Zero configuration** - Automatically detects and loads the correct binary for your platform +- ✅ **TypeScript native** - Full type definitions included +- ✅ **Modern ESM + CJS** - Works with both ES modules and CommonJS +- ✅ **Small footprint** - Only downloads binaries for your platform (~30MB) +- ✅ **Offline-ready** - No external services required + +## Installation + +```bash +npm install @sqliteai/sqlite-vector +``` + +The package automatically downloads the correct native extension for your platform during installation. + +### Supported Platforms + +| Platform | Architecture | Package | +|----------|-------------|---------| +| macOS | ARM64 (Apple Silicon) | `@sqliteai/sqlite-vector-darwin-arm64` | +| macOS | x64 (Intel) | `@sqliteai/sqlite-vector-darwin-x64` | +| Linux | ARM64 (glibc) | `@sqliteai/sqlite-vector-linux-arm64` | +| Linux | ARM64 (musl/Alpine) | `@sqliteai/sqlite-vector-linux-arm64-musl` | +| Linux | x64 (glibc) | `@sqliteai/sqlite-vector-linux-x64` | +| Linux | x64 (musl/Alpine) | `@sqliteai/sqlite-vector-linux-x64-musl` | +| Windows | x64 | `@sqliteai/sqlite-vector-win32-x64` | + +## Usage + +### Basic Usage + +```typescript +import { getExtensionPath } from '@sqliteai/sqlite-vector'; +import Database from 'better-sqlite3'; + +// Get the path to the vector extension +const extensionPath = getExtensionPath(); + +// Load it into your SQLite database +const db = new Database(':memory:'); +db.loadExtension(extensionPath); + +// Use vector functions +db.exec(` + CREATE TABLE embeddings ( + id INTEGER PRIMARY KEY, + vector BLOB, + text TEXT + ); +`); + +// Initialize vector search +db.prepare("SELECT vector_init('embeddings', 'vector', 'type=FLOAT32,dimension=384')").run(); + +// Insert vectors (using your embedding model) +const embedding = new Float32Array(384); +// ... fill embedding with your model's output + +db.prepare('INSERT INTO embeddings (vector, text) VALUES (?, ?)').run( + Buffer.from(embedding.buffer), + 'Sample text' +); + +// Perform similarity search +const query = new Float32Array(384); // Your query embedding +const results = db.prepare(` + SELECT e.id, e.text, v.distance + FROM embeddings AS e + JOIN vector_quantize_scan('embeddings', 'vector', ?, 10) AS v + ON e.id = v.rowid + ORDER BY v.distance ASC +`).all(Buffer.from(query.buffer)); + +console.log(results); +``` + +### CommonJS + +```javascript +const { getExtensionPath } = require('@sqliteai/sqlite-vector'); +const Database = require('better-sqlite3'); + +const db = new Database(':memory:'); +db.loadExtension(getExtensionPath()); + +// Ready to use +const version = db.prepare('SELECT vector_version()').pluck().get(); +console.log('Vector extension version:', version); +``` + +### Get Extension Information + +```typescript +import { getExtensionInfo } from '@sqliteai/sqlite-vector'; + +const info = getExtensionInfo(); +console.log(info); +// { +// platform: 'darwin-arm64', +// packageName: '@sqliteai/sqlite-vector-darwin-arm64', +// binaryName: 'vector.dylib', +// path: '/path/to/node_modules/@sqliteai/sqlite-vector-darwin-arm64/vector.dylib' +// } +``` + +## Examples + +For complete, runnable examples, see the [sqlite-extensions-guide](https://github.com/sqliteai/sqlite-extensions-guide/tree/main/examples/node): + +- **[basic-usage.js](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/examples/node/basic-usage.js)** - Generic extension loading for any @sqliteai extension +- **[semantic-search.js](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/examples/node/semantic-search.js)** - Complete semantic search with OpenAI or on-device embeddings +- **[with-multiple-extensions.js](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/examples/node/with-multiple-extensions.js)** - Loading multiple extensions together (vector + sync + AI + js) + +These examples are generic and work with all SQLite extensions: `sqlite-vector`, `sqlite-sync`, `sqlite-js`, and `sqlite-ai`. + +## API Reference + +### `getExtensionPath(): string` + +Returns the absolute path to the SQLite Vector extension binary for the current platform. + +**Returns:** `string` - Absolute path to the extension file (`.so`, `.dylib`, or `.dll`) + +**Throws:** `ExtensionNotFoundError` - If the extension binary cannot be found for the current platform + +**Example:** +```typescript +import { getExtensionPath } from '@sqliteai/sqlite-vector'; + +const path = getExtensionPath(); +// => '/path/to/node_modules/@sqliteai/sqlite-vector-darwin-arm64/vector.dylib' +``` + +--- + +### `getExtensionInfo(): ExtensionInfo` + +Returns detailed information about the extension for the current platform. + +**Returns:** `ExtensionInfo` object with the following properties: +- `platform: Platform` - Current platform identifier (e.g., `'darwin-arm64'`) +- `packageName: string` - Name of the platform-specific npm package +- `binaryName: string` - Filename of the binary (e.g., `'vector.dylib'`) +- `path: string` - Full path to the extension binary + +**Throws:** `ExtensionNotFoundError` - If the extension binary cannot be found + +**Example:** +```typescript +import { getExtensionInfo } from '@sqliteai/sqlite-vector'; + +const info = getExtensionInfo(); +console.log(`Running on ${info.platform}`); +console.log(`Extension path: ${info.path}`); +``` + +--- + +### `getCurrentPlatform(): Platform` + +Returns the current platform identifier. + +**Returns:** `Platform` - One of: +- `'darwin-arm64'` - macOS ARM64 +- `'darwin-x64'` - macOS x64 +- `'linux-arm64'` - Linux ARM64 (glibc) +- `'linux-arm64-musl'` - Linux ARM64 (musl) +- `'linux-x64'` - Linux x64 (glibc) +- `'linux-x64-musl'` - Linux x64 (musl) +- `'win32-x64'` - Windows x64 + +**Throws:** `Error` - If the platform is unsupported + +--- + +### `isMusl(): boolean` + +Detects if the system uses musl libc (Alpine Linux, etc.). + +**Returns:** `boolean` - `true` if musl is detected, `false` otherwise + +--- + +### `class ExtensionNotFoundError extends Error` + +Error thrown when the SQLite Vector extension cannot be found for the current platform. + +## Vector Search Guide + +For detailed information on how to use the vector search features, see the [main documentation](https://github.com/sqliteai/sqlite-vector/blob/main/README.md). + +### Quick Reference + +```sql +-- Initialize vector column +SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384'); + +-- Quantize vectors for faster search +SELECT vector_quantize('table', 'column'); + +-- Preload into memory for 4-5x speedup +SELECT vector_quantize_preload('table', 'column'); + +-- Search for similar vectors +SELECT * FROM table AS t +JOIN vector_quantize_scan('table', 'column', , ) AS v +ON t.rowid = v.rowid +ORDER BY v.distance; +``` + +### Distance Metrics + +Specify the distance metric during initialization: + +```sql +-- L2 (Euclidean) - default +SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=L2'); + +-- Cosine similarity +SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=COSINE'); + +-- Dot product +SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=DOT'); + +-- L1 (Manhattan) +SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=L1'); +``` + +## Troubleshooting + +### Extension Not Found + +If you get `ExtensionNotFoundError`, try: + +```bash +# Force reinstall dependencies +npm install --force + +# Or manually install the platform package +npm install @sqliteai/sqlite-vector-darwin-arm64 # Replace with your platform +``` + +### Platform Not Detected + +The package should automatically detect your platform. If detection fails, please [open an issue](https://github.com/sqliteai/sqlite-vector/issues) with: +- Your OS and architecture +- Node.js version +- Output of `node -p "process.platform + '-' + process.arch"` + +### Alpine Linux / musl + +On Alpine Linux or other musl-based systems, the package automatically detects musl and installs the correct variant. If you encounter issues: + +```bash +# Verify musl detection +node -e "console.log(require('@sqliteai/sqlite-vector').isMusl())" + +# Should print: true +``` + +## Development + +### Building from Source + +```bash +# Clone the repository +git clone https://github.com/sqliteai/sqlite-vector.git +cd sqlite-vector/packages/node + +# Install dependencies +npm install + +# Build TypeScript +npm run build + +# Run tests +npm test +``` + +### Project Structure + +``` +packages/node/ +├── src/ +│ ├── index.ts # Main entry point +│ ├── platform.ts # Platform detection logic +│ └── index.test.ts # Test suite +├── dist/ # Compiled JavaScript (generated) +├── generate-platform-packages.js # Platform package generator +├── package.json +├── tsconfig.json +└── README.md +``` + +## Related Projects + +- **[SQLite-AI](https://github.com/sqliteai/sqlite-ai)** - On-device AI inference and embedding generation +- **[SQLite-Sync](https://github.com/sqliteai/sqlite-sync)** - Sync on-device databases with the cloud +- **[SQLite-JS](https://github.com/sqliteai/sqlite-js)** - Define SQLite functions in JavaScript + +## License + +This project is licensed under the [Elastic License 2.0](../../../LICENSE.md). + +For production or managed service use, please [contact SQLite Cloud, Inc](mailto:info@sqlitecloud.io) for a commercial license. + +## Contributing + +Contributions are welcome! Please see the [main repository](https://github.com/sqliteai/sqlite-vector) for contribution guidelines. + +## Support + +- 📖 [Documentation](https://github.com/sqliteai/sqlite-vector/blob/main/API.md) +- 🐛 [Report Issues](https://github.com/sqliteai/sqlite-vector/issues) +- 💬 [Discussions](https://github.com/sqliteai/sqlite-vector/discussions) diff --git a/packages/node/generate-platform-packages.js b/packages/node/generate-platform-packages.js new file mode 100755 index 0000000..84cf421 --- /dev/null +++ b/packages/node/generate-platform-packages.js @@ -0,0 +1,255 @@ +#!/usr/bin/env node + +/** + * Generates platform-specific packages dynamically + * + * This script creates npm packages for each platform from templates, + * eliminating the need to maintain nearly-identical files in the repo. + * + * Usage: + * node generate-platform-packages.js + * + * Example: + * node generate-platform-packages.js 0.9.40 ./artifacts ./platform-packages + */ + +const fs = require('fs'); +const path = require('path'); + +// Platform configuration +const PLATFORMS = [ + { + name: 'darwin-arm64', + os: ['darwin'], + cpu: ['arm64'], + description: 'SQLite Vector extension for macOS ARM64 (Apple Silicon)', + binaryName: 'vector.dylib', + artifactFolder: 'vector-macos-arm64', + }, + { + name: 'darwin-x64', + os: ['darwin'], + cpu: ['x64'], + description: 'SQLite Vector extension for macOS x64 (Intel)', + binaryName: 'vector.dylib', + artifactFolder: 'vector-macos-x64', + }, + { + name: 'linux-arm64', + os: ['linux'], + cpu: ['arm64'], + description: 'SQLite Vector extension for Linux ARM64 (glibc)', + binaryName: 'vector.so', + artifactFolder: 'vector-linux-arm64', + }, + { + name: 'linux-arm64-musl', + os: ['linux'], + cpu: ['arm64'], + description: 'SQLite Vector extension for Linux ARM64 (musl)', + binaryName: 'vector.so', + artifactFolder: 'vector-linux-musl-arm64', + }, + { + name: 'linux-x64', + os: ['linux'], + cpu: ['x64'], + description: 'SQLite Vector extension for Linux x64 (glibc)', + binaryName: 'vector.so', + artifactFolder: 'vector-linux-x64', + }, + { + name: 'linux-x64-musl', + os: ['linux'], + cpu: ['x64'], + description: 'SQLite Vector extension for Linux x64 (musl)', + binaryName: 'vector.so', + artifactFolder: 'vector-linux-musl-x64', + }, + { + name: 'win32-x64', + os: ['win32'], + cpu: ['x64'], + description: 'SQLite Vector extension for Windows x64', + binaryName: 'vector.dll', + artifactFolder: 'vector-windows-x64', + }, +]; + +/** + * Generate package.json for a platform + */ +function generatePackageJson(platform, version) { + return { + name: `@sqliteai/sqlite-vector-${platform.name}`, + version: version, + description: platform.description, + main: 'index.js', + os: platform.os, + cpu: platform.cpu, + files: [ + platform.binaryName, + 'index.js', + 'README.md', + 'LICENSE.md', + ], + keywords: [ + 'sqlite', + 'vector', + ...platform.name.split('-'), + ], + author: 'Gioele Cantoni (gioele@sqlitecloud.io)', + license: 'SEE LICENSE IN LICENSE.md', + repository: { + type: 'git', + url: 'https://github.com/sqliteai/sqlite-vector.git', + directory: 'packages/node', + }, + engines: { + node: '>=16.0.0', + }, + }; +} + +/** + * Generate index.js for a platform + */ +function generateIndexJs(platform) { + return `const { join } = require('path'); + +module.exports = { + path: join(__dirname, '${platform.binaryName}') +}; +`; +} + +/** + * Generate README.md for a platform + */ +function generateReadme(platform, version) { + return `# @sqliteai/sqlite-vector-${platform.name} + +${platform.description} + +**Version:** ${version} + +This is a platform-specific package for [@sqliteai/sqlite-vector](https://www.npmjs.com/package/@sqliteai/sqlite-vector). + +It is installed automatically as an optional dependency and should not be installed directly. + +## Installation + +Install the main package instead: + +\`\`\`bash +npm install @sqliteai/sqlite-vector +\`\`\` + +## Platform + +- **OS:** ${platform.os.join(', ')} +- **CPU:** ${platform.cpu.join(', ')} +- **Binary:** ${platform.binaryName} + +## License + +See [LICENSE.md](../../../LICENSE.md) in the root directory. +`; +} + +/** + * Main function + */ +function main() { + const args = process.argv.slice(2); + + if (args.length < 3) { + console.error('Usage: node generate-platform-packages.js '); + console.error('Example: node generate-platform-packages.js 0.9.40 ./artifacts ./platform-packages'); + process.exit(1); + } + + const [version, artifactsDir, outputDir] = args; + + // Find LICENSE.md (should be in repo root) + const licensePath = path.resolve(__dirname, '../../LICENSE.md'); + if (!fs.existsSync(licensePath)) { + console.error(`Error: LICENSE.md not found at ${licensePath}`); + process.exit(1); + } + + // Validate version format + if (!/^\d+\.\d+\.\d+$/.test(version)) { + console.error(`Error: Invalid version format: ${version}`); + console.error('Version must be in semver format (e.g., 0.9.40)'); + process.exit(1); + } + + console.log(`Generating platform packages version ${version}...\n`); + + // Create output directory + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + let successCount = 0; + let errorCount = 0; + + // Generate each platform package + for (const platform of PLATFORMS) { + const platformDir = path.join(outputDir, platform.name); + const artifactPath = path.join(artifactsDir, platform.artifactFolder, platform.binaryName); + + try { + // Create platform directory + fs.mkdirSync(platformDir, { recursive: true }); + + // Generate package.json + const packageJson = generatePackageJson(platform, version); + fs.writeFileSync( + path.join(platformDir, 'package.json'), + JSON.stringify(packageJson, null, 2) + '\n' + ); + + // Generate index.js + const indexJs = generateIndexJs(platform); + fs.writeFileSync(path.join(platformDir, 'index.js'), indexJs); + + // Generate README.md + const readme = generateReadme(platform, version); + fs.writeFileSync(path.join(platformDir, 'README.md'), readme); + + // Copy LICENSE.md + fs.copyFileSync(licensePath, path.join(platformDir, 'LICENSE.md')); + + // Copy binary if it exists + if (fs.existsSync(artifactPath)) { + fs.copyFileSync(artifactPath, path.join(platformDir, platform.binaryName)); + console.log(`✓ ${platform.name} (with binary)`); + } else { + console.log(`✓ ${platform.name} (no binary found at ${artifactPath})`); + } + + successCount++; + } catch (error) { + console.error(`✗ ${platform.name}: ${error.message}`); + errorCount++; + } + } + + console.log(`\nGenerated ${successCount} platform package(s)`); + + if (errorCount > 0) { + console.error(`Failed to generate ${errorCount} package(s)`); + process.exit(1); + } + + console.log('Done!'); +} + +// Run +if (require.main === module) { + main(); +} + +module.exports = { PLATFORMS, generatePackageJson, generateIndexJs, generateReadme }; diff --git a/packages/node/package.json b/packages/node/package.json new file mode 100644 index 0000000..241a8b0 --- /dev/null +++ b/packages/node/package.json @@ -0,0 +1,71 @@ +{ + "name": "@sqliteai/sqlite-vector", + "version": "0.9.40", + "description": "SQLite vector search extension for Node.js - Cross-platform vector embeddings and similarity search", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "files": [ + "dist", + "README.md", + "LICENSE.md" + ], + "scripts": { + "build": "tsup", + "prepublishOnly": "npm run build", + "test": "node --test dist/index.test.js", + "typecheck": "tsc --noEmit", + "generate-platforms": "node generate-platform-packages.js" + }, + "keywords": [ + "sqlite", + "vector", + "embedding", + "ai", + "machine-learning", + "similarity-search", + "semantic-search", + "vector-database", + "sqlite-extension" + ], + "author": "Gioele Cantoni (gioele@sqlitecloud.io)", + "license": "SEE LICENSE IN LICENSE.md", + "repository": { + "type": "git", + "url": "https://github.com/sqliteai/sqlite-vector.git", + "directory": "packages/node" + }, + "homepage": "https://github.com/sqliteai/sqlite-vector#readme", + "bugs": { + "url": "https://github.com/sqliteai/sqlite-vector/issues" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "@sqliteai/sqlite-vector-darwin-arm64": "0.9.40", + "@sqliteai/sqlite-vector-darwin-x64": "0.9.40", + "@sqliteai/sqlite-vector-linux-arm64": "0.9.40", + "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.40", + "@sqliteai/sqlite-vector-linux-x64": "0.9.40", + "@sqliteai/sqlite-vector-linux-x64-musl": "0.9.40", + "@sqliteai/sqlite-vector-win32-x64": "0.9.40" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsup": "^8.0.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/node/src/index.test.ts b/packages/node/src/index.test.ts new file mode 100644 index 0000000..0e7f11d --- /dev/null +++ b/packages/node/src/index.test.ts @@ -0,0 +1,109 @@ +import { test, describe } from 'node:test'; +import assert from 'node:assert'; +import { + getCurrentPlatform, + getPlatformPackageName, + getBinaryName, + isMusl, + getExtensionPath, + getExtensionInfo, + ExtensionNotFoundError +} from './index.js'; + +describe('Platform Detection', () => { + test('getCurrentPlatform() returns a valid platform', () => { + const platform = getCurrentPlatform(); + const validPlatforms = [ + 'darwin-arm64', + 'darwin-x64', + 'linux-arm64', + 'linux-arm64-musl', + 'linux-x64', + 'linux-x64-musl', + 'win32-x64', + ]; + + assert.ok( + validPlatforms.includes(platform), + `Platform ${platform} should be one of: ${validPlatforms.join(', ')}` + ); + }); + + test('getPlatformPackageName() returns correct package name format', () => { + const packageName = getPlatformPackageName(); + + assert.ok( + packageName.startsWith('@sqliteai/sqlite-vector-'), + 'Package name should start with @sqliteai/sqlite-vector-' + ); + + assert.match( + packageName, + /^@sqliteai\/sqlite-vector-(darwin|linux|win32)-(arm64|x64)(-musl)?$/, + 'Package name should match expected format' + ); + }); + + test('getBinaryName() returns correct extension', () => { + const binaryName = getBinaryName(); + + assert.match( + binaryName, + /^vector\.(dylib|so|dll)$/, + 'Binary name should be vector.dylib, vector.so, or vector.dll' + ); + }); + + test('isMusl() returns a boolean', () => { + const result = isMusl(); + assert.strictEqual(typeof result, 'boolean'); + }); +}); + +describe('Extension Path Resolution', () => { + test('getExtensionPath() returns a string or throws', () => { + try { + const path = getExtensionPath(); + assert.strictEqual(typeof path, 'string'); + assert.ok(path.length > 0, 'Path should not be empty'); + } catch (error) { + // If it throws, it should be ExtensionNotFoundError + assert.ok( + error instanceof ExtensionNotFoundError, + 'Should throw ExtensionNotFoundError if extension not found' + ); + } + }); + + test('getExtensionInfo() returns complete info object', () => { + try { + const info = getExtensionInfo(); + + assert.ok(info.platform, 'Should have platform'); + assert.ok(info.packageName, 'Should have packageName'); + assert.ok(info.binaryName, 'Should have binaryName'); + assert.ok(info.path, 'Should have path'); + + assert.strictEqual(typeof info.platform, 'string'); + assert.strictEqual(typeof info.packageName, 'string'); + assert.strictEqual(typeof info.binaryName, 'string'); + assert.strictEqual(typeof info.path, 'string'); + } catch (error) { + // If it throws, it should be ExtensionNotFoundError + assert.ok( + error instanceof ExtensionNotFoundError, + 'Should throw ExtensionNotFoundError if extension not found' + ); + } + }); +}); + +describe('Error Handling', () => { + test('ExtensionNotFoundError has correct properties', () => { + const error = new ExtensionNotFoundError('Test message'); + + assert.ok(error instanceof Error); + assert.strictEqual(error.name, 'ExtensionNotFoundError'); + assert.strictEqual(error.message, 'Test message'); + }); +}); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts new file mode 100644 index 0000000..d4246e8 --- /dev/null +++ b/packages/node/src/index.ts @@ -0,0 +1,133 @@ +import { resolve, dirname, join } from 'node:path'; +import { existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { + getCurrentPlatform, + getPlatformPackageName, + getBinaryName, + type Platform +} from './platform.js'; + +/** + * Error thrown when the SQLite Vector extension cannot be found + */ +export class ExtensionNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'ExtensionNotFoundError'; + } +} + +/** + * Attempts to load the platform-specific package + * @returns The path to the extension binary, or null if not found + */ +function tryLoadPlatformPackage(): string | null { + try { + const packageName = getPlatformPackageName(); + + // Try to dynamically import the platform package + // This works in both CommonJS and ESM + const platformPackage = require(packageName); + + if (platformPackage?.path && typeof platformPackage.path === 'string') { + if (existsSync(platformPackage.path)) { + return platformPackage.path; + } + } + } catch (error) { + // Platform package not installed or failed to load + // This is expected when optionalDependencies fail + } + + return null; +} + +/** + * Gets the absolute path to the SQLite Vector extension binary for the current platform + * + * @returns Absolute path to the extension binary (.so, .dylib, or .dll) + * @throws {ExtensionNotFoundError} If the extension binary cannot be found + * + * @example + * ```typescript + * import { getExtensionPath } from '@sqliteai/sqlite-vector'; + * + * const extensionPath = getExtensionPath(); + * // On macOS ARM64: /path/to/node_modules/@sqliteai/sqlite-vector-darwin-arm64/vector.dylib + * ``` + */ +export function getExtensionPath(): string { + // Try to load from platform-specific package + const platformPath = tryLoadPlatformPackage(); + if (platformPath) { + return resolve(platformPath); + } + + // If we reach here, the platform package wasn't installed + const currentPlatform = getCurrentPlatform(); + const packageName = getPlatformPackageName(); + + throw new ExtensionNotFoundError( + `SQLite Vector extension not found for platform: ${currentPlatform}\n\n` + + `The platform-specific package "${packageName}" is not installed.\n` + + `This usually happens when:\n` + + ` 1. Your platform is not supported\n` + + ` 2. npm failed to install optional dependencies\n` + + ` 3. You're installing with --no-optional flag\n\n` + + `Try running: npm install --force` + ); +} + +/** + * Information about the current platform and extension + */ +export interface ExtensionInfo { + /** Current platform identifier (e.g., 'darwin-arm64') */ + platform: Platform; + /** Name of the platform-specific npm package */ + packageName: string; + /** Filename of the binary (e.g., 'vector.dylib') */ + binaryName: string; + /** Full path to the extension binary */ + path: string; +} + +/** + * Gets detailed information about the SQLite Vector extension + * + * @returns Extension information object + * + * @example + * ```typescript + * import { getExtensionInfo } from '@sqliteai/sqlite-vector'; + * + * const info = getExtensionInfo(); + * console.log(info); + * // { + * // platform: 'darwin-arm64', + * // packageName: '@sqliteai/sqlite-vector-darwin-arm64', + * // binaryName: 'vector.dylib', + * // path: '/path/to/vector.dylib' + * // } + * ``` + */ +export function getExtensionInfo(): ExtensionInfo { + return { + platform: getCurrentPlatform(), + packageName: getPlatformPackageName(), + binaryName: getBinaryName(), + path: getExtensionPath(), + }; +} + +// Default export for CommonJS compatibility +export default { + getExtensionPath, + getExtensionInfo, + ExtensionNotFoundError, +}; + +// Re-export platform utilities +export { getCurrentPlatform, getPlatformPackageName, getBinaryName, isMusl } from './platform.js'; +export type { Platform } from './platform.js'; diff --git a/packages/node/src/platform.ts b/packages/node/src/platform.ts new file mode 100644 index 0000000..47ace1d --- /dev/null +++ b/packages/node/src/platform.ts @@ -0,0 +1,148 @@ +import { platform, arch } from 'node:os'; +import { existsSync, readFileSync } from 'node:fs'; +import { execSync } from 'node:child_process'; + +/** + * Supported platform identifiers + */ +export type Platform = + | 'darwin-arm64' + | 'darwin-x64' + | 'linux-arm64' + | 'linux-arm64-musl' + | 'linux-x64' + | 'linux-x64-musl' + | 'win32-x64'; + +/** + * Binary extension for each platform + */ +export const PLATFORM_EXTENSIONS: Record = { + darwin: '.dylib', + linux: '.so', + win32: '.dll', +} as const; + +/** + * Detects if the system uses musl libc (Alpine Linux, etc.) + * Uses multiple detection strategies for reliability + */ +export function isMusl(): boolean { + // Only relevant for Linux + if (platform() !== 'linux') { + return false; + } + + // Strategy 1: Check for musl-specific files + const muslFiles = [ + '/lib/ld-musl-x86_64.so.1', + '/lib/ld-musl-aarch64.so.1', + '/lib/ld-musl-armhf.so.1', + ]; + + for (const file of muslFiles) { + if (existsSync(file)) { + return true; + } + } + + // Strategy 2: Check ldd version output + try { + const lddVersion = execSync('ldd --version 2>&1', { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + + if (lddVersion.includes('musl')) { + return true; + } + } catch { + // ldd command failed, continue to next strategy + } + + // Strategy 3: Check /etc/os-release for Alpine + try { + if (existsSync('/etc/os-release')) { + const osRelease = readFileSync('/etc/os-release', 'utf-8'); + if (osRelease.includes('Alpine') || osRelease.includes('musl')) { + return true; + } + } + } catch { + // File read failed, continue to next strategy + } + + // Strategy 4: Check process.report.getReport() for musl + try { + const report = (process as any).report?.getReport?.(); + if (report?.header?.glibcVersionRuntime === '') { + // Empty glibc version often indicates musl + return true; + } + } catch { + // Report not available + } + + return false; +} + +/** + * Gets the current platform identifier + * @throws {Error} If the platform is unsupported + */ +export function getCurrentPlatform(): Platform { + const platformName = platform(); + const archName = arch(); + + // macOS + if (platformName === 'darwin') { + if (archName === 'arm64') return 'darwin-arm64'; + if (archName === 'x64') return 'darwin-x64'; + } + + // Linux (with musl detection) + if (platformName === 'linux') { + const muslSuffix = isMusl() ? '-musl' : ''; + + if (archName === 'arm64') { + return `linux-arm64${muslSuffix}` as Platform; + } + if (archName === 'x64') { + return `linux-x64${muslSuffix}` as Platform; + } + } + + // Windows + if (platformName === 'win32') { + if (archName === 'x64') return 'win32-x64'; + } + + // Unsupported platform + throw new Error( + `Unsupported platform: ${platformName}-${archName}. ` + + `Supported platforms: darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-x64 ` + + `(with glibc or musl support for Linux)` + ); +} + +/** + * Gets the package name for the current platform + */ +export function getPlatformPackageName(): string { + const currentPlatform = getCurrentPlatform(); + return `@sqliteai/sqlite-vector-${currentPlatform}`; +} + +/** + * Gets the binary filename for the current platform + */ +export function getBinaryName(): string { + const platformName = platform(); + const extension = PLATFORM_EXTENSIONS[platformName]; + + if (!extension) { + throw new Error(`Unknown platform: ${platformName}`); + } + + return `vector${extension}`; +} diff --git a/packages/node/tsconfig.json b/packages/node/tsconfig.json new file mode 100644 index 0000000..73a9474 --- /dev/null +++ b/packages/node/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020"], + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/node/tsup.config.ts b/packages/node/tsup.config.ts new file mode 100644 index 0000000..ba19007 --- /dev/null +++ b/packages/node/tsup.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + dts: true, + splitting: false, + sourcemap: true, + clean: true, + treeshake: true, + minify: false, + target: 'node16', + outDir: 'dist', +}); diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index f6a3cf3..cf6a00b 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.39" +#define SQLITE_VECTOR_VERSION "0.9.40" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From c111bf5eb0b30c18770656af00997df87f6d06fe Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 15:08:50 +0200 Subject: [PATCH 085/108] fix(packages/node): adjust x86_64 platform package names --- packages/node/README.md | 16 ++++----- packages/node/generate-platform-packages.js | 38 ++++++++++----------- packages/node/package.json | 16 ++++----- packages/node/src/index.test.ts | 10 +++--- packages/node/src/platform.ts | 18 +++++----- src/sqlite-vector.h | 2 +- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/packages/node/README.md b/packages/node/README.md index 35049a7..7fbb082 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -29,12 +29,12 @@ The package automatically downloads the correct native extension for your platfo | Platform | Architecture | Package | |----------|-------------|---------| | macOS | ARM64 (Apple Silicon) | `@sqliteai/sqlite-vector-darwin-arm64` | -| macOS | x64 (Intel) | `@sqliteai/sqlite-vector-darwin-x64` | +| macOS | x86_64 (Intel) | `@sqliteai/sqlite-vector-darwin-x86_64` | | Linux | ARM64 (glibc) | `@sqliteai/sqlite-vector-linux-arm64` | | Linux | ARM64 (musl/Alpine) | `@sqliteai/sqlite-vector-linux-arm64-musl` | -| Linux | x64 (glibc) | `@sqliteai/sqlite-vector-linux-x64` | -| Linux | x64 (musl/Alpine) | `@sqliteai/sqlite-vector-linux-x64-musl` | -| Windows | x64 | `@sqliteai/sqlite-vector-win32-x64` | +| Linux | x86_64 (glibc) | `@sqliteai/sqlite-vector-linux-x86_64` | +| Linux | x86_64 (musl/Alpine) | `@sqliteai/sqlite-vector-linux-x86_64-musl` | +| Windows | x86_64 | `@sqliteai/sqlite-vector-win32-x86_64` | ## Usage @@ -173,12 +173,12 @@ Returns the current platform identifier. **Returns:** `Platform` - One of: - `'darwin-arm64'` - macOS ARM64 -- `'darwin-x64'` - macOS x64 +- `'darwin-x86_64'` - macOS x86_64 - `'linux-arm64'` - Linux ARM64 (glibc) - `'linux-arm64-musl'` - Linux ARM64 (musl) -- `'linux-x64'` - Linux x64 (glibc) -- `'linux-x64-musl'` - Linux x64 (musl) -- `'win32-x64'` - Windows x64 +- `'linux-x86_64'` - Linux x86_64 (glibc) +- `'linux-x86_64-musl'` - Linux x86_64 (musl) +- `'win32-x86_64'` - Windows x86_64 **Throws:** `Error` - If the platform is unsupported diff --git a/packages/node/generate-platform-packages.js b/packages/node/generate-platform-packages.js index 84cf421..b94da82 100755 --- a/packages/node/generate-platform-packages.js +++ b/packages/node/generate-platform-packages.js @@ -10,7 +10,7 @@ * node generate-platform-packages.js * * Example: - * node generate-platform-packages.js 0.9.40 ./artifacts ./platform-packages + * node generate-platform-packages.js 0.9.41 ./artifacts ./platform-packages */ const fs = require('fs'); @@ -27,12 +27,12 @@ const PLATFORMS = [ artifactFolder: 'vector-macos-arm64', }, { - name: 'darwin-x64', + name: 'darwin-x86_64', os: ['darwin'], - cpu: ['x64'], - description: 'SQLite Vector extension for macOS x64 (Intel)', + cpu: ['x64', 'ia32'], + description: 'SQLite Vector extension for macOS x86_64 (Intel)', binaryName: 'vector.dylib', - artifactFolder: 'vector-macos-x64', + artifactFolder: 'vector-macos-x86_64', }, { name: 'linux-arm64', @@ -51,28 +51,28 @@ const PLATFORMS = [ artifactFolder: 'vector-linux-musl-arm64', }, { - name: 'linux-x64', + name: 'linux-x86_64', os: ['linux'], - cpu: ['x64'], - description: 'SQLite Vector extension for Linux x64 (glibc)', + cpu: ['x64', 'ia32'], + description: 'SQLite Vector extension for Linux x86_64 (glibc)', binaryName: 'vector.so', - artifactFolder: 'vector-linux-x64', + artifactFolder: 'vector-linux-x86_64', }, { - name: 'linux-x64-musl', + name: 'linux-x86_64-musl', os: ['linux'], - cpu: ['x64'], - description: 'SQLite Vector extension for Linux x64 (musl)', + cpu: ['x64', 'ia32'], + description: 'SQLite Vector extension for Linux x86_64 (musl)', binaryName: 'vector.so', - artifactFolder: 'vector-linux-musl-x64', + artifactFolder: 'vector-linux-musl-x86_64', }, { - name: 'win32-x64', + name: 'win32-x86_64', os: ['win32'], - cpu: ['x64'], - description: 'SQLite Vector extension for Windows x64', + cpu: ['x64', 'ia32'], + description: 'SQLite Vector extension for Windows x86_64', binaryName: 'vector.dll', - artifactFolder: 'vector-windows-x64', + artifactFolder: 'vector-windows-x86_64', }, ]; @@ -165,7 +165,7 @@ function main() { if (args.length < 3) { console.error('Usage: node generate-platform-packages.js '); - console.error('Example: node generate-platform-packages.js 0.9.40 ./artifacts ./platform-packages'); + console.error('Example: node generate-platform-packages.js 0.9.41 ./artifacts ./platform-packages'); process.exit(1); } @@ -181,7 +181,7 @@ function main() { // Validate version format if (!/^\d+\.\d+\.\d+$/.test(version)) { console.error(`Error: Invalid version format: ${version}`); - console.error('Version must be in semver format (e.g., 0.9.40)'); + console.error('Version must be in semver format (e.g., 0.9.41)'); process.exit(1); } diff --git a/packages/node/package.json b/packages/node/package.json index 241a8b0..3a71c7c 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sqliteai/sqlite-vector", - "version": "0.9.40", + "version": "0.9.41", "description": "SQLite vector search extension for Node.js - Cross-platform vector embeddings and similarity search", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -55,13 +55,13 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "@sqliteai/sqlite-vector-darwin-arm64": "0.9.40", - "@sqliteai/sqlite-vector-darwin-x64": "0.9.40", - "@sqliteai/sqlite-vector-linux-arm64": "0.9.40", - "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.40", - "@sqliteai/sqlite-vector-linux-x64": "0.9.40", - "@sqliteai/sqlite-vector-linux-x64-musl": "0.9.40", - "@sqliteai/sqlite-vector-win32-x64": "0.9.40" + "@sqliteai/sqlite-vector-darwin-arm64": "0.9.41", + "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.41", + "@sqliteai/sqlite-vector-linux-arm64": "0.9.41", + "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.41", + "@sqliteai/sqlite-vector-linux-x86_64": "0.9.41", + "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.41", + "@sqliteai/sqlite-vector-win32-x86_64": "0.9.41" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/packages/node/src/index.test.ts b/packages/node/src/index.test.ts index 0e7f11d..32eff74 100644 --- a/packages/node/src/index.test.ts +++ b/packages/node/src/index.test.ts @@ -15,12 +15,12 @@ describe('Platform Detection', () => { const platform = getCurrentPlatform(); const validPlatforms = [ 'darwin-arm64', - 'darwin-x64', + 'darwin-x86_64', 'linux-arm64', 'linux-arm64-musl', - 'linux-x64', - 'linux-x64-musl', - 'win32-x64', + 'linux-x86_64', + 'linux-x86_64-musl', + 'win32-x86_64', ]; assert.ok( @@ -39,7 +39,7 @@ describe('Platform Detection', () => { assert.match( packageName, - /^@sqliteai\/sqlite-vector-(darwin|linux|win32)-(arm64|x64)(-musl)?$/, + /^@sqliteai\/sqlite-vector-(darwin|linux|win32)-(arm64|x86_64)(-musl)?$/, 'Package name should match expected format' ); }); diff --git a/packages/node/src/platform.ts b/packages/node/src/platform.ts index 47ace1d..6785300 100644 --- a/packages/node/src/platform.ts +++ b/packages/node/src/platform.ts @@ -7,12 +7,12 @@ import { execSync } from 'node:child_process'; */ export type Platform = | 'darwin-arm64' - | 'darwin-x64' + | 'darwin-x86_64' | 'linux-arm64' | 'linux-arm64-musl' - | 'linux-x64' - | 'linux-x64-musl' - | 'win32-x64'; + | 'linux-x86_64' + | 'linux-x86_64-musl' + | 'win32-x86_64'; /** * Binary extension for each platform @@ -97,7 +97,7 @@ export function getCurrentPlatform(): Platform { // macOS if (platformName === 'darwin') { if (archName === 'arm64') return 'darwin-arm64'; - if (archName === 'x64') return 'darwin-x64'; + if (archName === 'x86_64') return 'darwin-x86_64'; } // Linux (with musl detection) @@ -107,20 +107,20 @@ export function getCurrentPlatform(): Platform { if (archName === 'arm64') { return `linux-arm64${muslSuffix}` as Platform; } - if (archName === 'x64') { - return `linux-x64${muslSuffix}` as Platform; + if (archName === 'x86_64') { + return `linux-x86_64${muslSuffix}` as Platform; } } // Windows if (platformName === 'win32') { - if (archName === 'x64') return 'win32-x64'; + if (archName === 'x86_64') return 'win32-x86_64'; } // Unsupported platform throw new Error( `Unsupported platform: ${platformName}-${archName}. ` + - `Supported platforms: darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-x64 ` + + `Supported platforms: darwin-arm64, darwin-x86_64, linux-arm64, linux-x86_64, win32-x86_64 ` + `(with glibc or musl support for Linux)` ); } diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index cf6a00b..4074c33 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.40" +#define SQLITE_VECTOR_VERSION "0.9.41" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 8cfa65c2203eee80b72367f14e5b09d9054ba6b6 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 15:16:45 +0200 Subject: [PATCH 086/108] fix(packages/node): DTS Build error --- packages/node/generate-platform-packages.js | 6 +++--- packages/node/package.json | 16 ++++++++-------- packages/node/src/index.ts | 3 +-- src/sqlite-vector.h | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/node/generate-platform-packages.js b/packages/node/generate-platform-packages.js index b94da82..fdb735f 100755 --- a/packages/node/generate-platform-packages.js +++ b/packages/node/generate-platform-packages.js @@ -10,7 +10,7 @@ * node generate-platform-packages.js * * Example: - * node generate-platform-packages.js 0.9.41 ./artifacts ./platform-packages + * node generate-platform-packages.js 0.9.42 ./artifacts ./platform-packages */ const fs = require('fs'); @@ -165,7 +165,7 @@ function main() { if (args.length < 3) { console.error('Usage: node generate-platform-packages.js '); - console.error('Example: node generate-platform-packages.js 0.9.41 ./artifacts ./platform-packages'); + console.error('Example: node generate-platform-packages.js 0.9.42 ./artifacts ./platform-packages'); process.exit(1); } @@ -181,7 +181,7 @@ function main() { // Validate version format if (!/^\d+\.\d+\.\d+$/.test(version)) { console.error(`Error: Invalid version format: ${version}`); - console.error('Version must be in semver format (e.g., 0.9.41)'); + console.error('Version must be in semver format (e.g., 0.9.42)'); process.exit(1); } diff --git a/packages/node/package.json b/packages/node/package.json index 3a71c7c..93088bc 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sqliteai/sqlite-vector", - "version": "0.9.41", + "version": "0.9.42", "description": "SQLite vector search extension for Node.js - Cross-platform vector embeddings and similarity search", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -55,13 +55,13 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "@sqliteai/sqlite-vector-darwin-arm64": "0.9.41", - "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.41", - "@sqliteai/sqlite-vector-linux-arm64": "0.9.41", - "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.41", - "@sqliteai/sqlite-vector-linux-x86_64": "0.9.41", - "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.41", - "@sqliteai/sqlite-vector-win32-x86_64": "0.9.41" + "@sqliteai/sqlite-vector-darwin-arm64": "0.9.42", + "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.42", + "@sqliteai/sqlite-vector-linux-arm64": "0.9.42", + "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.42", + "@sqliteai/sqlite-vector-linux-x86_64": "0.9.42", + "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.42", + "@sqliteai/sqlite-vector-win32-x86_64": "0.9.42" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index d4246e8..05b36dc 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,6 +1,5 @@ -import { resolve, dirname, join } from 'node:path'; +import { resolve } from 'node:path'; import { existsSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; import { getCurrentPlatform, getPlatformPackageName, diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 4074c33..d05a0ab 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.41" +#define SQLITE_VECTOR_VERSION "0.9.42" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 8a9eb9c822cc9c995f28a1354ad65da38d7cdab9 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 15:34:55 +0200 Subject: [PATCH 087/108] fix(packages/node): test with vitest instead of native node --- packages/node/generate-platform-packages.js | 6 +- packages/node/package.json | 21 +++--- packages/node/src/index.test.ts | 84 ++++++++------------- src/sqlite-vector.h | 2 +- 4 files changed, 47 insertions(+), 66 deletions(-) diff --git a/packages/node/generate-platform-packages.js b/packages/node/generate-platform-packages.js index fdb735f..fd541f3 100755 --- a/packages/node/generate-platform-packages.js +++ b/packages/node/generate-platform-packages.js @@ -10,7 +10,7 @@ * node generate-platform-packages.js * * Example: - * node generate-platform-packages.js 0.9.42 ./artifacts ./platform-packages + * node generate-platform-packages.js 0.9.43 ./artifacts ./platform-packages */ const fs = require('fs'); @@ -165,7 +165,7 @@ function main() { if (args.length < 3) { console.error('Usage: node generate-platform-packages.js '); - console.error('Example: node generate-platform-packages.js 0.9.42 ./artifacts ./platform-packages'); + console.error('Example: node generate-platform-packages.js 0.9.43 ./artifacts ./platform-packages'); process.exit(1); } @@ -181,7 +181,7 @@ function main() { // Validate version format if (!/^\d+\.\d+\.\d+$/.test(version)) { console.error(`Error: Invalid version format: ${version}`); - console.error('Version must be in semver format (e.g., 0.9.42)'); + console.error('Version must be in semver format (e.g., 0.9.43)'); process.exit(1); } diff --git a/packages/node/package.json b/packages/node/package.json index 93088bc..796e9a8 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sqliteai/sqlite-vector", - "version": "0.9.42", + "version": "0.9.43", "description": "SQLite vector search extension for Node.js - Cross-platform vector embeddings and similarity search", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -25,7 +25,7 @@ "scripts": { "build": "tsup", "prepublishOnly": "npm run build", - "test": "node --test dist/index.test.js", + "test": "vitest", "typecheck": "tsc --noEmit", "generate-platforms": "node generate-platform-packages.js" }, @@ -55,17 +55,18 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "@sqliteai/sqlite-vector-darwin-arm64": "0.9.42", - "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.42", - "@sqliteai/sqlite-vector-linux-arm64": "0.9.42", - "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.42", - "@sqliteai/sqlite-vector-linux-x86_64": "0.9.42", - "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.42", - "@sqliteai/sqlite-vector-win32-x86_64": "0.9.42" + "@sqliteai/sqlite-vector-darwin-arm64": "0.9.43", + "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.43", + "@sqliteai/sqlite-vector-linux-arm64": "0.9.43", + "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.43", + "@sqliteai/sqlite-vector-linux-x86_64": "0.9.43", + "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.43", + "@sqliteai/sqlite-vector-win32-x86_64": "0.9.43" }, "devDependencies": { "@types/node": "^20.0.0", "tsup": "^8.0.0", - "typescript": "^5.3.0" + "typescript": "^5.3.0", + "vitest": "^3.2.4" } } diff --git a/packages/node/src/index.test.ts b/packages/node/src/index.test.ts index 32eff74..59215d4 100644 --- a/packages/node/src/index.test.ts +++ b/packages/node/src/index.test.ts @@ -1,5 +1,4 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; +import { describe, it, expect } from 'vitest'; import { getCurrentPlatform, getPlatformPackageName, @@ -8,10 +7,10 @@ import { getExtensionPath, getExtensionInfo, ExtensionNotFoundError -} from './index.js'; +} from './index'; describe('Platform Detection', () => { - test('getCurrentPlatform() returns a valid platform', () => { + it('getCurrentPlatform() returns a valid platform', () => { const platform = getCurrentPlatform(); const validPlatforms = [ 'darwin-arm64', @@ -23,87 +22,68 @@ describe('Platform Detection', () => { 'win32-x86_64', ]; - assert.ok( - validPlatforms.includes(platform), - `Platform ${platform} should be one of: ${validPlatforms.join(', ')}` - ); + expect(validPlatforms).toContain(platform); }); - test('getPlatformPackageName() returns correct package name format', () => { + it('getPlatformPackageName() returns correct package name format', () => { const packageName = getPlatformPackageName(); - assert.ok( - packageName.startsWith('@sqliteai/sqlite-vector-'), - 'Package name should start with @sqliteai/sqlite-vector-' - ); + expect(packageName.startsWith('@sqliteai/sqlite-vector-')).toBe(true); - assert.match( - packageName, - /^@sqliteai\/sqlite-vector-(darwin|linux|win32)-(arm64|x86_64)(-musl)?$/, - 'Package name should match expected format' + expect(packageName).toMatch( + /^@sqliteai\/sqlite-vector-(darwin|linux|win32)-(arm64|x86_64)(-musl)?$/ ); }); - test('getBinaryName() returns correct extension', () => { + it('getBinaryName() returns correct extension', () => { const binaryName = getBinaryName(); - assert.match( - binaryName, - /^vector\.(dylib|so|dll)$/, - 'Binary name should be vector.dylib, vector.so, or vector.dll' + expect(binaryName).toMatch( + /^vector\.(dylib|so|dll)$/ ); }); - test('isMusl() returns a boolean', () => { - const result = isMusl(); - assert.strictEqual(typeof result, 'boolean'); + it('isMusl() returns a boolean', () => { + expect(typeof isMusl()).toBe('boolean'); }); }); describe('Extension Path Resolution', () => { - test('getExtensionPath() returns a string or throws', () => { + it('getExtensionPath() returns a string or throws', () => { try { const path = getExtensionPath(); - assert.strictEqual(typeof path, 'string'); - assert.ok(path.length > 0, 'Path should not be empty'); + expect(typeof path).toBe('string'); + expect(path.length).toBeGreaterThan(0); } catch (error) { - // If it throws, it should be ExtensionNotFoundError - assert.ok( - error instanceof ExtensionNotFoundError, - 'Should throw ExtensionNotFoundError if extension not found' - ); + expect(error instanceof ExtensionNotFoundError).toBe(true); } }); - test('getExtensionInfo() returns complete info object', () => { + it('getExtensionInfo() returns complete info object', () => { try { const info = getExtensionInfo(); - assert.ok(info.platform, 'Should have platform'); - assert.ok(info.packageName, 'Should have packageName'); - assert.ok(info.binaryName, 'Should have binaryName'); - assert.ok(info.path, 'Should have path'); + expect(info.platform).toBeTruthy(); + expect(info.packageName).toBeTruthy(); + expect(info.binaryName).toBeTruthy(); + expect(info.path).toBeTruthy(); - assert.strictEqual(typeof info.platform, 'string'); - assert.strictEqual(typeof info.packageName, 'string'); - assert.strictEqual(typeof info.binaryName, 'string'); - assert.strictEqual(typeof info.path, 'string'); + expect(typeof info.platform).toBe('string'); + expect(typeof info.packageName).toBe('string'); + expect(typeof info.binaryName).toBe('string'); + expect(typeof info.path).toBe('string'); } catch (error) { - // If it throws, it should be ExtensionNotFoundError - assert.ok( - error instanceof ExtensionNotFoundError, - 'Should throw ExtensionNotFoundError if extension not found' - ); + expect(error instanceof ExtensionNotFoundError).toBe(true); } }); }); describe('Error Handling', () => { - test('ExtensionNotFoundError has correct properties', () => { + it('ExtensionNotFoundError has correct properties', () => { const error = new ExtensionNotFoundError('Test message'); - assert.ok(error instanceof Error); - assert.strictEqual(error.name, 'ExtensionNotFoundError'); - assert.strictEqual(error.message, 'Test message'); + expect(error instanceof Error).toBe(true); + expect(error.name).toBe('ExtensionNotFoundError'); + expect(error.message).toBe('Test message'); }); -}); +}); \ No newline at end of file diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index d05a0ab..82e665b 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.42" +#define SQLITE_VECTOR_VERSION "0.9.43" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 52ea81386ec0e023bb8cd4d0c5497e05f095df53 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 15:53:38 +0200 Subject: [PATCH 088/108] fix(packages/node): wrong x86 archName --- packages/node/generate-platform-packages.js | 6 +++--- packages/node/package.json | 16 ++++++++-------- packages/node/src/platform.ts | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/node/generate-platform-packages.js b/packages/node/generate-platform-packages.js index fd541f3..4825a17 100755 --- a/packages/node/generate-platform-packages.js +++ b/packages/node/generate-platform-packages.js @@ -10,7 +10,7 @@ * node generate-platform-packages.js * * Example: - * node generate-platform-packages.js 0.9.43 ./artifacts ./platform-packages + * node generate-platform-packages.js 0.9.44 ./artifacts ./platform-packages */ const fs = require('fs'); @@ -165,7 +165,7 @@ function main() { if (args.length < 3) { console.error('Usage: node generate-platform-packages.js '); - console.error('Example: node generate-platform-packages.js 0.9.43 ./artifacts ./platform-packages'); + console.error('Example: node generate-platform-packages.js 0.9.44 ./artifacts ./platform-packages'); process.exit(1); } @@ -181,7 +181,7 @@ function main() { // Validate version format if (!/^\d+\.\d+\.\d+$/.test(version)) { console.error(`Error: Invalid version format: ${version}`); - console.error('Version must be in semver format (e.g., 0.9.43)'); + console.error('Version must be in semver format (e.g., 0.9.44)'); process.exit(1); } diff --git a/packages/node/package.json b/packages/node/package.json index 796e9a8..80aacdf 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sqliteai/sqlite-vector", - "version": "0.9.43", + "version": "0.9.44", "description": "SQLite vector search extension for Node.js - Cross-platform vector embeddings and similarity search", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -55,13 +55,13 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "@sqliteai/sqlite-vector-darwin-arm64": "0.9.43", - "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.43", - "@sqliteai/sqlite-vector-linux-arm64": "0.9.43", - "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.43", - "@sqliteai/sqlite-vector-linux-x86_64": "0.9.43", - "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.43", - "@sqliteai/sqlite-vector-win32-x86_64": "0.9.43" + "@sqliteai/sqlite-vector-darwin-arm64": "0.9.44", + "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.44", + "@sqliteai/sqlite-vector-linux-arm64": "0.9.44", + "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.44", + "@sqliteai/sqlite-vector-linux-x86_64": "0.9.44", + "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.44", + "@sqliteai/sqlite-vector-win32-x86_64": "0.9.44" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/packages/node/src/platform.ts b/packages/node/src/platform.ts index 6785300..816ff2d 100644 --- a/packages/node/src/platform.ts +++ b/packages/node/src/platform.ts @@ -97,7 +97,7 @@ export function getCurrentPlatform(): Platform { // macOS if (platformName === 'darwin') { if (archName === 'arm64') return 'darwin-arm64'; - if (archName === 'x86_64') return 'darwin-x86_64'; + if (archName === 'x64' || archName === 'ia32') return 'darwin-x86_64'; } // Linux (with musl detection) @@ -107,14 +107,14 @@ export function getCurrentPlatform(): Platform { if (archName === 'arm64') { return `linux-arm64${muslSuffix}` as Platform; } - if (archName === 'x86_64') { + if (archName === 'x64' || archName === 'ia32') { return `linux-x86_64${muslSuffix}` as Platform; } } // Windows if (platformName === 'win32') { - if (archName === 'x86_64') return 'win32-x86_64'; + if (archName === 'x64' || archName === 'ia32') return 'win32-x86_64'; } // Unsupported platform From c7fd872f0d5c8eedb08c2b0ea4aa0127c6f92ca8 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 16:00:06 +0200 Subject: [PATCH 089/108] Bump version to 0.9.45 --- packages/node/generate-platform-packages.js | 6 +++--- packages/node/package.json | 16 ++++++++-------- src/sqlite-vector.h | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/node/generate-platform-packages.js b/packages/node/generate-platform-packages.js index 4825a17..0501b55 100755 --- a/packages/node/generate-platform-packages.js +++ b/packages/node/generate-platform-packages.js @@ -10,7 +10,7 @@ * node generate-platform-packages.js * * Example: - * node generate-platform-packages.js 0.9.44 ./artifacts ./platform-packages + * node generate-platform-packages.js 0.9.45 ./artifacts ./platform-packages */ const fs = require('fs'); @@ -165,7 +165,7 @@ function main() { if (args.length < 3) { console.error('Usage: node generate-platform-packages.js '); - console.error('Example: node generate-platform-packages.js 0.9.44 ./artifacts ./platform-packages'); + console.error('Example: node generate-platform-packages.js 0.9.45 ./artifacts ./platform-packages'); process.exit(1); } @@ -181,7 +181,7 @@ function main() { // Validate version format if (!/^\d+\.\d+\.\d+$/.test(version)) { console.error(`Error: Invalid version format: ${version}`); - console.error('Version must be in semver format (e.g., 0.9.44)'); + console.error('Version must be in semver format (e.g., 0.9.45)'); process.exit(1); } diff --git a/packages/node/package.json b/packages/node/package.json index 80aacdf..ab5fb53 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sqliteai/sqlite-vector", - "version": "0.9.44", + "version": "0.9.45", "description": "SQLite vector search extension for Node.js - Cross-platform vector embeddings and similarity search", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -55,13 +55,13 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "@sqliteai/sqlite-vector-darwin-arm64": "0.9.44", - "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.44", - "@sqliteai/sqlite-vector-linux-arm64": "0.9.44", - "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.44", - "@sqliteai/sqlite-vector-linux-x86_64": "0.9.44", - "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.44", - "@sqliteai/sqlite-vector-win32-x86_64": "0.9.44" + "@sqliteai/sqlite-vector-darwin-arm64": "0.9.45", + "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.45", + "@sqliteai/sqlite-vector-linux-arm64": "0.9.45", + "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.45", + "@sqliteai/sqlite-vector-linux-x86_64": "0.9.45", + "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.45", + "@sqliteai/sqlite-vector-win32-x86_64": "0.9.45" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 82e665b..0edea54 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.43" +#define SQLITE_VECTOR_VERSION "0.9.45" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From d8eb7220eb996007ddaaefd6b5a772b49ad8aad0 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Thu, 16 Oct 2025 16:32:15 +0200 Subject: [PATCH 090/108] fix(packages/node): automatic package version update --- .github/workflows/main.yml | 22 + packages/node/package-lock.json | 2569 +++++++++++++++++++++++++++++++ src/sqlite-vector.h | 2 +- 3 files changed, 2592 insertions(+), 1 deletion(-) create mode 100644 packages/node/package-lock.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a059ed5..f187078 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -335,6 +335,28 @@ jobs: run: | cd packages/node + # Update version in package.json and package-lock.json + echo "Updating versions to ${{ steps.tag.outputs.version }}..." + + # Update package.json + jq --arg version "${{ steps.tag.outputs.version }}" \ + '.version = $version | .optionalDependencies = (.optionalDependencies | with_entries(.value = $version))' \ + package.json > package.tmp.json && mv package.tmp.json package.json + + # Update package-lock.json + jq --arg version "${{ steps.tag.outputs.version }}" \ + '.version = $version | + .packages[""].version = $version | + .packages[""].optionalDependencies = (.packages[""].optionalDependencies | with_entries(.value = $version)) | + if .packages then .packages |= with_entries( + if (.key | startswith("node_modules/@sqliteai/sqlite-vector-")) then + .value.version = $version + else . end + ) else . end' \ + package-lock.json > package-lock.tmp.json && mv package-lock.tmp.json package-lock.json + + echo "✓ Updated package.json and package-lock.json to version ${{ steps.tag.outputs.version }}" + # Generate platform packages echo "Generating platform packages..." node generate-platform-packages.js "${{ steps.tag.outputs.version }}" "../../artifacts" "./platform-packages" diff --git a/packages/node/package-lock.json b/packages/node/package-lock.json new file mode 100644 index 0000000..5f1e4bb --- /dev/null +++ b/packages/node/package-lock.json @@ -0,0 +1,2569 @@ +{ + "name": "@sqliteai/sqlite-vector", + "version": "0.9.45", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sqliteai/sqlite-vector", + "version": "0.9.45", + "license": "SEE LICENSE IN LICENSE.md", + "devDependencies": { + "@types/node": "^20.0.0", + "tsup": "^8.0.0", + "typescript": "^5.3.0", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "@sqliteai/sqlite-vector-darwin-arm64": "0.9.45", + "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.45", + "@sqliteai/sqlite-vector-linux-arm64": "0.9.45", + "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.45", + "@sqliteai/sqlite-vector-linux-x86_64": "0.9.45", + "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.45", + "@sqliteai/sqlite-vector-win32-x86_64": "0.9.45" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz", + "integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", + "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.25.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", + "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 0edea54..50964ec 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.45" +#define SQLITE_VECTOR_VERSION "0.9.46" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 4b05be759edf321c20c8936207009baa4b86a0dd Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 17 Oct 2025 18:54:58 +0200 Subject: [PATCH 091/108] fix(packages/node): update package README and replace NPM_TOKEN with OIDC auth --- .github/workflows/main.yml | 13 ++- packages/node/README.md | 200 +++---------------------------------- src/sqlite-vector.h | 2 +- 3 files changed, 21 insertions(+), 194 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f187078..94f1a78 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: permissions: contents: write - # id-token: write # TODO: Enable after first release for npm provenance with OIDC + id-token: write jobs: build: @@ -328,10 +328,11 @@ jobs: node-version: '20' registry-url: 'https://registry.npmjs.org' + - name: update npm # npm 11.5.1 is required for OIDC auth https://docs.npmjs.com/trusted-publishers + run: npm install -g npm@11.5.1 + - name: build and publish npm packages if: steps.tag.outputs.version != '' - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | cd packages/node @@ -377,8 +378,7 @@ jobs: platform_name=$(basename "$platform_dir") echo " Publishing @sqliteai/sqlite-vector-${platform_name}..." cd "$platform_dir" - npm publish --access public - # TODO: Add --provenance flag after switching to OIDC (requires package to exist first) + npm publish --provenance --access public cd .. echo " ✓ Published @sqliteai/sqlite-vector-${platform_name}" done @@ -386,8 +386,7 @@ jobs: # Publish main package echo "Publishing main package to npm..." - npm publish --access public - # TODO: Add --provenance flag after switching to OIDC (requires package to exist first) + npm publish --provenance --access public echo "✓ Published @sqliteai/sqlite-vector@${{ steps.tag.outputs.version }}" echo "" diff --git a/packages/node/README.md b/packages/node/README.md index 7fbb082..bb63997 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -1,11 +1,11 @@ # @sqliteai/sqlite-vector [![npm version](https://badge.fury.io/js/@sqliteai%2Fsqlite-vector.svg)](https://www.npmjs.com/package/@sqliteai/sqlite-vector) -[![License](https://img.shields.io/badge/license-Elastic%202.0-blue.svg)](../../../LICENSE.md) +[![License](https://img.shields.io/badge/license-Elastic%202.0-blue.svg)](LICENSE.md) -> SQLite Vector extension for Node.js - Cross-platform vector embeddings and similarity search +> SQLite Vector extension packaged for Node.js -**SQLite Vector** brings powerful vector search capabilities to SQLite in Node.js. Perfect for Edge AI, semantic search, RAG applications, and similarity matching - all running locally without external dependencies. +**SQLite Vector** is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. It works seamlessly on **iOS, Android, Windows, Linux, and macOS**, using just **30MB of memory** by default. With support for **Float32, Float16, BFloat16, Int8, and UInt8**, and **highly optimized distance functions**, it's the ideal solution for **Edge AI** applications. ## Features @@ -13,7 +13,7 @@ - ✅ **Zero configuration** - Automatically detects and loads the correct binary for your platform - ✅ **TypeScript native** - Full type definitions included - ✅ **Modern ESM + CJS** - Works with both ES modules and CommonJS -- ✅ **Small footprint** - Only downloads binaries for your platform (~30MB) +- ✅ **Small footprint** - Only downloads binaries for your platform - ✅ **Offline-ready** - No external services required ## Installation @@ -36,61 +36,16 @@ The package automatically downloads the correct native extension for your platfo | Linux | x86_64 (musl/Alpine) | `@sqliteai/sqlite-vector-linux-x86_64-musl` | | Windows | x86_64 | `@sqliteai/sqlite-vector-win32-x86_64` | -## Usage +## sqlite-vector API + +For detailed information on how to use the vector extension features, see the [main documentation](https://github.com/sqliteai/sqlite-vector/blob/main/README.md). -### Basic Usage +## Usage ```typescript import { getExtensionPath } from '@sqliteai/sqlite-vector'; import Database from 'better-sqlite3'; -// Get the path to the vector extension -const extensionPath = getExtensionPath(); - -// Load it into your SQLite database -const db = new Database(':memory:'); -db.loadExtension(extensionPath); - -// Use vector functions -db.exec(` - CREATE TABLE embeddings ( - id INTEGER PRIMARY KEY, - vector BLOB, - text TEXT - ); -`); - -// Initialize vector search -db.prepare("SELECT vector_init('embeddings', 'vector', 'type=FLOAT32,dimension=384')").run(); - -// Insert vectors (using your embedding model) -const embedding = new Float32Array(384); -// ... fill embedding with your model's output - -db.prepare('INSERT INTO embeddings (vector, text) VALUES (?, ?)').run( - Buffer.from(embedding.buffer), - 'Sample text' -); - -// Perform similarity search -const query = new Float32Array(384); // Your query embedding -const results = db.prepare(` - SELECT e.id, e.text, v.distance - FROM embeddings AS e - JOIN vector_quantize_scan('embeddings', 'vector', ?, 10) AS v - ON e.id = v.rowid - ORDER BY v.distance ASC -`).all(Buffer.from(query.buffer)); - -console.log(results); -``` - -### CommonJS - -```javascript -const { getExtensionPath } = require('@sqliteai/sqlite-vector'); -const Database = require('better-sqlite3'); - const db = new Database(':memory:'); db.loadExtension(getExtensionPath()); @@ -99,28 +54,9 @@ const version = db.prepare('SELECT vector_version()').pluck().get(); console.log('Vector extension version:', version); ``` -### Get Extension Information - -```typescript -import { getExtensionInfo } from '@sqliteai/sqlite-vector'; - -const info = getExtensionInfo(); -console.log(info); -// { -// platform: 'darwin-arm64', -// packageName: '@sqliteai/sqlite-vector-darwin-arm64', -// binaryName: 'vector.dylib', -// path: '/path/to/node_modules/@sqliteai/sqlite-vector-darwin-arm64/vector.dylib' -// } -``` - ## Examples -For complete, runnable examples, see the [sqlite-extensions-guide](https://github.com/sqliteai/sqlite-extensions-guide/tree/main/examples/node): - -- **[basic-usage.js](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/examples/node/basic-usage.js)** - Generic extension loading for any @sqliteai extension -- **[semantic-search.js](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/examples/node/semantic-search.js)** - Complete semantic search with OpenAI or on-device embeddings -- **[with-multiple-extensions.js](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/examples/node/with-multiple-extensions.js)** - Loading multiple extensions together (vector + sync + AI + js) +For complete, runnable examples, see the [sqlite-extensions-guide](https://github.com/sqliteai/sqlite-extensions-guide/tree/main/examples/node). These examples are generic and work with all SQLite extensions: `sqlite-vector`, `sqlite-sync`, `sqlite-js`, and `sqlite-ai`. @@ -196,131 +132,23 @@ Detects if the system uses musl libc (Alpine Linux, etc.). Error thrown when the SQLite Vector extension cannot be found for the current platform. -## Vector Search Guide - -For detailed information on how to use the vector search features, see the [main documentation](https://github.com/sqliteai/sqlite-vector/blob/main/README.md). - -### Quick Reference - -```sql --- Initialize vector column -SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384'); - --- Quantize vectors for faster search -SELECT vector_quantize('table', 'column'); - --- Preload into memory for 4-5x speedup -SELECT vector_quantize_preload('table', 'column'); - --- Search for similar vectors -SELECT * FROM table AS t -JOIN vector_quantize_scan('table', 'column', , ) AS v -ON t.rowid = v.rowid -ORDER BY v.distance; -``` - -### Distance Metrics - -Specify the distance metric during initialization: - -```sql --- L2 (Euclidean) - default -SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=L2'); - --- Cosine similarity -SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=COSINE'); - --- Dot product -SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=DOT'); - --- L1 (Manhattan) -SELECT vector_init('table', 'column', 'type=FLOAT32,dimension=384,distance=L1'); -``` - -## Troubleshooting - -### Extension Not Found - -If you get `ExtensionNotFoundError`, try: - -```bash -# Force reinstall dependencies -npm install --force - -# Or manually install the platform package -npm install @sqliteai/sqlite-vector-darwin-arm64 # Replace with your platform -``` - -### Platform Not Detected - -The package should automatically detect your platform. If detection fails, please [open an issue](https://github.com/sqliteai/sqlite-vector/issues) with: -- Your OS and architecture -- Node.js version -- Output of `node -p "process.platform + '-' + process.arch"` - -### Alpine Linux / musl - -On Alpine Linux or other musl-based systems, the package automatically detects musl and installs the correct variant. If you encounter issues: - -```bash -# Verify musl detection -node -e "console.log(require('@sqliteai/sqlite-vector').isMusl())" - -# Should print: true -``` - -## Development - -### Building from Source - -```bash -# Clone the repository -git clone https://github.com/sqliteai/sqlite-vector.git -cd sqlite-vector/packages/node - -# Install dependencies -npm install - -# Build TypeScript -npm run build - -# Run tests -npm test -``` - -### Project Structure - -``` -packages/node/ -├── src/ -│ ├── index.ts # Main entry point -│ ├── platform.ts # Platform detection logic -│ └── index.test.ts # Test suite -├── dist/ # Compiled JavaScript (generated) -├── generate-platform-packages.js # Platform package generator -├── package.json -├── tsconfig.json -└── README.md -``` - ## Related Projects -- **[SQLite-AI](https://github.com/sqliteai/sqlite-ai)** - On-device AI inference and embedding generation -- **[SQLite-Sync](https://github.com/sqliteai/sqlite-sync)** - Sync on-device databases with the cloud -- **[SQLite-JS](https://github.com/sqliteai/sqlite-js)** - Define SQLite functions in JavaScript +- **[@sqliteai/sqlite-ai](https://www.npmjs.com/package/@sqliteai/sqlite-ai)** - On-device AI inference and embedding generation +- **[@sqliteai/sqlite-sync](https://www.npmjs.com/package/@sqliteai/sqlite-sync)** - Sync on-device databases with the cloud +- **[@sqliteai/sqlite-js](https://www.npmjs.com/package/@sqliteai/sqlite-js)** - Define SQLite functions in JavaScript ## License -This project is licensed under the [Elastic License 2.0](../../../LICENSE.md). +This project is licensed under the [Elastic License 2.0](LICENSE.md). For production or managed service use, please [contact SQLite Cloud, Inc](mailto:info@sqlitecloud.io) for a commercial license. ## Contributing -Contributions are welcome! Please see the [main repository](https://github.com/sqliteai/sqlite-vector) for contribution guidelines. +Contributions are welcome! Please see the [main repository](https://github.com/sqliteai/sqlite-vector) to open an issue. ## Support - 📖 [Documentation](https://github.com/sqliteai/sqlite-vector/blob/main/API.md) - 🐛 [Report Issues](https://github.com/sqliteai/sqlite-vector/issues) -- 💬 [Discussions](https://github.com/sqliteai/sqlite-vector/discussions) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 50964ec..eee1b5a 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.46" +#define SQLITE_VECTOR_VERSION "0.9.47" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From b2149d696c6c557d2ec6e5592cee0fbdeff8314e Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 17 Oct 2025 19:15:42 +0200 Subject: [PATCH 092/108] fix(packages/node): stuck npm version package README badge --- packages/node/README.md | 2 +- src/sqlite-vector.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node/README.md b/packages/node/README.md index bb63997..32b97f5 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -1,6 +1,6 @@ # @sqliteai/sqlite-vector -[![npm version](https://badge.fury.io/js/@sqliteai%2Fsqlite-vector.svg)](https://www.npmjs.com/package/@sqliteai/sqlite-vector) +[![npm version](https://badge.fury.io/js/@sqliteai%2Fsqlite-vector.svg)](https://badge.fury.io/js/@sqliteai%2Fsqlite-vector) [![License](https://img.shields.io/badge/license-Elastic%202.0-blue.svg)](LICENSE.md) > SQLite Vector extension packaged for Node.js diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index eee1b5a..f4c4d84 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.47" +#define SQLITE_VECTOR_VERSION "0.9.48" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 758671c66dcec3c6c1bb5508a6cfb6951428fad7 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 17 Oct 2025 19:43:45 +0200 Subject: [PATCH 093/108] fix(workflow): update release message with all the supported packages --- .github/workflows/main.yml | 25 ++++++------------------- src/sqlite-vector.h | 2 +- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94f1a78..603db49 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -400,26 +400,13 @@ jobs: if: steps.tag.outputs.version != '' with: body: | - # Node.js/npm Package + # Packages - **Main package**: [@sqliteai/sqlite-vector](https://www.npmjs.com/package/@sqliteai/sqlite-vector) - - ```bash - npm install @sqliteai/sqlite-vector - ``` - - Platform-specific packages are installed automatically based on your OS. - - --- - - # WASM Releases - - **WASM repository**: [sqlite-wasm](https://github.com/sqliteai/sqlite-wasm) - **WASM package**: [@sqliteai/sqlite-wasm](https://www.npmjs.com/package/@sqliteai/sqlite-wasm) - - ```bash - npm install @sqliteai/sqlite-wasm - ``` + [**Node**](https://www.npmjs.com/package/@sqliteai/sqlite-vector): `npm install @sqliteai/sqlite-vector` + [**WASM**](https://www.npmjs.com/package/@sqliteai/sqlite-wasm): `npm install @sqliteai/sqlite-wasm` + [**Android**](https://central.sonatype.com/artifact/ai.sqlite/vector): `ai.sqlite:vector:${{ steps.tag.outputs.version }}` + [**Python**](https://pypi.org/project/sqliteai-vector): `pip install sqliteai-vector` + [**Swift**](https://github.com/sqliteai/sqlite-vector#swift-package): [Installation Guide](https://github.com/sqliteai/sqlite-vector#swift-package) --- diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index f4c4d84..ad181dc 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.48" +#define SQLITE_VECTOR_VERSION "0.9.49" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 70c08f56cf563517fed5a5fc8f0677efa9442abc Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 20 Oct 2025 11:52:50 +0200 Subject: [PATCH 094/108] fix(packages/node): update platform specific packages LICENSE path in README --- packages/node/generate-platform-packages.js | 2 +- src/sqlite-vector.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node/generate-platform-packages.js b/packages/node/generate-platform-packages.js index 0501b55..4916583 100755 --- a/packages/node/generate-platform-packages.js +++ b/packages/node/generate-platform-packages.js @@ -153,7 +153,7 @@ npm install @sqliteai/sqlite-vector ## License -See [LICENSE.md](../../../LICENSE.md) in the root directory. +See [LICENSE.md](./LICENSE.md) in the root directory. `; } diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index ad181dc..19f1adc 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.49" +#define SQLITE_VECTOR_VERSION "0.9.50" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From c066d9e3ffca3ee8277cd7f54ed8d12b268a5389 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 20 Oct 2025 12:38:12 +0200 Subject: [PATCH 095/108] fix(packages/node): clean .gitignore and .npmignore; remove package-lock.json from library files to avoid integrity and resolution issues --- .github/workflows/main.yml | 16 +- .gitignore | 21 +- packages/node/.gitignore | 33 - packages/node/.npmignore | 6 +- packages/node/package-lock.json | 2569 ------------------------------- src/sqlite-vector.h | 2 +- 6 files changed, 23 insertions(+), 2624 deletions(-) delete mode 100644 packages/node/.gitignore delete mode 100644 packages/node/package-lock.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 603db49..7653ebe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -336,7 +336,7 @@ jobs: run: | cd packages/node - # Update version in package.json and package-lock.json + # Update version in package.json echo "Updating versions to ${{ steps.tag.outputs.version }}..." # Update package.json @@ -344,19 +344,7 @@ jobs: '.version = $version | .optionalDependencies = (.optionalDependencies | with_entries(.value = $version))' \ package.json > package.tmp.json && mv package.tmp.json package.json - # Update package-lock.json - jq --arg version "${{ steps.tag.outputs.version }}" \ - '.version = $version | - .packages[""].version = $version | - .packages[""].optionalDependencies = (.packages[""].optionalDependencies | with_entries(.value = $version)) | - if .packages then .packages |= with_entries( - if (.key | startswith("node_modules/@sqliteai/sqlite-vector-")) then - .value.version = $version - else . end - ) else . end' \ - package-lock.json > package-lock.tmp.json && mv package-lock.tmp.json package-lock.json - - echo "✓ Updated package.json and package-lock.json to version ${{ steps.tag.outputs.version }}" + echo "✓ Updated package.json to version ${{ steps.tag.outputs.version }}" # Generate platform packages echo "Generating platform packages..." diff --git a/.gitignore b/.gitignore index 243743c..41ab9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Build artifacts build/ -/dist +dist/ .build *.a *.sqlite @@ -20,10 +20,27 @@ jniLibs/ *.ap_ *.dex +# Node.js +node_modules/ +package-lock.json +*.tsbuildinfo +coverage/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +packages/node/platform-packages/ +packages/node/test-artifacts/ +packages/node/test-output/ +packages/node/test-platform-packages/ + # IDE .vscode .idea/ *.iml +*.swp +*.swo # System -.DS_Store \ No newline at end of file +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/packages/node/.gitignore b/packages/node/.gitignore deleted file mode 100644 index 8f2a530..0000000 --- a/packages/node/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Dependencies -node_modules/ - -# Build outputs -dist/ -*.tsbuildinfo - -# Test coverage -coverage/ - -# Logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# OS files -.DS_Store -Thumbs.db - -# IDE -.vscode/ -.idea/ -*.swp -*.swo - -# Generated platform packages (created dynamically in GitHub Actions) -platform-packages/ - -# Test outputs -test-artifacts/ -test-output/ -test-platform-packages/ diff --git a/packages/node/.npmignore b/packages/node/.npmignore index cfa4c54..7a87f94 100644 --- a/packages/node/.npmignore +++ b/packages/node/.npmignore @@ -5,16 +5,12 @@ src/ tsconfig.json tsup.config.ts -# Documentation (not needed in published package) -IMPLEMENTATION.md -WORKFLOW_STEPS.md -README.md - # Scripts (only for repo/CI) generate-platform-packages.js # Development files node_modules/ +package-lock.json coverage/ *.log diff --git a/packages/node/package-lock.json b/packages/node/package-lock.json deleted file mode 100644 index 5f1e4bb..0000000 --- a/packages/node/package-lock.json +++ /dev/null @@ -1,2569 +0,0 @@ -{ - "name": "@sqliteai/sqlite-vector", - "version": "0.9.45", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@sqliteai/sqlite-vector", - "version": "0.9.45", - "license": "SEE LICENSE IN LICENSE.md", - "devDependencies": { - "@types/node": "^20.0.0", - "tsup": "^8.0.0", - "typescript": "^5.3.0", - "vitest": "^3.2.4" - }, - "engines": { - "node": ">=16.0.0" - }, - "optionalDependencies": { - "@sqliteai/sqlite-vector-darwin-arm64": "0.9.45", - "@sqliteai/sqlite-vector-darwin-x86_64": "0.9.45", - "@sqliteai/sqlite-vector-linux-arm64": "0.9.45", - "@sqliteai/sqlite-vector-linux-arm64-musl": "0.9.45", - "@sqliteai/sqlite-vector-linux-x86_64": "0.9.45", - "@sqliteai/sqlite-vector-linux-x86_64-musl": "0.9.45", - "@sqliteai/sqlite-vector-win32-x86_64": "0.9.45" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", - "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", - "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", - "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", - "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", - "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", - "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", - "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", - "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", - "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", - "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", - "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", - "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", - "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", - "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", - "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", - "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", - "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", - "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", - "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", - "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", - "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", - "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", - "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", - "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", - "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", - "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", - "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", - "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", - "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", - "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", - "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", - "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", - "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", - "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", - "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", - "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", - "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", - "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", - "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", - "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", - "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", - "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", - "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", - "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", - "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", - "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz", - "integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/bundle-require": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", - "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.11", - "@esbuild/android-arm": "0.25.11", - "@esbuild/android-arm64": "0.25.11", - "@esbuild/android-x64": "0.25.11", - "@esbuild/darwin-arm64": "0.25.11", - "@esbuild/darwin-x64": "0.25.11", - "@esbuild/freebsd-arm64": "0.25.11", - "@esbuild/freebsd-x64": "0.25.11", - "@esbuild/linux-arm": "0.25.11", - "@esbuild/linux-arm64": "0.25.11", - "@esbuild/linux-ia32": "0.25.11", - "@esbuild/linux-loong64": "0.25.11", - "@esbuild/linux-mips64el": "0.25.11", - "@esbuild/linux-ppc64": "0.25.11", - "@esbuild/linux-riscv64": "0.25.11", - "@esbuild/linux-s390x": "0.25.11", - "@esbuild/linux-x64": "0.25.11", - "@esbuild/netbsd-arm64": "0.25.11", - "@esbuild/netbsd-x64": "0.25.11", - "@esbuild/openbsd-arm64": "0.25.11", - "@esbuild/openbsd-x64": "0.25.11", - "@esbuild/openharmony-arm64": "0.25.11", - "@esbuild/sunos-x64": "0.25.11", - "@esbuild/win32-arm64": "0.25.11", - "@esbuild/win32-ia32": "0.25.11", - "@esbuild/win32-x64": "0.25.11" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true, - "license": "MIT" - }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/rollup": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.4", - "@rollup/rollup-android-arm64": "4.52.4", - "@rollup/rollup-darwin-arm64": "4.52.4", - "@rollup/rollup-darwin-x64": "4.52.4", - "@rollup/rollup-freebsd-arm64": "4.52.4", - "@rollup/rollup-freebsd-x64": "4.52.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", - "@rollup/rollup-linux-arm64-gnu": "4.52.4", - "@rollup/rollup-linux-arm64-musl": "4.52.4", - "@rollup/rollup-linux-loong64-gnu": "4.52.4", - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-musl": "4.52.4", - "@rollup/rollup-linux-s390x-gnu": "4.52.4", - "@rollup/rollup-linux-x64-gnu": "4.52.4", - "@rollup/rollup-linux-x64-musl": "4.52.4", - "@rollup/rollup-openharmony-arm64": "4.52.4", - "@rollup/rollup-win32-arm64-msvc": "4.52.4", - "@rollup/rollup-win32-ia32-msvc": "4.52.4", - "@rollup/rollup-win32-x64-gnu": "4.52.4", - "@rollup/rollup-win32-x64-msvc": "4.52.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "deprecated": "The work that was done in this beta branch won't be included in future versions", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tsup": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", - "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^5.1.0", - "cac": "^6.7.14", - "chokidar": "^4.0.3", - "consola": "^3.4.0", - "debug": "^4.4.0", - "esbuild": "^0.25.0", - "fix-dts-default-cjs-exports": "^1.0.0", - "joycon": "^3.1.1", - "picocolors": "^1.1.1", - "postcss-load-config": "^6.0.1", - "resolve-from": "^5.0.0", - "rollup": "^4.34.8", - "source-map": "0.8.0-beta.0", - "sucrase": "^3.35.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.11", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@microsoft/api-extractor": "^7.36.0", - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.5.0" - }, - "peerDependenciesMeta": { - "@microsoft/api-extractor": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.1.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", - "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - } - } -} diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 19f1adc..17f57f6 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.50" +#define SQLITE_VECTOR_VERSION "0.9.51" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 1fa81433ee1027cadc5fb2f4522b34b20a32aa80 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 29 Oct 2025 19:24:08 +0100 Subject: [PATCH 096/108] fix(android): add support for amerabi-v7a (arm NEON 32bit); fixes #30 --- .github/workflows/main.yml | 10 ++- Makefile | 19 ++++-- src/distance-neon.c | 123 +++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7653ebe..d80183d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ${{ matrix.os }} container: ${{ matrix.container && matrix.container || '' }} - name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && matrix.name != 'android-aar' && ( matrix.name != 'macos' || matrix.arch != 'x86_64' ) && ' + test' || ''}} + name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.arch != 'armeabi-v7a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && matrix.name != 'android-aar' && ( matrix.name != 'macos' || matrix.arch != 'x86_64' ) && ' + test' || ''}} timeout-minutes: 20 strategy: fail-fast: false @@ -47,6 +47,10 @@ jobs: arch: arm64-v8a name: android make: PLATFORM=android ARCH=arm64-v8a + - os: ubuntu-22.04 + arch: armeabi-v7a + name: android + make: PLATFORM=android ARCH=armeabi-v7a - os: ubuntu-22.04 arch: x86_64 name: android @@ -140,7 +144,7 @@ jobs: security delete-keychain build.keychain - name: android setup test environment - if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' + if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' && matrix.arch != 'armeabi-v7a' run: | echo "::group::enable kvm group perms" @@ -168,7 +172,7 @@ jobs: echo "::endgroup::" - name: android test sqlite-vector - if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' + if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' && matrix.arch != 'armeabi-v7a' uses: reactivecircus/android-emulator-runner@v2.34.0 with: api-level: 26 diff --git a/Makefile b/Makefile index e02aaba..70ea497 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ else ifeq ($(PLATFORM),macos) STRIP = strip -x -S $@ else ifeq ($(PLATFORM),android) ifndef ARCH # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH - $(error "Android ARCH must be set to ARCH=x86_64 or ARCH=arm64-v8a") + $(error "Android ARCH must be set to ARCH=x86_64, ARCH=arm64-v8a, or ARCH=armeabi-v7a") endif ifndef ANDROID_NDK # Set ANDROID_NDK path to find android build tools; e.g. on MacOS: export ANDROID_NDK=/Users/username/Library/Android/sdk/ndk/25.2.9519653 $(error "Android NDK must be set") @@ -67,8 +67,14 @@ else ifeq ($(PLATFORM),android) BIN = $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(HOST)-x86_64/bin ifneq (,$(filter $(ARCH),arm64 arm64-v8a)) override ARCH := aarch64 + ANDROID_ABI := android26 + else ifeq ($(ARCH),armeabi-v7a) + override ARCH := armv7a + ANDROID_ABI := androideabi26 + else + ANDROID_ABI := android26 endif - CC = $(BIN)/$(ARCH)-linux-android26-clang + CC = $(BIN)/$(ARCH)-linux-$(ANDROID_ABI)-clang TARGET := $(DIST_DIR)/vector.so LDFLAGS += -lm -shared STRIP = $(BIN)/llvm-strip --strip-unneeded $@ @@ -184,11 +190,14 @@ $(DIST_DIR)/%.xcframework: $(LIB_NAMES) xcframework: $(DIST_DIR)/vector.xcframework -AAR_ARM = packages/android/src/main/jniLibs/arm64-v8a/ +AAR_ARM64 = packages/android/src/main/jniLibs/arm64-v8a/ +AAR_ARM = packages/android/src/main/jniLibs/armeabi-v7a/ AAR_X86 = packages/android/src/main/jniLibs/x86_64/ aar: - mkdir -p $(AAR_ARM) $(AAR_X86) + mkdir -p $(AAR_ARM64) $(AAR_ARM) $(AAR_X86) $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=arm64-v8a + mv $(DIST_DIR)/vector.so $(AAR_ARM64) + $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=armeabi-v7a mv $(DIST_DIR)/vector.so $(AAR_ARM) $(MAKE) clean && $(MAKE) PLATFORM=android ARCH=x86_64 mv $(DIST_DIR)/vector.so $(AAR_X86) @@ -208,7 +217,7 @@ help: @echo " linux (default on Linux)" @echo " macos (default on macOS)" @echo " windows (default on Windows)" - @echo " android (needs ARCH to be set to x86_64 or arm64-v8a and ANDROID_NDK to be set)" + @echo " android (needs ARCH to be set to x86_64, arm64-v8a, or armeabi-v7a and ANDROID_NDK to be set)" @echo " ios (only on macOS)" @echo " ios-sim (only on macOS)" @echo "" diff --git a/src/distance-neon.c b/src/distance-neon.c index d2c227e..dd3b2f0 100644 --- a/src/distance-neon.c +++ b/src/distance-neon.c @@ -18,6 +18,17 @@ extern distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX]; extern char *distance_backend_name; +// Helper function for 32-bit ARM: vmaxv_u16 is not available in ARMv7 NEON +#if __SIZEOF_POINTER__ == 4 +static inline uint16_t vmaxv_u16_compat(uint16x4_t v) { + // Use pairwise max to reduce vector + uint16x4_t m = vpmax_u16(v, v); // [max(v0,v1), max(v2,v3), max(v0,v1), max(v2,v3)] + m = vpmax_u16(m, m); // [max(all), max(all), max(all), max(all)] + return vget_lane_u16(m, 0); +} +#define vmaxv_u16 vmaxv_u16_compat +#endif + // MARK: FLOAT32 - float float32_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool use_sqrt) { @@ -158,6 +169,31 @@ float bfloat16_distance_l2_impl_neon (const void *v1, const void *v2, int n, boo const uint16_t *a = (const uint16_t *)v1; const uint16_t *b = (const uint16_t *)v2; +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: use scalar double accumulation (no float64x2_t in NEON) + double sum = 0.0; + int i = 0; + + for (; i <= n - 4; i += 4) { + uint16x4_t av16 = vld1_u16(a + i); + uint16x4_t bv16 = vld1_u16(b + i); + + float32x4_t va = bf16x4_to_f32x4_u16(av16); + float32x4_t vb = bf16x4_to_f32x4_u16(bv16); + float32x4_t d = vsubq_f32(va, vb); + // mask-out NaNs: m = (d==d) + uint32x4_t m = vceqq_f32(d, d); + d = vbslq_f32(m, d, vdupq_n_f32(0.0f)); + + // Store and accumulate in scalar double + float tmp[4]; + vst1q_f32(tmp, d); + for (int j = 0; j < 4; j++) { + double dj = (double)tmp[j]; + sum = fma(dj, dj, sum); + } + } +#else // Accumulate in f64 to avoid overflow from huge bf16 values. float64x2_t acc0 = vdupq_n_f64(0.0), acc1 = vdupq_n_f64(0.0); int i = 0; @@ -205,6 +241,7 @@ float bfloat16_distance_l2_impl_neon (const void *v1, const void *v2, int n, boo } double sum = vaddvq_f64(vaddq_f64(acc0, acc1)); +#endif // scalar tail; treat NaN as 0, Inf as +Inf result for (; i < n; ++i) { @@ -409,8 +446,15 @@ float float16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool const uint16x4_t SIGN_MASK = vdup_n_u16(0x8000u); const uint16x4_t ZERO16 = vdup_n_u16(0); +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: use scalar double accumulation + double sum = 0.0; + int i = 0; +#else + // 64-bit ARM: use float64x2_t NEON intrinsics float64x2_t acc0 = vdupq_n_f64(0.0), acc1 = vdupq_n_f64(0.0); int i = 0; +#endif for (; i <= n - 4; i += 4) { uint16x4_t av16 = vld1_u16(a + i); @@ -443,6 +487,16 @@ float float16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool uint32x4_t m = vceqq_f32(d32, d32); /* true where not-NaN */ d32 = vbslq_f32(m, d32, vdupq_n_f32(0.0f)); +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: accumulate in scalar double + float tmp[4]; + vst1q_f32(tmp, d32); + for (int j = 0; j < 4; j++) { + double dj = (double)tmp[j]; + sum = fma(dj, dj, sum); + } +#else + // 64-bit ARM: use NEON f64 operations float64x2_t dlo = vcvt_f64_f32(vget_low_f32(d32)); float64x2_t dhi = vcvt_f64_f32(vget_high_f32(d32)); #if defined(__ARM_FEATURE_FMA) @@ -451,10 +505,13 @@ float float16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool #else acc0 = vaddq_f64(acc0, vmulq_f64(dlo, dlo)); acc1 = vaddq_f64(acc1, vmulq_f64(dhi, dhi)); +#endif #endif } +#if __SIZEOF_POINTER__ != 4 double sum = vaddvq_f64(vaddq_f64(acc0, acc1)); +#endif /* tail (scalar; same Inf/NaN policy) */ for (; i < n; ++i) { @@ -487,10 +544,17 @@ float float16_distance_cosine_neon (const void *v1, const void *v2, int n) { const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); const uint16x4_t ZERO16 = vdup_n_u16(0); +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: use scalar double accumulation + double dot = 0.0, normx = 0.0, normy = 0.0; + int i = 0; +#else + // 64-bit ARM: use float64x2_t NEON intrinsics float64x2_t acc_dot_lo = vdupq_n_f64(0.0), acc_dot_hi = vdupq_n_f64(0.0); float64x2_t acc_a2_lo = vdupq_n_f64(0.0), acc_a2_hi = vdupq_n_f64(0.0); float64x2_t acc_b2_lo = vdupq_n_f64(0.0), acc_b2_hi = vdupq_n_f64(0.0); int i = 0; +#endif for (; i <= n - 4; i += 4) { uint16x4_t av16 = vld1_u16(a + i); @@ -512,6 +576,19 @@ float float16_distance_cosine_neon (const void *v1, const void *v2, int n) { ax = vbslq_f32(mx, ax, vdupq_n_f32(0.0f)); by = vbslq_f32(my, by, vdupq_n_f32(0.0f)); +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: accumulate in scalar double + float ax_tmp[4], by_tmp[4]; + vst1q_f32(ax_tmp, ax); + vst1q_f32(by_tmp, by); + for (int j = 0; j < 4; j++) { + double x = (double)ax_tmp[j]; + double y = (double)by_tmp[j]; + dot += x * y; + normx += x * x; + normy += y * y; + } +#else /* widen to f64 and accumulate */ float64x2_t ax_lo = vcvt_f64_f32(vget_low_f32(ax)), ax_hi = vcvt_f64_f32(vget_high_f32(ax)); float64x2_t by_lo = vcvt_f64_f32(vget_low_f32(by)), by_hi = vcvt_f64_f32(vget_high_f32(by)); @@ -530,12 +607,15 @@ float float16_distance_cosine_neon (const void *v1, const void *v2, int n) { acc_a2_hi = vaddq_f64(acc_a2_hi, vmulq_f64(ax_hi, ax_hi)); acc_b2_lo = vaddq_f64(acc_b2_lo, vmulq_f64(by_lo, by_lo)); acc_b2_hi = vaddq_f64(acc_b2_hi, vmulq_f64(by_hi, by_hi)); +#endif #endif } +#if __SIZEOF_POINTER__ != 4 double dot = vaddvq_f64(vaddq_f64(acc_dot_lo, acc_dot_hi)); double normx= vaddvq_f64(vaddq_f64(acc_a2_lo, acc_a2_hi)); double normy= vaddvq_f64(vaddq_f64(acc_b2_lo, acc_b2_hi)); +#endif /* tail (scalar) */ for (; i < n; ++i) { @@ -569,8 +649,15 @@ float float16_distance_dot_neon (const void *v1, const void *v2, int n) { const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); const uint16x4_t ZERO16 = vdup_n_u16(0); +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: use scalar double accumulation + double dot = 0.0; + int i = 0; +#else + // 64-bit ARM: use float64x2_t NEON intrinsics float64x2_t acc_lo = vdupq_n_f64(0.0), acc_hi = vdupq_n_f64(0.0); int i = 0; +#endif for (; i <= n - 4; i += 4) { uint16x4_t av16 = vld1_u16(a + i); @@ -588,7 +675,11 @@ float float16_distance_dot_neon (const void *v1, const void *v2, int n) { if (isnan(x) || isnan(y)) continue; double p = (double)x * (double)y; if (isinf(p)) return (p>0)? -INFINITY : INFINITY; +#if __SIZEOF_POINTER__ == 4 + dot += p; +#else acc_lo = vsetq_lane_f64(vgetq_lane_f64(acc_lo,0)+p, acc_lo, 0); /* cheap add */ +#endif } continue; } @@ -603,13 +694,26 @@ float float16_distance_dot_neon (const void *v1, const void *v2, int n) { by = vbslq_f32(my, by, vdupq_n_f32(0.0f)); float32x4_t prod = vmulq_f32(ax, by); + +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: accumulate in scalar double + float prod_tmp[4]; + vst1q_f32(prod_tmp, prod); + for (int j = 0; j < 4; j++) { + dot += (double)prod_tmp[j]; + } +#else + // 64-bit ARM: use NEON f64 operations float64x2_t lo = vcvt_f64_f32(vget_low_f32(prod)); float64x2_t hi = vcvt_f64_f32(vget_high_f32(prod)); acc_lo = vaddq_f64(acc_lo, lo); acc_hi = vaddq_f64(acc_hi, hi); +#endif } +#if __SIZEOF_POINTER__ != 4 double dot = vaddvq_f64(vaddq_f64(acc_lo, acc_hi)); +#endif for (; i < n; ++i) { float x = float16_to_float32(a[i]); @@ -635,8 +739,15 @@ float float16_distance_l1_neon (const void *v1, const void *v2, int n) { const uint16x4_t SIGN_MASK = vdup_n_u16(0x8000u); const uint16x4_t ZERO16 = vdup_n_u16(0); +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: use scalar double accumulation + double sum = 0.0; + int i = 0; +#else + // 64-bit ARM: use float64x2_t NEON intrinsics float64x2_t acc = vdupq_n_f64(0.0); int i = 0; +#endif for (; i <= n - 4; i += 4) { uint16x4_t av16 = vld1_u16(a + i); @@ -665,13 +776,25 @@ float float16_distance_l1_neon (const void *v1, const void *v2, int n) { uint32x4_t m = vceqq_f32(d, d); /* mask NaNs -> 0 */ d = vbslq_f32(m, d, vdupq_n_f32(0.0f)); +#if __SIZEOF_POINTER__ == 4 + // 32-bit ARM: accumulate in scalar double + float tmp[4]; + vst1q_f32(tmp, d); + for (int j = 0; j < 4; j++) { + sum += (double)tmp[j]; + } +#else + // 64-bit ARM: use NEON f64 operations float64x2_t lo = vcvt_f64_f32(vget_low_f32(d)); float64x2_t hi = vcvt_f64_f32(vget_high_f32(d)); acc = vaddq_f64(acc, lo); acc = vaddq_f64(acc, hi); +#endif } +#if __SIZEOF_POINTER__ != 4 double sum = vaddvq_f64(acc); +#endif for (; i < n; ++i) { uint16_t ai=a[i], bi=b[i]; From 2ed19c8cd0b28c78369fc970855309e5ce08ae37 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 5 Nov 2025 16:24:57 +0000 Subject: [PATCH 097/108] fix(distance-neon): update ARM pointer size checks to use _ARM32BIT_ macro for clarity --- src/distance-neon.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/distance-neon.c b/src/distance-neon.c index dd3b2f0..5900438 100644 --- a/src/distance-neon.c +++ b/src/distance-neon.c @@ -13,13 +13,18 @@ #if defined(__ARM_NEON) || defined(__ARM_NEON__) + +#if __SIZEOF_POINTER__ == 4 +#define _ARM32BIT_ 1 +#endif + #include extern distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX]; extern char *distance_backend_name; // Helper function for 32-bit ARM: vmaxv_u16 is not available in ARMv7 NEON -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ static inline uint16_t vmaxv_u16_compat(uint16x4_t v) { // Use pairwise max to reduce vector uint16x4_t m = vpmax_u16(v, v); // [max(v0,v1), max(v2,v3), max(v0,v1), max(v2,v3)] @@ -169,7 +174,7 @@ float bfloat16_distance_l2_impl_neon (const void *v1, const void *v2, int n, boo const uint16_t *a = (const uint16_t *)v1; const uint16_t *b = (const uint16_t *)v2; -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: use scalar double accumulation (no float64x2_t in NEON) double sum = 0.0; int i = 0; @@ -446,7 +451,7 @@ float float16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool const uint16x4_t SIGN_MASK = vdup_n_u16(0x8000u); const uint16x4_t ZERO16 = vdup_n_u16(0); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: use scalar double accumulation double sum = 0.0; int i = 0; @@ -487,7 +492,7 @@ float float16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool uint32x4_t m = vceqq_f32(d32, d32); /* true where not-NaN */ d32 = vbslq_f32(m, d32, vdupq_n_f32(0.0f)); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: accumulate in scalar double float tmp[4]; vst1q_f32(tmp, d32); @@ -509,7 +514,7 @@ float float16_distance_l2_impl_neon (const void *v1, const void *v2, int n, bool #endif } -#if __SIZEOF_POINTER__ != 4 +#ifndef _ARM32BIT_ double sum = vaddvq_f64(vaddq_f64(acc0, acc1)); #endif @@ -544,7 +549,7 @@ float float16_distance_cosine_neon (const void *v1, const void *v2, int n) { const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); const uint16x4_t ZERO16 = vdup_n_u16(0); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: use scalar double accumulation double dot = 0.0, normx = 0.0, normy = 0.0; int i = 0; @@ -576,7 +581,7 @@ float float16_distance_cosine_neon (const void *v1, const void *v2, int n) { ax = vbslq_f32(mx, ax, vdupq_n_f32(0.0f)); by = vbslq_f32(my, by, vdupq_n_f32(0.0f)); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: accumulate in scalar double float ax_tmp[4], by_tmp[4]; vst1q_f32(ax_tmp, ax); @@ -611,7 +616,7 @@ float float16_distance_cosine_neon (const void *v1, const void *v2, int n) { #endif } -#if __SIZEOF_POINTER__ != 4 +#ifndef _ARM32BIT_ double dot = vaddvq_f64(vaddq_f64(acc_dot_lo, acc_dot_hi)); double normx= vaddvq_f64(vaddq_f64(acc_a2_lo, acc_a2_hi)); double normy= vaddvq_f64(vaddq_f64(acc_b2_lo, acc_b2_hi)); @@ -649,7 +654,7 @@ float float16_distance_dot_neon (const void *v1, const void *v2, int n) { const uint16x4_t FRAC_MASK = vdup_n_u16(0x03FFu); const uint16x4_t ZERO16 = vdup_n_u16(0); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: use scalar double accumulation double dot = 0.0; int i = 0; @@ -675,7 +680,7 @@ float float16_distance_dot_neon (const void *v1, const void *v2, int n) { if (isnan(x) || isnan(y)) continue; double p = (double)x * (double)y; if (isinf(p)) return (p>0)? -INFINITY : INFINITY; -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ dot += p; #else acc_lo = vsetq_lane_f64(vgetq_lane_f64(acc_lo,0)+p, acc_lo, 0); /* cheap add */ @@ -695,7 +700,7 @@ float float16_distance_dot_neon (const void *v1, const void *v2, int n) { float32x4_t prod = vmulq_f32(ax, by); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: accumulate in scalar double float prod_tmp[4]; vst1q_f32(prod_tmp, prod); @@ -711,7 +716,7 @@ float float16_distance_dot_neon (const void *v1, const void *v2, int n) { #endif } -#if __SIZEOF_POINTER__ != 4 +#ifndef _ARM32BIT_ double dot = vaddvq_f64(vaddq_f64(acc_lo, acc_hi)); #endif @@ -739,7 +744,7 @@ float float16_distance_l1_neon (const void *v1, const void *v2, int n) { const uint16x4_t SIGN_MASK = vdup_n_u16(0x8000u); const uint16x4_t ZERO16 = vdup_n_u16(0); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: use scalar double accumulation double sum = 0.0; int i = 0; @@ -776,7 +781,7 @@ float float16_distance_l1_neon (const void *v1, const void *v2, int n) { uint32x4_t m = vceqq_f32(d, d); /* mask NaNs -> 0 */ d = vbslq_f32(m, d, vdupq_n_f32(0.0f)); -#if __SIZEOF_POINTER__ == 4 +#ifdef _ARM32BIT_ // 32-bit ARM: accumulate in scalar double float tmp[4]; vst1q_f32(tmp, d); @@ -792,7 +797,7 @@ float float16_distance_l1_neon (const void *v1, const void *v2, int n) { #endif } -#if __SIZEOF_POINTER__ != 4 +#ifndef _ARM32BIT_ double sum = vaddvq_f64(acc); #endif From b2f78a7d39520fc154ee5fc933c66066dd88df29 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 5 Nov 2025 17:03:52 +0000 Subject: [PATCH 098/108] Bump version to 0.9.52 --- src/sqlite-vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 17f57f6..1c695a3 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.51" +#define SQLITE_VECTOR_VERSION "0.9.52" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From c46fa73aa51af0ba0a10447715e46295220224a2 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Wed, 5 Nov 2025 17:18:11 +0000 Subject: [PATCH 099/108] fix(workflow): differentiate error and warning for artifact size increase based on event type --- .github/workflows/main.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d80183d..e4384e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -267,8 +267,12 @@ jobs: # Check if increase is more than 5% if (( $(echo "$INCREASE > 5" | bc -l) )); then - echo "❌ ERROR: $artifact size increased by ${INCREASE}% (limit: 5%)" - FAILED=1 + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + echo "⚠️ WARNING: $artifact size increased by ${INCREASE}% (limit: 5%)" + else + echo "❌ ERROR: $artifact size increased by ${INCREASE}% (limit: 5%)" + FAILED=1 + fi fi done From 5ae9bf431d704bf75f352df74db3a8d4e437d9d9 Mon Sep 17 00:00:00 2001 From: Teddy ALBINA Date: Wed, 17 Dec 2025 14:25:45 +0100 Subject: [PATCH 100/108] Add avx512 computation --- src/distance-avx512.c | 887 ++++++++++++++++++++++++++++++++++++++++++ src/distance-avx512.h | 15 + src/distance-cpu.c | 63 ++- src/sqlite-vector.h | 2 +- 4 files changed, 965 insertions(+), 2 deletions(-) create mode 100644 src/distance-avx512.c create mode 100644 src/distance-avx512.h diff --git a/src/distance-avx512.c b/src/distance-avx512.c new file mode 100644 index 0000000..701dbee --- /dev/null +++ b/src/distance-avx512.c @@ -0,0 +1,887 @@ +// +// distance-avx512.c +// sqlitevector +// +// Converted to AVX-512 +// + +#include "distance-avx512.h" +#include "distance-cpu.h" + +// Check for AVX512 Foundation (F) and Byte/Word (BW) which are standard on Skylake-X/IceLake+ +#if defined(__AVX512F__) && defined(__AVX512BW__) +#include +#include +#include + +extern distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX]; +extern char* distance_backend_name; + +// Abs for f32 (AVX512F has native abs) +#define _mm512_abs_ps(x) _mm512_abs_ps(x) + +// Horizontal sum for __m512 (f32) -> float +static inline float hsum512_ps(__m512 v) { + return _mm512_reduce_add_ps(v); +} + +// Horizontal sum for __m512d (f64) -> double +static inline double hsum512d(__m512d v) { + return _mm512_reduce_add_pd(v); +} + +// Helper: Horizontal sum for __m256i (used in int accumulators if we reduce to 256 first) +// But for AVX512 we usually reduce the full ZMM. +static inline uint32_t hsum512_epi32(__m512i v) { + return _mm512_reduce_add_epi32(v); +} + +// per-block Inf mismatch test on 16 lanes (returns true if L1/L2 should be +Inf) +static inline bool block_has_l2_inf_mismatch_16(const uint16_t* a, const uint16_t* b) { + /* mismatch if (a_inf ^ b_inf) OR (both Inf and signs differ) */ + /* This loop is scalar, but checked per block of 16 to match vector stride */ + for (int k = 0; k < 16; ++k) { + uint16_t ak = a[k], bk = b[k]; + bool ai = f16_is_inf(ak), bi = f16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (f16_sign(ak) != f16_sign(bk)))) return true; + } + return false; +} + +/* 16bf16 -> 16f32: widen to u32, shift <<16, reinterpret as f32 */ +static inline __m512 bf16x16_to_f32x16_loadu(const uint16_t* p) { + // Load 16x u16 (256 bits) + __m256i v16 = _mm256_loadu_si256((const __m256i*)p); + // Widen to 16x u32 (512 bits) + __m512i v32 = _mm512_cvtepu16_epi32(v16); + // Shift left 16 + v32 = _mm512_slli_epi32(v32, 16); + // Bitcast to f32 + return _mm512_castsi512_ps(v32); +} + +/* Any lane has infinite difference? (a_inf ^ b_inf) || (both inf and signs differ) */ +static inline bool block_has_l2_inf_mismatch_bf16_16(const uint16_t* a, const uint16_t* b) { + for (int k = 0; k < 16; ++k) { + uint16_t ak = a[k], bk = b[k]; + bool ai = bfloat16_is_inf(ak), bi = bfloat16_is_inf(bk); + if ((ai ^ bi) || (ai && bi && (bfloat16_sign(ak) != bfloat16_sign(bk)))) return true; + } + return false; +} + + +// MARK: - FLOAT32 - + +static inline float float32_distance_l2_impl_avx512(const void* v1, const void* v2, int n, bool use_sqrt) { + const float* a = (const float*)v1; + const float* b = (const float*)v2; + + __m512 acc = _mm512_setzero_ps(); + int i = 0; + + // Stride 16 for AVX-512 + for (; i <= n - 16; i += 16) { + __m512 va = _mm512_loadu_ps(a + i); + __m512 vb = _mm512_loadu_ps(b + i); + __m512 diff = _mm512_sub_ps(va, vb); + acc = _mm512_fmadd_ps(diff, diff, acc); + } + + float total = hsum512_ps(acc); + + for (; i < n; ++i) { + float d = a[i] - b[i]; + total += d * d; + } + + return use_sqrt ? sqrtf(total) : total; +} + +float float32_distance_l2_avx512(const void* v1, const void* v2, int n) { + return float32_distance_l2_impl_avx512(v1, v2, n, true); +} + +float float32_distance_l2_squared_avx512(const void* v1, const void* v2, int n) { + return float32_distance_l2_impl_avx512(v1, v2, n, false); +} + +float float32_distance_l1_avx512(const void* v1, const void* v2, int n) { + const float* a = (const float*)v1; + const float* b = (const float*)v2; + + __m512 acc = _mm512_setzero_ps(); + int i = 0; + + for (; i <= n - 16; i += 16) { + __m512 va = _mm512_loadu_ps(a + i); + __m512 vb = _mm512_loadu_ps(b + i); + __m512 diff = _mm512_sub_ps(va, vb); + acc = _mm512_add_ps(acc, _mm512_abs_ps(diff)); + } + + float total = hsum512_ps(acc); + + for (; i < n; ++i) { + total += fabsf(a[i] - b[i]); + } + + return total; +} + +float float32_distance_dot_avx512(const void* v1, const void* v2, int n) { + const float* a = (const float*)v1; + const float* b = (const float*)v2; + + __m512 acc = _mm512_setzero_ps(); + int i = 0; + + for (; i <= n - 16; i += 16) { + __m512 va = _mm512_loadu_ps(a + i); + __m512 vb = _mm512_loadu_ps(b + i); + acc = _mm512_fmadd_ps(va, vb, acc); + } + + float total = hsum512_ps(acc); + + for (; i < n; ++i) { + total += a[i] * b[i]; + } + + return -total; +} + +float float32_distance_cosine_avx512(const void* a, const void* b, int n) { + float dot = -float32_distance_dot_avx512(a, b, n); + float norm_a = sqrtf(-float32_distance_dot_avx512(a, a, n)); + float norm_b = sqrtf(-float32_distance_dot_avx512(b, b, n)); + + if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f; + + float cosine_similarity = dot / (norm_a * norm_b); + return 1.0f - cosine_similarity; +} + +// MARK: - FLOAT16 - + +static inline float float16_distance_l2_impl_avx512(const void* v1, const void* v2, int n, bool use_sqrt) { + const uint16_t* a = (const uint16_t*)v1; + const uint16_t* b = (const uint16_t*)v2; + + // Accumulate in double (2x __m512d) + __m512d acc0 = _mm512_setzero_pd(); + __m512d acc1 = _mm512_setzero_pd(); + int i = 0; + + for (; i <= n - 16; i += 16) { + /* Inf mismatch => distance is +Inf */ + if (block_has_l2_inf_mismatch_16(a + i, b + i)) return INFINITY; + + // Load 16x f16 (256 bits) + __m256i va_h = _mm256_loadu_si256((const __m256i*)(a + i)); + __m256i vb_h = _mm256_loadu_si256((const __m256i*)(b + i)); + + // Convert to f32 + __m512 va = _mm512_cvtph_ps(va_h); + __m512 vb = _mm512_cvtph_ps(vb_h); + + // Check for NaN to zero them out (matching original logic) + // Original logic: if (isnan(ak) || isnan(bk)) diff = 0 + // In AVX512, cvtph_ps preserves NaN. + __m512 d = _mm512_sub_ps(va, vb); + + // Mask: keep where NOT NaN. If input was NaN, sub result is NaN. + // We want to treat (NaN - x) or (x - NaN) as 0.0 contribution. + // Or strictly: if a[k] or b[k] is NaN. + // _mm512_cmp_ps_mask(x, x, _CMP_ORD_Q) is true if not NaN. + __mmask16 mask_a = _mm512_cmp_ps_mask(va, va, _CMP_ORD_Q); + __mmask16 mask_b = _mm512_cmp_ps_mask(vb, vb, _CMP_ORD_Q); + __mmask16 mask_valid = mask_a & mask_b; + + // If not valid, set d to 0.0 + d = _mm512_mask_set1_ps(d, ~mask_valid, 0.0f); + + // Widen to f64 and accumulate + __m256 d_lo = _mm512_castps512_ps256(d); + __m256 d_hi = _mm512_extractf32x8_ps(d, 1); + + __m512d dlo_d = _mm512_cvtps_pd(d_lo); + __m512d dhi_d = _mm512_cvtps_pd(d_hi); + + acc0 = _mm512_fmadd_pd(dlo_d, dlo_d, acc0); + acc1 = _mm512_fmadd_pd(dhi_d, dhi_d, acc1); + } + + double sum = hsum512d(acc0) + hsum512d(acc1); + + /* scalar tail with same NaN/Inf policy */ + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai) == f16_sign(bi))) return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + double d = (double)float16_to_float32(ai) - (double)float16_to_float32(bi); + sum = fma(d, d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} + +float float16_distance_l2_avx512(const void* v1, const void* v2, int n) { + return float16_distance_l2_impl_avx512(v1, v2, n, true); +} + +float float16_distance_l2_squared_avx512(const void* v1, const void* v2, int n) { + return float16_distance_l2_impl_avx512(v1, v2, n, false); +} + +float float16_distance_l1_avx512(const void* v1, const void* v2, int n) { + const uint16_t* a = (const uint16_t*)v1; + const uint16_t* b = (const uint16_t*)v2; + + __m512d acc0 = _mm512_setzero_pd(); + __m512d acc1 = _mm512_setzero_pd(); + int i = 0; + + for (; i <= n - 16; i += 16) { + if (block_has_l2_inf_mismatch_16(a + i, b + i)) return INFINITY; + + __m256i va_h = _mm256_loadu_si256((const __m256i*)(a + i)); + __m256i vb_h = _mm256_loadu_si256((const __m256i*)(b + i)); + + __m512 va = _mm512_cvtph_ps(va_h); + __m512 vb = _mm512_cvtph_ps(vb_h); + + __m512 d = _mm512_abs_ps(_mm512_sub_ps(va, vb)); + + // Zero out NaNs + __mmask16 mask_a = _mm512_cmp_ps_mask(va, va, _CMP_ORD_Q); + __mmask16 mask_b = _mm512_cmp_ps_mask(vb, vb, _CMP_ORD_Q); + d = _mm512_mask_set1_ps(d, ~(mask_a & mask_b), 0.0f); + + // Convert to double to accumulate + __m256 d_lo = _mm512_castps512_ps256(d); + __m256 d_hi = _mm512_extractf32x8_ps(d, 1); + + acc0 = _mm512_add_pd(acc0, _mm512_cvtps_pd(d_lo)); + acc1 = _mm512_add_pd(acc1, _mm512_cvtps_pd(d_hi)); + } + + double sum = hsum512d(acc0) + hsum512d(acc1); + + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if ((f16_is_inf(ai) || f16_is_inf(bi)) && !(f16_is_inf(ai) && f16_is_inf(bi) && f16_sign(ai) == f16_sign(bi))) return INFINITY; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + sum += fabs((double)float16_to_float32(ai) - (double)float16_to_float32(bi)); + } + + return (float)sum; +} + +float float16_distance_dot_avx512(const void* v1, const void* v2, int n) { + const uint16_t* a = (const uint16_t*)v1; + const uint16_t* b = (const uint16_t*)v2; + + __m512d acc0 = _mm512_setzero_pd(); + __m512d acc1 = _mm512_setzero_pd(); + int i = 0; + + for (; i <= n - 16; i += 16) { + // Scalar check for Inf/NaN edge cases in the block + for (int k = 0; k < 16; ++k) { + uint16_t ak = a[i + k], bk = b[i + k]; + if (f16_is_nan(ak) || f16_is_nan(bk)) continue; + bool ai = f16_is_inf(ak), bi = f16_is_inf(bk); + if (ai || bi) { + if ((ai && f16_is_zero(bk)) || (bi && f16_is_zero(ak))) { + // Inf * 0 -> NaN (ignore) + } + else { + int s = (f16_sign(ak) ^ f16_sign(bk)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; + } + } + } + + __m256i va_h = _mm256_loadu_si256((const __m256i*)(a + i)); + __m256i vb_h = _mm256_loadu_si256((const __m256i*)(b + i)); + + __m512 va = _mm512_cvtph_ps(va_h); + __m512 vb = _mm512_cvtph_ps(vb_h); + + // Zero out NaNs + __mmask16 mask_a = _mm512_cmp_ps_mask(va, va, _CMP_ORD_Q); + __mmask16 mask_b = _mm512_cmp_ps_mask(vb, vb, _CMP_ORD_Q); + + va = _mm512_mask_set1_ps(va, ~mask_a, 0.0f); + vb = _mm512_mask_set1_ps(vb, ~mask_b, 0.0f); + + // This multiply might generate Infs, but we checked scalar first. + // We still need to handle the case where standard float math generates Inf from finite * finite? + // The original code checks isinf(p). + __m512 p = _mm512_mul_ps(va, vb); + + // Convert to double + __m256 p_lo = _mm512_castps512_ps256(p); + __m256 p_hi = _mm512_extractf32x8_ps(p, 1); + + acc0 = _mm512_add_pd(acc0, _mm512_cvtps_pd(p_lo)); + acc1 = _mm512_add_pd(acc1, _mm512_cvtps_pd(p_hi)); + } + + double dot = hsum512d(acc0) + hsum512d(acc1); + + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if (f16_is_nan(ai) || f16_is_nan(bi)) continue; + bool aiinf = f16_is_inf(ai), biinf = f16_is_inf(bi); + if (aiinf || biinf) { + if ((aiinf && f16_is_zero(bi)) || (biinf && f16_is_zero(ai))) { + } + else { + int s = (f16_sign(ai) ^ f16_sign(bi)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; + } + } + else { + float x = float16_to_float32(ai); + float y = float16_to_float32(bi); + double p = (double)x * (double)y; + if (isinf(p)) return (p > 0) ? -INFINITY : INFINITY; + if (!isnan(p)) dot += p; + } + } + + return (float)(-dot); +} + +float float16_distance_cosine_avx512(const void* va, const void* vb, int n) { + const uint16_t* a = (const uint16_t*)va; + const uint16_t* b = (const uint16_t*)vb; + + for (int i = 0; i < n; ++i) { + if (f16_is_inf(a[i]) || f16_is_inf(b[i])) return 1.0f; + } + + float dot = -float16_distance_dot_avx512(a, b, n); + float norm_a = sqrtf(-float16_distance_dot_avx512(a, a, n)); + float norm_b = sqrtf(-float16_distance_dot_avx512(b, b, n)); + + if (!(norm_a > 0.0f) || !(norm_b > 0.0f) || !isfinite(norm_a) || !isfinite(norm_b) || !isfinite(dot)) + return 1.0f; + + float cosine = dot / (norm_a * norm_b); + if (cosine > 1.0f) cosine = 1.0f; + if (cosine < -1.0f) cosine = -1.0f; + return 1.0f - cosine; +} + + +// MARK: - BFLOAT16 - + +static inline float bfloat16_distance_l2_impl_avx512(const void* v1, const void* v2, int n, bool use_sqrt) { + const uint16_t* a = (const uint16_t*)v1; + const uint16_t* b = (const uint16_t*)v2; + + __m512d acc0 = _mm512_setzero_pd(); + __m512d acc1 = _mm512_setzero_pd(); + int i = 0; + + for (; i <= n - 16; i += 16) { + if (block_has_l2_inf_mismatch_bf16_16(a + i, b + i)) return INFINITY; + + __m512 af = bf16x16_to_f32x16_loadu(a + i); + __m512 bf = bf16x16_to_f32x16_loadu(b + i); + + // Extract halves to convert to double (precision) + __m256 af_lo = _mm512_castps512_ps256(af); + __m256 af_hi = _mm512_extractf32x8_ps(af, 1); + __m256 bf_lo = _mm512_castps512_ps256(bf); + __m256 bf_hi = _mm512_extractf32x8_ps(bf, 1); + + __m512d a0 = _mm512_cvtps_pd(af_lo); + __m512d a1 = _mm512_cvtps_pd(af_hi); + __m512d b0 = _mm512_cvtps_pd(bf_lo); + __m512d b1 = _mm512_cvtps_pd(bf_hi); + + __m512d d0 = _mm512_sub_pd(a0, b0); + __m512d d1 = _mm512_sub_pd(a1, b1); + + /* zero-out NaNs */ + __mmask8 m0 = _mm512_cmp_pd_mask(d0, d0, _CMP_ORD_Q); + __mmask8 m1 = _mm512_cmp_pd_mask(d1, d1, _CMP_ORD_Q); + d0 = _mm512_mask_set1_pd(d0, ~m0, 0.0); + d1 = _mm512_mask_set1_pd(d1, ~m1, 0.0); + + acc0 = _mm512_fmadd_pd(d0, d0, acc0); + acc1 = _mm512_fmadd_pd(d1, d1, acc1); + } + + double sum = hsum512d(acc0) + hsum512d(acc1); + + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if ((bfloat16_is_inf(ai) || bfloat16_is_inf(bi)) && !(bfloat16_is_inf(ai) && bfloat16_is_inf(bi) && bfloat16_sign(ai) == bfloat16_sign(bi))) return INFINITY; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + double d = (double)bfloat16_to_float32(ai) - (double)bfloat16_to_float32(bi); + sum = fma(d, d, sum); + } + + return use_sqrt ? (float)sqrt(sum) : (float)sum; +} + +float bfloat16_distance_l2_avx512(const void* v1, const void* v2, int n) { + return bfloat16_distance_l2_impl_avx512(v1, v2, n, true); +} + +float bfloat16_distance_l2_squared_avx512(const void* v1, const void* v2, int n) { + return bfloat16_distance_l2_impl_avx512(v1, v2, n, false); +} + +float bfloat16_distance_l1_avx512(const void* v1, const void* v2, int n) { + const uint16_t* a = (const uint16_t*)v1; + const uint16_t* b = (const uint16_t*)v2; + + __m512d acc0 = _mm512_setzero_pd(); + __m512d acc1 = _mm512_setzero_pd(); + int i = 0; + + for (; i <= n - 16; i += 16) { + if (block_has_l2_inf_mismatch_bf16_16(a + i, b + i)) return INFINITY; + + __m512 af = bf16x16_to_f32x16_loadu(a + i); + __m512 bf = bf16x16_to_f32x16_loadu(b + i); + + __m256 af_lo = _mm512_castps512_ps256(af); + __m256 af_hi = _mm512_extractf32x8_ps(af, 1); + __m256 bf_lo = _mm512_castps512_ps256(bf); + __m256 bf_hi = _mm512_extractf32x8_ps(bf, 1); + + __m512d d0 = _mm512_sub_pd(_mm512_cvtps_pd(af_lo), _mm512_cvtps_pd(bf_lo)); + __m512d d1 = _mm512_sub_pd(_mm512_cvtps_pd(af_hi), _mm512_cvtps_pd(bf_hi)); + + d0 = _mm512_abs_pd(d0); + d1 = _mm512_abs_pd(d1); + + // NaN -> 0 + __mmask8 m0 = _mm512_cmp_pd_mask(d0, d0, _CMP_ORD_Q); + __mmask8 m1 = _mm512_cmp_pd_mask(d1, d1, _CMP_ORD_Q); + d0 = _mm512_mask_set1_pd(d0, ~m0, 0.0); + d1 = _mm512_mask_set1_pd(d1, ~m1, 0.0); + + acc0 = _mm512_add_pd(acc0, d0); + acc1 = _mm512_add_pd(acc1, d1); + } + + double sum = hsum512d(acc0) + hsum512d(acc1); + + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if ((bfloat16_is_inf(ai) || bfloat16_is_inf(bi)) && !(bfloat16_is_inf(ai) && bfloat16_is_inf(bi) && bfloat16_sign(ai) == bfloat16_sign(bi))) return INFINITY; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + sum += fabs((double)bfloat16_to_float32(ai) - (double)bfloat16_to_float32(bi)); + } + + return (float)sum; +} + +float bfloat16_distance_dot_avx512(const void* v1, const void* v2, int n) { + const uint16_t* a = (const uint16_t*)v1; + const uint16_t* b = (const uint16_t*)v2; + + __m512d acc0 = _mm512_setzero_pd(); + __m512d acc1 = _mm512_setzero_pd(); + int i = 0; + + for (; i <= n - 16; i += 16) { + for (int k = 0; k < 16; ++k) { + uint16_t ak = a[i + k], bk = b[i + k]; + bool ai = bfloat16_is_inf(ak), bi = bfloat16_is_inf(bk); + if (ai || bi) { + if ((ai && bfloat16_is_zero(bk)) || (bi && bfloat16_is_zero(ak))) { + continue; + } + else { + int s = (bfloat16_sign(ak) ^ bfloat16_sign(bk)) ? -1 : +1; + return s < 0 ? INFINITY : -INFINITY; + } + } + } + + __m512 af = bf16x16_to_f32x16_loadu(a + i); + __m512 bf = bf16x16_to_f32x16_loadu(b + i); + + // NaN -> 0 + __mmask16 ma = _mm512_cmp_ps_mask(af, af, _CMP_ORD_Q); + __mmask16 mb = _mm512_cmp_ps_mask(bf, bf, _CMP_ORD_Q); + af = _mm512_mask_set1_ps(af, ~ma, 0.0f); + bf = _mm512_mask_set1_ps(bf, ~mb, 0.0f); + + __m512 prod = _mm512_mul_ps(af, bf); + + __m256 lo = _mm512_castps512_ps256(prod); + __m256 hi = _mm512_extractf32x8_ps(prod, 1); + __m512d d0 = _mm512_cvtps_pd(lo); + __m512d d1 = _mm512_cvtps_pd(hi); + + acc0 = _mm512_add_pd(acc0, d0); + acc1 = _mm512_add_pd(acc1, d1); + } + + double dot = hsum512d(acc0) + hsum512d(acc1); + + for (; i < n; ++i) { + uint16_t ai = a[i], bi = b[i]; + if (bfloat16_is_nan(ai) || bfloat16_is_nan(bi)) continue; + bool aiinf = bfloat16_is_inf(ai), biinf = bfloat16_is_inf(bi); + if (aiinf || biinf) { + if ((aiinf && bfloat16_is_zero(bi)) || (biinf && bfloat16_is_zero(ai))) { + } + else { + int sgn = (bfloat16_sign(ai) ^ bfloat16_sign(bi)) ? -1 : +1; + return sgn < 0 ? INFINITY : -INFINITY; + } + } + else { + double p = (double)bfloat16_to_float32(ai) * (double)bfloat16_to_float32(bi); + dot += p; + } + } + + return (float)(-dot); +} + +float bfloat16_distance_cosine_avx512(const void* v1, const void* v2, int n) { + float dot = -bfloat16_distance_dot_avx512(v1, v2, n); + float norm_a = sqrtf(-bfloat16_distance_dot_avx512(v1, v1, n)); + float norm_b = sqrtf(-bfloat16_distance_dot_avx512(v2, v2, n)); + + if (!(norm_a > 0.0f) || !(norm_b > 0.0f) || !isfinite(norm_a) || !isfinite(norm_b) || !isfinite(dot)) + return 1.0f; + + float cs = dot / (norm_a * norm_b); + if (cs > 1.0f) cs = 1.0f; + if (cs < -1.0f) cs = -1.0f; + return 1.0f - cs; +} + + +// MARK: - UINT8 - + +static inline float uint8_distance_l2_impl_avx512(const void* v1, const void* v2, int n, bool use_sqrt) { + const uint8_t* a = (const uint8_t*)v1; + const uint8_t* b = (const uint8_t*)v2; + + __m512i acc = _mm512_setzero_si512(); + int i = 0; + + // Process 64 elements at a time (64 bytes = 512 bits) + for (; i <= n - 64; i += 64) { + __m512i va = _mm512_loadu_si512((const void*)(a + i)); + __m512i vb = _mm512_loadu_si512((const void*)(b + i)); + + // Split 64x u8 into 2x 32x u16 (Low 32 bytes and High 32 bytes of 512 register) + + // 1. Lower 32 bytes -> 32x u16 + __m256i va_half_lo = _mm512_castsi512_si256(va); + __m256i vb_half_lo = _mm512_castsi512_si256(vb); + __m512i va_16_lo = _mm512_cvtepu8_epi16(va_half_lo); + __m512i vb_16_lo = _mm512_cvtepu8_epi16(vb_half_lo); + + // 2. Upper 32 bytes -> 32x u16 + __m256i va_half_hi = _mm512_extracti64x4_epi64(va, 1); + __m256i vb_half_hi = _mm512_extracti64x4_epi64(vb, 1); + __m512i va_16_hi = _mm512_cvtepu8_epi16(va_half_hi); + __m512i vb_16_hi = _mm512_cvtepu8_epi16(vb_half_hi); + + // Compute diffs (16-bit) + __m512i d_lo = _mm512_sub_epi16(va_16_lo, vb_16_lo); + __m512i d_hi = _mm512_sub_epi16(va_16_hi, vb_16_hi); + + // Square diffs (16-bit result) + __m512i s_lo = _mm512_mullo_epi16(d_lo, d_lo); + __m512i s_hi = _mm512_mullo_epi16(d_hi, d_hi); + + // Widen to 32-bit and accumulate. + // Each 512-bit register of 16-bit ints splits into TWO 512-bit registers of 32-bit ints. + // s_lo splits into s_lo_0, s_lo_1 + + __m256i s_lo_half = _mm512_castsi512_si256(s_lo); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(s_lo_half)); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(s_lo, 1))); + + __m256i s_hi_half = _mm512_castsi512_si256(s_hi); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(s_hi_half)); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(s_hi, 1))); + } + + uint32_t total = hsum512_epi32(acc); + + // Tail loop + for (; i < n; ++i) { + int d = (int)a[i] - (int)b[i]; + total += d * d; + } + + return use_sqrt ? sqrtf((float)total) : (float)total; +} + +float uint8_distance_l2_avx512(const void* v1, const void* v2, int n) { + return uint8_distance_l2_impl_avx512(v1, v2, n, true); +} + +float uint8_distance_l2_squared_avx512(const void* v1, const void* v2, int n) { + return uint8_distance_l2_impl_avx512(v1, v2, n, false); +} + +float uint8_distance_dot_avx512(const void* v1, const void* v2, int n) { + const uint8_t* a = (const uint8_t*)v1; + const uint8_t* b = (const uint8_t*)v2; + + __m512i acc = _mm512_setzero_si512(); + int i = 0; + + for (; i <= n - 64; i += 64) { + __m512i va = _mm512_loadu_si512((const void*)(a + i)); + __m512i vb = _mm512_loadu_si512((const void*)(b + i)); + + __m512i va_16_lo = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(va)); + __m512i vb_16_lo = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(vb)); + __m512i va_16_hi = _mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(va, 1)); + __m512i vb_16_hi = _mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(vb, 1)); + + __m512i p_lo = _mm512_mullo_epi16(va_16_lo, vb_16_lo); + __m512i p_hi = _mm512_mullo_epi16(va_16_hi, vb_16_hi); + + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(p_lo))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(p_lo, 1))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(p_hi))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(p_hi, 1))); + } + + uint32_t total = hsum512_epi32(acc); + + for (; i < n; ++i) { + total += a[i] * b[i]; + } + + return -(float)total; +} + +float uint8_distance_l1_avx512(const void* v1, const void* v2, int n) { + const uint8_t* a = (const uint8_t*)v1; + const uint8_t* b = (const uint8_t*)v2; + + __m512i acc = _mm512_setzero_si512(); + int i = 0; + + for (; i <= n - 64; i += 64) { + __m512i va = _mm512_loadu_si512((const void*)(a + i)); + __m512i vb = _mm512_loadu_si512((const void*)(b + i)); + + __m512i va_16_lo = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(va)); + __m512i vb_16_lo = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(vb)); + __m512i va_16_hi = _mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(va, 1)); + __m512i vb_16_hi = _mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(vb, 1)); + + // abs(a-b) in 16-bit + // Note: AVX512BW has _mm512_abs_epi16 + __m512i d_lo = _mm512_abs_epi16(_mm512_sub_epi16(va_16_lo, vb_16_lo)); + __m512i d_hi = _mm512_abs_epi16(_mm512_sub_epi16(va_16_hi, vb_16_hi)); + + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(d_lo))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(d_lo, 1))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(d_hi))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(d_hi, 1))); + } + + uint32_t total = hsum512_epi32(acc); + + for (; i < n; ++i) { + total += abs((int)a[i] - (int)b[i]); + } + + return (float)total; +} + +float uint8_distance_cosine_avx512(const void* a, const void* b, int n) { + float dot = -uint8_distance_dot_avx512(a, b, n); + float norm_a = sqrtf(-uint8_distance_dot_avx512(a, a, n)); + float norm_b = sqrtf(-uint8_distance_dot_avx512(b, b, n)); + + if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f; + + float cosine_similarity = dot / (norm_a * norm_b); + return 1.0f - cosine_similarity; +} + + +// MARK: - INT8 - + +static inline float int8_distance_l2_impl_avx512(const void* v1, const void* v2, int n, bool use_sqrt) { + const int8_t* a = (const int8_t*)v1; + const int8_t* b = (const int8_t*)v2; + + __m512i acc = _mm512_setzero_si512(); + int i = 0; + + for (; i <= n - 64; i += 64) { + __m512i va = _mm512_loadu_si512((const void*)(a + i)); + __m512i vb = _mm512_loadu_si512((const void*)(b + i)); + + // Sign extend int8 to int16. + // _mm512_cvtepi8_epi16 behaves exactly like cvtepu8 but for signed. + __m512i va_16_lo = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(va)); + __m512i vb_16_lo = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(vb)); + __m512i va_16_hi = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(va, 1)); + __m512i vb_16_hi = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(vb, 1)); + + __m512i d_lo = _mm512_sub_epi16(va_16_lo, vb_16_lo); + __m512i d_hi = _mm512_sub_epi16(va_16_hi, vb_16_hi); + + __m512i s_lo = _mm512_mullo_epi16(d_lo, d_lo); + __m512i s_hi = _mm512_mullo_epi16(d_hi, d_hi); + + // Sign extend 16 to 32 and add (results of square are positive, but keeping types consistent) + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_castsi512_si256(s_lo))); + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(s_lo, 1))); + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_castsi512_si256(s_hi))); + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(s_hi, 1))); + } + + uint32_t total = hsum512_epi32(acc); + + for (; i < n; ++i) { + int d = (int)a[i] - (int)b[i]; + total += d * d; + } + + return use_sqrt ? sqrtf((float)total) : (float)total; +} + +float int8_distance_l2_avx512(const void* v1, const void* v2, int n) { + return int8_distance_l2_impl_avx512(v1, v2, n, true); +} + +float int8_distance_l2_squared_avx512(const void* v1, const void* v2, int n) { + return int8_distance_l2_impl_avx512(v1, v2, n, false); +} + +float int8_distance_dot_avx512(const void* v1, const void* v2, int n) { + const int8_t* a = (const int8_t*)v1; + const int8_t* b = (const int8_t*)v2; + + __m512i acc = _mm512_setzero_si512(); + int i = 0; + + for (; i <= n - 64; i += 64) { + __m512i va = _mm512_loadu_si512((const void*)(a + i)); + __m512i vb = _mm512_loadu_si512((const void*)(b + i)); + + __m512i va_16_lo = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(va)); + __m512i vb_16_lo = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(vb)); + __m512i va_16_hi = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(va, 1)); + __m512i vb_16_hi = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(vb, 1)); + + __m512i p_lo = _mm512_mullo_epi16(va_16_lo, vb_16_lo); + __m512i p_hi = _mm512_mullo_epi16(va_16_hi, vb_16_hi); + + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_castsi512_si256(p_lo))); + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(p_lo, 1))); + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_castsi512_si256(p_hi))); + acc = _mm512_add_epi32(acc, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(p_hi, 1))); + } + + int32_t total = (int32_t)hsum512_epi32(acc); + + for (; i < n; ++i) { + total += (int)a[i] * (int)b[i]; + } + + return -(float)total; +} + +float int8_distance_l1_avx512(const void* v1, const void* v2, int n) { + const int8_t* a = (const int8_t*)v1; + const int8_t* b = (const int8_t*)v2; + + __m512i acc = _mm512_setzero_si512(); + int i = 0; + + for (; i <= n - 64; i += 64) { + __m512i va = _mm512_loadu_si512((const void*)(a + i)); + __m512i vb = _mm512_loadu_si512((const void*)(b + i)); + + __m512i va_16_lo = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(va)); + __m512i vb_16_lo = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(vb)); + __m512i va_16_hi = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(va, 1)); + __m512i vb_16_hi = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(vb, 1)); + + __m512i d_lo = _mm512_abs_epi16(_mm512_sub_epi16(va_16_lo, vb_16_lo)); + __m512i d_hi = _mm512_abs_epi16(_mm512_sub_epi16(va_16_hi, vb_16_hi)); + + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(d_lo))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(d_lo, 1))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(d_hi))); + acc = _mm512_add_epi32(acc, _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(d_hi, 1))); + } + + int32_t total = (int32_t)hsum512_epi32(acc); + + for (; i < n; ++i) { + total += abs((int)a[i] - (int)b[i]); + } + + return (float)total; +} + +float int8_distance_cosine_avx512(const void* a, const void* b, int n) { + float dot = -int8_distance_dot_avx512(a, b, n); + float norm_a = sqrtf(-int8_distance_dot_avx512(a, a, n)); + float norm_b = sqrtf(-int8_distance_dot_avx512(b, b, n)); + + if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f; + + float cosine_similarity = dot / (norm_a * norm_b); + return 1.0f - cosine_similarity; +} + +#endif + +// MARK: - + +void init_distance_functions_avx512(void) { +#if defined(__AVX512F__) && defined(__AVX512BW__) + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F32] = float32_distance_l2_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_F16] = float16_distance_l2_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_U8] = uint8_distance_l2_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L2][VECTOR_TYPE_I8] = int8_distance_l2_avx512; + + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F32] = float32_distance_l2_squared_avx512; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_F16] = float16_distance_l2_squared_avx512; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_BF16] = bfloat16_distance_l2_squared_avx512; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_U8] = uint8_distance_l2_squared_avx512; + dispatch_distance_table[VECTOR_DISTANCE_SQUARED_L2][VECTOR_TYPE_I8] = int8_distance_l2_squared_avx512; + + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F32] = float32_distance_cosine_avx512; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_F16] = float16_distance_cosine_avx512; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_BF16] = bfloat16_distance_cosine_avx512; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_U8] = uint8_distance_cosine_avx512; + dispatch_distance_table[VECTOR_DISTANCE_COSINE][VECTOR_TYPE_I8] = int8_distance_cosine_avx512; + + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F32] = float32_distance_dot_avx512; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_F16] = float16_distance_dot_avx512; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_BF16] = bfloat16_distance_dot_avx512; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_U8] = uint8_distance_dot_avx512; + dispatch_distance_table[VECTOR_DISTANCE_DOT][VECTOR_TYPE_I8] = int8_distance_dot_avx512; + + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F32] = float32_distance_l1_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_F16] = float16_distance_l1_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_BF16] = bfloat16_distance_l1_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_avx512; + dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_avx512; + + distance_backend_name = "AVX512"; +#endif +} \ No newline at end of file diff --git a/src/distance-avx512.h b/src/distance-avx512.h new file mode 100644 index 0000000..3b1198f --- /dev/null +++ b/src/distance-avx512.h @@ -0,0 +1,15 @@ +// +// distance-avx512.h +// sqlitevector +// +// Created by Teddy Albina on 17/12/25. +// + +#ifndef __VECTOR_DISTANCE_AVX512__ +#define __VECTOR_DISTANCE_AVX512__ + +#include + +void init_distance_functions_avx512(void); + +#endif diff --git a/src/distance-cpu.c b/src/distance-cpu.c index 73d722d..5646526 100644 --- a/src/distance-cpu.c +++ b/src/distance-cpu.c @@ -16,6 +16,7 @@ #include "distance-neon.h" #include "distance-sse2.h" #include "distance-avx2.h" +#include "distance-avx512.h" char *distance_backend_name = "CPU"; distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX] = {0}; @@ -707,6 +708,25 @@ float int8_distance_l1_cpu (const void *v1, const void *v2, int n) { #endif } + void run_cpuid(int leaf, int subleaf, int result[4]) { + #if defined(_MSC_VER) + __cpuidex(result, leaf, subleaf); + #else + __cpuid_count(leaf, subleaf, result[0], result[1], result[2], result[3]); + #endif + } + + uint64_t run_xgetbv(uint32_t xcr) { + #if defined(_MSC_VER) + return _xgetbv(xcr); + #else + uint32_t eax, edx; + // xgetbv instruction: reads XCR specified by ecx into edx:eax + __asm__ volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(xcr)); + return ((uint64_t)edx << 32) | eax; + #endif + } + bool cpu_supports_avx2 (void) { #if FORCE_AVX2 return true; @@ -719,6 +739,43 @@ float int8_distance_l1_cpu (const void *v1, const void *v2, int n) { #endif } + bool cpu_supports_avx512(void) { + #if FORCE_AVX512 + return true; + #else + int cpu_info[4]; + + // 1. Check maximum CPUID leaf + run_cpuid(0, 0, cpu_info); + if (cpu_info[0] < 7) return false; // CPU too old + + // 2. Check for OSXSAVE (Leaf 1, ECX bit 27) + // This implies the processor supports XSAVE/XRSTOR + run_cpuid(1, 0, cpu_info); + if (!(cpu_info[2] & (1 << 27))) return false; + + // 3. Check XCR0 for OS support of ZMM registers + // We need bits 5 (opmask), 6 (ZMM_Hi256), and 7 (Hi16_ZMM) to be 1. + // Also usually need bit 1 (SSE) and 2 (AVX) + uint64_t xcr0 = run_xgetbv(0); + uint64_t avx512_state = (1 << 5) | (1 << 6) | (1 << 7); + if ((xcr0 & avx512_state) != avx512_state) return false; + + // 4. Check hardware feature bits (Leaf 7, Subleaf 0) + run_cpuid(7, 0, cpu_info); + + // EBX Bit 16: AVX512F (Foundation) + bool has_avx512f = (cpu_info[1] & (1 << 16)); + + // Optional: Check for BW (Byte/Word) and VL (Vector Length) + // Many algorithms (like your integer distance code) need these. + bool has_avx512bw = (cpu_info[1] & (1 << 30)); + bool has_avx512vl = (cpu_info[1] & (1 << 31)); + + return has_avx512f && has_avx512bw && has_avx512vl; + #endif + } + bool cpu_supports_sse2 (void) { int eax, ebx, ecx, edx; x86_cpuid(1, 0, &eax, &ebx, &ecx, &edx); @@ -801,7 +858,11 @@ void init_distance_functions (bool force_cpu) { #if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) if (cpu_supports_avx2()) { init_distance_functions_avx2(); - } else if (cpu_supports_sse2()) { + } + else if (cpu_supports_avx512()) { + init_distance_functions_avx512(); + } + else if (cpu_supports_sse2()) { init_distance_functions_sse2(); } #elif defined(__ARM_NEON) || defined(__aarch64__) diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 1c695a3..1997774 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.52" +#define SQLITE_VECTOR_VERSION "0.9.53" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 7d9ee84e03f177bdef20b148583e9278eff3cce9 Mon Sep 17 00:00:00 2001 From: Teddy ALBINA Date: Wed, 17 Dec 2025 15:22:21 +0100 Subject: [PATCH 101/108] Change priority of cpu_supports_avx512 --- src/distance-cpu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/distance-cpu.c b/src/distance-cpu.c index 5646526..ae5f4fc 100644 --- a/src/distance-cpu.c +++ b/src/distance-cpu.c @@ -856,12 +856,12 @@ void init_distance_functions (bool force_cpu) { if (force_cpu) return; #if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) - if (cpu_supports_avx2()) { - init_distance_functions_avx2(); - } - else if (cpu_supports_avx512()) { + if (cpu_supports_avx512()) { init_distance_functions_avx512(); } + else if (cpu_supports_avx2()) { + init_distance_functions_avx2(); + } else if (cpu_supports_sse2()) { init_distance_functions_sse2(); } From df0e895da52e3c2fa8d0ef27282d5f4045f25327 Mon Sep 17 00:00:00 2001 From: Woojong Koh Date: Wed, 21 Jan 2026 04:58:03 +0900 Subject: [PATCH 102/108] chore: add flake.nix for reproducible builds --- flake.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..5a9df81 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1768564909, + "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f6fc065 --- /dev/null +++ b/flake.nix @@ -0,0 +1,57 @@ +{ + description = "SQLite Vector Extension"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + stdenv = pkgs.stdenv; + version = "0.9.53"; + in + { + packages.default = stdenv.mkDerivation { + pname = "sqlite-vector"; + inherit version; + + src = pkgs.lib.cleanSource ./.; + + makeFlags = [ + "CC=${stdenv.cc.targetPrefix}cc" + ] ++ pkgs.lib.optionals stdenv.isDarwin [ + "ARCH=${if stdenv.hostPlatform.isAarch64 then "arm64" else "x86_64"}" + ]; + + installPhase = "install -D dist/vector* -t $out/lib"; + + checkInputs = [ pkgs.sqlite ]; + doCheck = true; + checkPhase = '' + make test + ''; + }; + + devShells.default = + let + sqlite-vector = self.packages.${system}.default; + in + pkgs.mkShell { + packages = [ + pkgs.sqlite + pkgs.gnumake + sqlite-vector + ]; + + shellHook = '' + export SQLITE_VECTOR_LIB="${sqlite-vector}/lib/vector${stdenv.hostPlatform.extensions.sharedLibrary}" + echo "SQLite Vector extension available at: $SQLITE_VECTOR_LIB" + echo "Load it in sqlite3 with: .load $SQLITE_VECTOR_LIB" + ''; + }; + } + ); +} From bc1cae5a70f80cfafa2948059b6fb12fd6bc0f0f Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Wed, 21 Jan 2026 14:47:11 +0100 Subject: [PATCH 103/108] Fixed several issues related to possible minor leaks and code consistency. --- src/sqlite-vector.c | 145 ++++++++++++++++++++++++-------------------- src/sqlite-vector.h | 2 +- 2 files changed, 81 insertions(+), 66 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index b2bbcd0..c87fb6f 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -335,23 +335,23 @@ static char *sqlite_get_int_prikey_column (sqlite3 *db, const char *table_name) char sql[STATIC_SQL_SIZE]; sqlite3_snprintf(sizeof(sql), sql, "SELECT COUNT(*), type, name FROM pragma_table_info('%q') WHERE pk > 0;", table_name); char *prikey = NULL; - + sqlite3_stmt *stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_text(stmt, 1, table_name, -1, SQLITE_STATIC); - + if (sqlite3_step(stmt) == SQLITE_ROW) { int count = sqlite3_column_int(stmt, 0); if (count == 1) { const char *decl_type = (const char *)sqlite3_column_text(stmt, 1); // see https://www.sqlite.org/datatype3.html (Determination Of Column Affinity) - if (strcasestr(decl_type, "INT")) { + if (decl_type && strcasestr(decl_type, "INT")) { prikey = sqlite_strdup((const char *)sqlite3_column_text(stmt, 2)); } } } } - + sqlite3_finalize(stmt); return prikey; } @@ -362,19 +362,19 @@ static bool sqlite_sanity_check (sqlite3_context *context, const char *table_nam // table_name must exists if (sqlite_table_exists(db, table_name) == false) { - context_result_error(context, SQLITE_ERROR, "Table '%s' does not exist.", table_name); + context_result_error(context, SQLITE_ERROR, "Table '%s' does not exist", table_name); return false; } // column_name must exists if (sqlite_column_exists(db, table_name, column_name) == false) { - context_result_error(context, SQLITE_ERROR, "Column '%s' does not exist in table '%s'.", column_name, table_name); + context_result_error(context, SQLITE_ERROR, "Column '%s' does not exist in table '%s'", column_name, table_name); return false; } // column_name must be of type BLOB if (sqlite_column_is_blob(db, table_name, column_name) == false) { - context_result_error(context, SQLITE_ERROR, "Column '%s' in table '%s' must be of type BLOB.", column_name, table_name); + context_result_error(context, SQLITE_ERROR, "Column '%s' in table '%s' must be of type BLOB", column_name, table_name); return false; } @@ -869,14 +869,14 @@ static void vector_print (void *buf, vector_type type, int n) { static bool sanity_check_args (sqlite3_context *context, const char *func_name, int argc, sqlite3_value **argv, int ntypes, int *types) { if (argc != ntypes) { - context_result_error(context, SQLITE_ERROR, "Function '%s' expects %d arguments, but %d were provided.", func_name, ntypes, argc); + context_result_error(context, SQLITE_ERROR, "Function '%s' expects %d arguments, but %d were provided", func_name, ntypes, argc); return false; } for (int i=0; iv_type = type; return true; } if (strncasecmp(key, OPTION_KEY_DIMENSION, key_len) == 0) { int dimension = (int)strtol(buffer, NULL, 0); - if (dimension <= 0) return context_result_error(context, SQLITE_ERROR, "Invalid vector dimension: expected a positive integer, got '%s'.", buffer); + if (dimension <= 0) return context_result_error(context, SQLITE_ERROR, "Invalid vector dimension: expected a positive integer, got '%s'", buffer); options->v_dim = dimension; return true; } @@ -978,20 +978,20 @@ bool vector_keyvalue_callback (sqlite3_context *context, void *xdata, const char if (strncasecmp(key, OPTION_KEY_MAXMEMORY, key_len) == 0) { uint64_t max_memory = human_to_number(buffer); - if (max_memory >= 0) options->max_memory = (int)max_memory; + if (max_memory > 0) options->max_memory = max_memory; return true; } if (strncasecmp(key, OPTION_KEY_QUANTTYPE, key_len) == 0) { vector_qtype type = quant_name_to_type(buffer); - if (type == -1) return context_result_error(context, SQLITE_ERROR, "Invalid quantization type: '%s' is not a recognized or supported quantization type.", buffer); + if (type == -1) return context_result_error(context, SQLITE_ERROR, "Invalid quantization type: '%s' is not a recognized or supported quantization type", buffer); options->q_type = type; return true; } if (strncasecmp(key, OPTION_KEY_DISTANCE, key_len) == 0) { vector_distance type = distance_name_to_type(buffer); - if (type == 0) return context_result_error(context, SQLITE_ERROR, "Invalid distance name: '%s' is not a recognized or supported distance.", buffer); + if (type == 0) return context_result_error(context, SQLITE_ERROR, "Invalid distance name: '%s' is not a recognized or supported distance", buffer); options->v_distance = type; return true; } @@ -1072,14 +1072,14 @@ table_context *vector_context_lookup (vector_context *ctx, const char *table_nam void vector_context_add (sqlite3_context *context, vector_context *ctx, const char *table_name, const char *column_name, vector_options *options) { // check if there is a free slot if (ctx->table_count >= MAX_TABLES) { - context_result_error(context, SQLITE_ERROR, "Cannot add table: maximum number of allowed tables reached (%d).", MAX_TABLES); + context_result_error(context, SQLITE_ERROR, "Cannot add table: maximum number of allowed tables reached (%d)", MAX_TABLES); return; } char *t_name = sqlite_strdup(table_name); char *c_name = sqlite_strdup(column_name); if (!t_name || !c_name) { - context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to duplicate table or column name."); + context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to duplicate table or column name"); if (t_name) sqlite3_free(t_name); if (c_name) sqlite3_free(c_name); return; @@ -1089,10 +1089,12 @@ void vector_context_add (sqlite3_context *context, vector_context *ctx, const ch sqlite3 *db = sqlite3_context_db_handle(context); bool is_without_rowid = sqlite_table_is_without_rowid(db, table_name); prikey = (is_without_rowid == false) ? sqlite_strdup("rowid") : sqlite_get_int_prikey_column(db, table_name); - + // sanity check primary key if (!prikey) { - (is_without_rowid) ? context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to duplicate rowid column name.") : context_result_error(context, SQLITE_ERROR, "WITHOUT ROWID table '%s' must have exactly one PRIMARY KEY column of type INTEGER.", table_name); + (is_without_rowid) ? context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to duplicate rowid column name") : context_result_error(context, SQLITE_ERROR, "WITHOUT ROWID table '%s' must have exactly one PRIMARY KEY column of type INTEGER", table_name); + sqlite3_free(t_name); + sqlite3_free(c_name); return; } @@ -1164,12 +1166,11 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta const char *pk_name = t_ctx->pk_name; int dim = t_ctx->options.v_dim; vector_type type = t_ctx->options.v_type; - float *tempv = NULL; // compute size of a single quant, format is: rowid + quantize dimensions size_t q_size = sizeof(int64_t) + (size_t)dim * sizeof(uint8_t); if (q_size == 0) { - sqlite3_result_error(context, "Vector dimension is zero, which is not possible.", -1); + sqlite3_result_error(context, "Vector dimension is zero, which is not possible", -1); return SQLITE_MISUSE; } @@ -1195,10 +1196,6 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta uint8_t *data = sqlite3_malloc64(out_bytes); uint8_t *original = data; if (!data) goto vector_rebuild_quantization_cleanup; - - sqlite3_uint64 temp_bytes = (sqlite3_uint64)dim * (sqlite3_uint64)sizeof(float); - tempv = (float *)sqlite3_malloc64(temp_bytes); - if (!tempv) goto vector_rebuild_quantization_cleanup; // SELECT rowid, embedding FROM table generate_select_from_table(table_name, column_name, pk_name, sql); @@ -1228,7 +1225,7 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta int blob_size = sqlite3_column_bytes(vm, 1); size_t need_bytes = (size_t)dim * (size_t)vector_type_to_size(type); if (blob_size < need_bytes) { - context_result_error(context, SQLITE_ERROR, "Invalid vector blob found at rowid %lld.", (long long)sqlite3_column_int64(vm, 0)); + context_result_error(context, SQLITE_ERROR, "Invalid vector blob found at rowid %lld", (long long)sqlite3_column_int64(vm, 0)); rc = SQLITE_ERROR; goto vector_rebuild_quantization_cleanup; } @@ -1252,7 +1249,7 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta val = (float)(((int8_t *)blob)[i]); break; default: - context_result_error(context, SQLITE_ERROR, "Unsupported vector type."); + context_result_error(context, SQLITE_ERROR, "Unsupported vector type"); rc = SQLITE_ERROR; goto vector_rebuild_quantization_cleanup; } @@ -1272,7 +1269,13 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta // STEP 2 // compute scale and offset and set table them to table context standard min-max linear quantization float abs_max = fmaxf(fabsf(min_val), fabsf(max_val)); // only used in VECTOR_QUANT_S8BIT - float scale = (qtype == VECTOR_QUANT_U8BIT) ? (255.0f / (max_val - min_val)) : (127.0f / abs_max); + float range = max_val - min_val; + float scale; + if (qtype == VECTOR_QUANT_U8BIT) { + scale = (range > 0.0f) ? (255.0f / range) : 1.0f; + } else { + scale = (abs_max > 0.0f) ? (127.0f / abs_max) : 1.0f; + } // in the VECTOR_QUANT_S8BIT version I am assuming a symmetric quantization, for asymmetric quantization min_val should be used float offset = (qtype == VECTOR_QUANT_U8BIT) ? min_val : 0.0f; @@ -1338,7 +1341,6 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta vector_rebuild_quantization_cleanup: if (rc != SQLITE_OK) printf("Error in vector_rebuild_quantization: %s\n", sqlite3_errmsg(db)); if (original) sqlite3_free(original); - if (tempv) sqlite3_free(tempv); if (vm) sqlite3_finalize(vm); if (count) *count = tot_processed; return rc; @@ -1354,7 +1356,7 @@ static void vector_quantize_preload (sqlite3_context *context, int argc, sqlite3 vector_context *v_ctx = (vector_context *)sqlite3_user_data(context); table_context *t_ctx = vector_context_lookup(v_ctx, table_name, column_name); if (!t_ctx) { - context_result_error(context, SQLITE_ERROR, "Vector context not found for table '%s' and column '%s'. Ensure that vector_init() has been called before using vector_quantize_preload().", table_name, column_name); + context_result_error(context, SQLITE_ERROR, "Vector context not found for table '%s' and column '%s'. Ensure that vector_init() has been called before using vector_quantize_preload()", table_name, column_name); return; } @@ -1372,14 +1374,14 @@ static void vector_quantize_preload (sqlite3_context *context, int argc, sqlite3 sqlite3 *db = sqlite3_context_db_handle(context); sqlite3_int64 required = sqlite_read_int64(db, sql); if (required == 0) { - context_result_error(context, SQLITE_ERROR, "Unable to read data from database. Ensure that vector_quantize() has been called before using vector_quantize_preload()."); + context_result_error(context, SQLITE_ERROR, "Unable to read data from database. Ensure that vector_quantize() has been called before using vector_quantize_preload()"); return; } int counter = 0; void *buffer = (void *)sqlite3_malloc64(required); if (!buffer) { - context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to allocate %lld bytes for quant buffer.", (long long)required); + context_result_error(context, SQLITE_NOMEM, "Out of memory: unable to allocate %lld bytes for quant buffer", (long long)required); return; } @@ -1425,7 +1427,7 @@ static void vector_quantize_preload (sqlite3_context *context, int argc, sqlite3 static int vector_quantize (sqlite3_context *context, const char *table_name, const char *column_name, const char *arg_options, bool *was_preloaded) { table_context *t_ctx = vector_context_lookup((vector_context *)sqlite3_user_data(context), table_name, column_name); if (!t_ctx) { - context_result_error(context, SQLITE_ERROR, "Vector context not found for table '%s' and column '%s'. Ensure that vector_init() has been called before using vector_quantize().", table_name, column_name); + context_result_error(context, SQLITE_ERROR, "Vector context not found for table '%s' and column '%s'. Ensure that vector_init() has been called before using vector_quantize()", table_name, column_name); return SQLITE_ERROR; } @@ -1529,21 +1531,23 @@ static void vector_quantize_memory (sqlite3_context *context, int argc, sqlite3_ static void vector_quantize_cleanup (sqlite3_context *context, int argc, sqlite3_value **argv) { int types[] = {SQLITE_TEXT, SQLITE_TEXT}; if (sanity_check_args(context, "vector_quantize_cleanup", argc, argv, 2, types) == false) return; - + const char *table_name = (const char *)sqlite3_value_text(argv[0]); const char *column_name = (const char *)sqlite3_value_text(argv[1]); - + vector_context *v_ctx = (vector_context *)sqlite3_user_data(context); table_context *t_ctx = vector_context_lookup(v_ctx, table_name, column_name); if (!t_ctx) return; // if no table context exists then do nothing - + // release any memory used in quantization + sqlite3_mutex_enter(qmutex); if (t_ctx->preloaded) { sqlite3_free(t_ctx->preloaded); t_ctx->preloaded = NULL; t_ctx->precounter = 0; } - + sqlite3_mutex_leave(qmutex); + // drop quant table (if any) char sql[STATIC_SQL_SIZE]; sqlite3 *db = sqlite3_context_db_handle(context); @@ -1561,7 +1565,7 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec // sanity check the JSON start array character if (*json != '[') { - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Malformed JSON: expected '[' at the beginning of the array."); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Malformed JSON: expected '[' at the beginning of the array"); } json++; @@ -1577,7 +1581,7 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec size_t alloc = (estimated_count + 1) * item_size; blob = sqlite3_malloc((int)alloc); if (!blob) { - return sqlite_common_set_error(context, vtab, SQLITE_NOMEM, "Out of memory: unable to allocate %lld bytes for BLOB buffer.", (long long)alloc); + return sqlite_common_set_error(context, vtab, SQLITE_NOMEM, "Out of memory: unable to allocate %lld bytes for BLOB buffer", (long long)alloc); } // typed pointers @@ -1604,12 +1608,12 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec if (p == endptr) { // parsing failed sqlite3_free(blob); - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Malformed JSON: expected a number at position %d (found '%c').", (int)(p - json) + 1, *p ? *p : '?'); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Malformed JSON: expected a number at position %d (found '%c')", (int)(p - json) + 1, *p ? *p : '?'); } if (count >= (int)(alloc / item_size)) { sqlite3_free(blob); - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Too many elements in JSON array."); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Too many elements in JSON array"); } // convert to proper type @@ -1629,7 +1633,7 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec case VECTOR_TYPE_U8: if (value < 0 || value > 255) { sqlite3_free(blob); - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Value out of range for uint8_t."); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Value out of range for uint8_t"); } uint8_blob[count++] = (uint8_t)value; break; @@ -1637,14 +1641,14 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec case VECTOR_TYPE_I8: if (value < -128 || value > 127) { sqlite3_free(blob); - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Value out of range for int8_t."); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Value out of range for int8_t"); } int8_blob[count++] = (int8_t)value; break; default: sqlite3_free(blob); - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Unsupported vector type."); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Unsupported vector type"); } p = endptr; @@ -1666,14 +1670,14 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec break; } else { sqlite3_free(blob); - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Malformed JSON: unexpected character '%c' at position %d.", *p ? *p : '?', (int)(p - json) + 1); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Malformed JSON: unexpected character '%c' at position %d", *p ? *p : '?', (int)(p - json) + 1); } } // sanity check vector dimension if ((dimension > 0) && (dimension != count)) { sqlite3_free(blob); - return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Invalid JSON vector dimension: expected %d but found %d.", dimension, count); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Invalid JSON vector dimension: expected %d but found %d", dimension, count); } if (size) *size = (int)(count * item_size); @@ -1691,13 +1695,13 @@ static void vector_as_type (sqlite3_context *context, vector_type type, int argc if (value_type == SQLITE_BLOB) { // the only check we can perform is that the blob size is an exact multiplier of the vector type if (value_size % vector_type_to_size(type) != 0) { - context_result_error(context, SQLITE_ERROR, "Invalid BLOB size for format '%s': size must be a multiple of %d bytes.", vector_type_to_name(type), vector_type_to_size(type)); + context_result_error(context, SQLITE_ERROR, "Invalid BLOB size for format '%s': size must be a multiple of %d bytes", vector_type_to_name(type), vector_type_to_size(type)); return; } if (dimension > 0) { int expected_size = (int)vector_type_to_size(type) * dimension; if (value_size != expected_size) { - context_result_error(context, SQLITE_ERROR, "Invalid BLOB size for format '%s': expected dimension should be %d (BLOB is %d bytes instead of %d).", vector_type_to_name(type), dimension, value_size, expected_size); + context_result_error(context, SQLITE_ERROR, "Invalid BLOB size for format '%s': expected dimension should be %d (BLOB is %d bytes instead of %d)", vector_type_to_name(type), dimension, value_size, expected_size); return; } } @@ -1710,7 +1714,7 @@ static void vector_as_type (sqlite3_context *context, vector_type type, int argc // try to parse JSON array value const char *json = (const char *)sqlite3_value_text(value); if (!json) { - context_result_error(context, SQLITE_ERROR, "Invalid TEXT input."); + context_result_error(context, SQLITE_ERROR, "Invalid TEXT input"); return; } @@ -1723,7 +1727,7 @@ static void vector_as_type (sqlite3_context *context, vector_type type, int argc return; } - context_result_error(context, SQLITE_ERROR, "Unsupported input type: only BLOB and TEXT values are accepted (received %s).", sqlite_type_name(value_type)); + context_result_error(context, SQLITE_ERROR, "Unsupported input type: only BLOB and TEXT values are accepted (received %s)", sqlite_type_name(value_type)); } static void vector_as_f32 (sqlite3_context *context, int argc, sqlite3_value **argv) { @@ -1747,6 +1751,7 @@ static void vector_as_i8 (sqlite3_context *context, int argc, sqlite3_value **ar } // MARK: - Modules - +static int vFullScanCursorNext (sqlite3_vtab_cursor *cur); static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv, const char *fname, vcursor_run_callback run_callback, vcursor_sort_callback sort_callback, bool quantized) { @@ -1761,7 +1766,7 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char // sanity check arguments int nargs = (is_streaming) ? 3 : 4; if (argc != nargs) { - return sqlite_vtab_set_error(&vtab->base, "%s expects %d arguments, but %d were provided.", fname, nargs, argc); + return sqlite_vtab_set_error(&vtab->base, "%s expects %d arguments, but %d were provided", fname, nargs, argc); } // SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT or SQLITE_BLOB, SQLITE_INTEGER @@ -1771,15 +1776,15 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char case 0: case 1: if (actual_type != SQLITE_TEXT) - return sqlite_vtab_set_error(&vtab->base, "%s: argument %d must be of type TEXT (got %s).", fname, (i+1), argc, sqlite_type_name(actual_type)); + return sqlite_vtab_set_error(&vtab->base, "%s: argument %d must be of type TEXT (got %s)", fname, (i+1), sqlite_type_name(actual_type)); break; case 2: if ((actual_type != SQLITE_TEXT) && (actual_type != SQLITE_BLOB)) - return sqlite_vtab_set_error(&vtab->base, "%s: argument %d must be of type TEXT or BLOB (got %s).", fname, (i+1), argc, sqlite_type_name(actual_type)); + return sqlite_vtab_set_error(&vtab->base, "%s: argument %d must be of type TEXT or BLOB (got %s)", fname, (i+1), sqlite_type_name(actual_type)); break; case 3: if (actual_type != SQLITE_INTEGER) - return sqlite_vtab_set_error(&vtab->base, "%s: argument %d must be of type INTEGER (got %s).", fname, (i+1), argc, sqlite_type_name(actual_type)); + return sqlite_vtab_set_error(&vtab->base, "%s: argument %d must be of type INTEGER (got %s)", fname, (i+1), sqlite_type_name(actual_type)); break; } } @@ -1789,7 +1794,7 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char const char *column_name = (const char *)sqlite3_value_text(argv[1]); table_context *t_ctx = vector_context_lookup(vtab->ctx, table_name, column_name); if (!t_ctx) { - return sqlite_vtab_set_error(&vtab->base, "%s: unable to retrieve context.", fname); + return sqlite_vtab_set_error(&vtab->base, "%s: unable to retrieve context", fname); } const void *vector = NULL; @@ -1801,7 +1806,7 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char } else { vector = (const void *)sqlite3_value_blob(argv[2]); vsize = sqlite3_value_bytes(argv[2]); - if (!vector) return sqlite_vtab_set_error(&vtab->base, "%s: input vector cannot be NULL.", fname); + if (!vector) return sqlite_vtab_set_error(&vtab->base, "%s: input vector cannot be NULL", fname); } VECTOR_PRINT((void*)vector, t_ctx->options.v_type, t_ctx->options.v_dim); @@ -1809,14 +1814,16 @@ static int vCursorFilterCommon (sqlite3_vtab_cursor *cur, int idxNum, const char char buffer[STATIC_SQL_SIZE]; char *name = generate_quant_table_name(table_name, column_name, buffer); if (!name || !sqlite_table_exists(vtab->db, name)) { - sqlite_vtab_set_error(&vtab->base, "Quantization table not found for table '%s' and column '%s'. Ensure that vector_quantize() has been called before using vector_quantize_scan().", table_name, column_name); + sqlite_vtab_set_error(&vtab->base, "Quantization table not found for table '%s' and column '%s'. Ensure that vector_quantize() has been called before using vector_quantize_scan()", table_name, column_name); return SQLITE_ERROR; } } c->table = t_ctx; if (is_streaming) { - return run_callback(vtab->db, c, vector, vsize); + int rc = run_callback(vtab->db, c, vector, vsize); + if (rc != SQLITE_OK) return rc; + return vFullScanCursorNext((sqlite3_vtab_cursor *)c); // Position on first row } // non-streaming flow @@ -2549,17 +2556,17 @@ static void vector_init (sqlite3_context *context, int argc, sqlite3_value **arg if (t_ctx) { // sanity check if (options.v_dim != t_ctx->options.v_dim) { - context_result_error(context, SQLITE_ERROR, "Inconsistent vector dimension for '%s.%s': existing=%d, provided=%d.", table_name, column_name, t_ctx->options.v_dim, options.v_dim); + context_result_error(context, SQLITE_ERROR, "Inconsistent vector dimension for '%s.%s': existing=%d, provided=%d", table_name, column_name, t_ctx->options.v_dim, options.v_dim); return; } if (options.v_type != t_ctx->options.v_type) { - context_result_error(context, SQLITE_ERROR, "Inconsistent vector type for '%s.%s': existing=%s, provided=%s.", table_name, column_name, vector_type_to_name(t_ctx->options.v_type), vector_type_to_name(options.v_type)); + context_result_error(context, SQLITE_ERROR, "Inconsistent vector type for '%s.%s': existing=%s, provided=%s", table_name, column_name, vector_type_to_name(t_ctx->options.v_type), vector_type_to_name(options.v_type)); return; } if (options.v_normalized != t_ctx->options.v_normalized) { - context_result_error(context, SQLITE_ERROR, "Inconsistent normalization flag for '%s.%s': existing=%s, provided=%s.", table_name, column_name, t_ctx->options.v_normalized ? "true" : "false", options.v_normalized ? "true" : "false"); + context_result_error(context, SQLITE_ERROR, "Inconsistent normalization flag for '%s.%s': existing=%s, provided=%s", table_name, column_name, t_ctx->options.v_normalized ? "true" : "false", options.v_normalized ? "true" : "false"); return; } @@ -2639,22 +2646,27 @@ SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const s if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_f32", 1, SQLITE_UTF8, ctx, vector_as_f32, NULL, NULL); + if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_f32", 2, SQLITE_UTF8, ctx, vector_as_f32, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - + rc = sqlite3_create_function(db, "vector_as_f16", 1, SQLITE_UTF8, ctx, vector_as_f16, NULL, NULL); + if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_f16", 2, SQLITE_UTF8, ctx, vector_as_f16, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - + rc = sqlite3_create_function(db, "vector_as_bf16", 1, SQLITE_UTF8, ctx, vector_as_bf16, NULL, NULL); + if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_bf16", 2, SQLITE_UTF8, ctx, vector_as_bf16, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - + rc = sqlite3_create_function(db, "vector_as_i8", 1, SQLITE_UTF8, ctx, vector_as_i8, NULL, NULL); + if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_i8", 2, SQLITE_UTF8, ctx, vector_as_i8, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - + rc = sqlite3_create_function(db, "vector_as_u8", 1, SQLITE_UTF8, ctx, vector_as_u8, NULL, NULL); + if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_u8", 2, SQLITE_UTF8, ctx, vector_as_u8, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; @@ -2669,7 +2681,10 @@ SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const s rc = sqlite3_create_module(db, "vector_quantize_scan_stream", &vQuantScanStreamModule, ctx); if (rc != SQLITE_OK) goto cleanup; - + + return SQLITE_OK; + cleanup: + vector_context_free(ctx); return rc; } diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 1997774..b2fbd69 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.53" +#define SQLITE_VECTOR_VERSION "0.9.55" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 6abfc23c7038e02198ec314dc773c0d361737ba1 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 22 Jan 2026 10:15:18 +0100 Subject: [PATCH 104/108] Added support for binary (1bit) vectors and SIMD-Optimized Hamming distance --- src/distance-avx2.c | 43 ++++++ src/distance-avx512.c | 72 ++++++++++- src/distance-avx512.h | 2 +- src/distance-cpu.c | 39 ++++++ src/distance-cpu.h | 11 +- src/distance-neon.c | 34 +++++ src/distance-sse2.c | 63 +++++++++ src/sqlite-vector.c | 295 +++++++++++++++++++++++++++++++++--------- src/sqlite-vector.h | 2 +- 9 files changed, 492 insertions(+), 69 deletions(-) diff --git a/src/distance-avx2.c b/src/distance-avx2.c index 75e6271..754b24b 100644 --- a/src/distance-avx2.c +++ b/src/distance-avx2.c @@ -949,6 +949,47 @@ float int8_distance_cosine_avx2 (const void *a, const void *b, int n) { return 1.0f - cosine_similarity; } +// MARK: - BIT - + +// lookup table for popcount of 4-bit values +static const __m256i popcount_lut = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); + +static inline __m256i popcount_avx2(__m256i v) { + __m256i low_mask = _mm256_set1_epi8(0x0f); + __m256i lo = _mm256_and_si256(v, low_mask); + __m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), low_mask); + __m256i cnt_lo = _mm256_shuffle_epi8(popcount_lut, lo); + __m256i cnt_hi = _mm256_shuffle_epi8(popcount_lut, hi); + return _mm256_add_epi8(cnt_lo, cnt_hi); +} + +float bit1_distance_hamming_avx2 (const void *v1, const void *v2, int n) { + const uint8_t *a = (const uint8_t *)v1; + const uint8_t *b = (const uint8_t *)v2; + __m256i acc = _mm256_setzero_si256(); + int i = 0; + + // Process 32 bytes at a time + for (; i + 32 <= n; i += 32) { + __m256i va = _mm256_loadu_si256((const __m256i *)(a + i)); + __m256i vb = _mm256_loadu_si256((const __m256i *)(b + i)); + __m256i xored = _mm256_xor_si256(va, vb); + __m256i popcnt = popcount_avx2(xored); + acc = _mm256_add_epi64(acc, _mm256_sad_epu8(popcnt, _mm256_setzero_si256())); + } + + // Horizontal sum + __m128i sum128 = _mm_add_epi64(_mm256_extracti128_si256(acc, 0), _mm256_extracti128_si256(acc, 1)); + int distance = _mm_extract_epi64(sum128, 0) + _mm_extract_epi64(sum128, 1); + + // Handle remainder with scalar + for (; i < n; i++) { + distance += __builtin_popcount(a[i] ^ b[i]); + } + + return (float)distance; +} + #endif // MARK: - @@ -985,6 +1026,8 @@ void init_distance_functions_avx2 (void) { dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_avx2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_avx2; + dispatch_distance_table[VECTOR_DISTANCE_HAMMING][VECTOR_TYPE_BIT] = bit1_distance_hamming_avx2; + distance_backend_name = "AVX2"; #endif } diff --git a/src/distance-avx512.c b/src/distance-avx512.c index 701dbee..5e1f996 100644 --- a/src/distance-avx512.c +++ b/src/distance-avx512.c @@ -48,7 +48,7 @@ static inline bool block_has_l2_inf_mismatch_16(const uint16_t* a, const uint16_ return false; } -/* 16bf16 -> 16f32: widen to u32, shift <<16, reinterpret as f32 */ +/* 16�bf16 -> 16�f32: widen to u32, shift <<16, reinterpret as f32 */ static inline __m512 bf16x16_to_f32x16_loadu(const uint16_t* p) { // Load 16x u16 (256 bits) __m256i v16 = _mm256_loadu_si256((const __m256i*)p); @@ -846,6 +846,72 @@ float int8_distance_cosine_avx512(const void* a, const void* b, int n) { return 1.0f - cosine_similarity; } +// MARK: - BIT - + +// AVX-512 popcount using lookup table (works on all AVX-512 CPUs) +static inline __m512i popcount_avx512(__m512i v) { + // Lookup table for popcount of 4-bit values + const __m512i popcount_lut = _mm512_set_epi8( + 4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0, + 4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0, + 4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0, + 4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0 + ); + const __m512i low_mask = _mm512_set1_epi8(0x0f); + + __m512i lo = _mm512_and_si512(v, low_mask); + __m512i hi = _mm512_and_si512(_mm512_srli_epi16(v, 4), low_mask); + __m512i cnt_lo = _mm512_shuffle_epi8(popcount_lut, lo); + __m512i cnt_hi = _mm512_shuffle_epi8(popcount_lut, hi); + return _mm512_add_epi8(cnt_lo, cnt_hi); +} + +// Hamming distance for 1-bit packed binary vectors +// n = number of dimensions (bits), not bytes +static float bit1_distance_hamming_avx512(const void *v1, const void *v2, int n) { + const uint8_t *a = (const uint8_t *)v1; + const uint8_t *b = (const uint8_t *)v2; + int num_bytes = (n + 7) / 8; + + __m512i acc = _mm512_setzero_si512(); + int i = 0; + + // Process 64 bytes at a time + for (; i + 64 <= num_bytes; i += 64) { + __m512i va = _mm512_loadu_si512((const __m512i *)(a + i)); + __m512i vb = _mm512_loadu_si512((const __m512i *)(b + i)); + __m512i xored = _mm512_xor_si512(va, vb); + +#if defined(__AVX512VPOPCNTDQ__) + // Native popcount (Ice Lake+) + __m512i popcnt = _mm512_popcnt_epi64(xored); + acc = _mm512_add_epi64(acc, popcnt); +#else + // Lookup table popcount (Skylake-X compatible) + __m512i popcnt = popcount_avx512(xored); + // Sum bytes to 64-bit using SAD against zero + acc = _mm512_add_epi64(acc, _mm512_sad_epu8(popcnt, _mm512_setzero_si512())); +#endif + } + + // Horizontal sum + uint64_t distance = _mm512_reduce_add_epi64(acc); + + // Handle remaining bytes with scalar code + for (; i < num_bytes; i++) { +#if defined(__GNUC__) || defined(__clang__) + distance += __builtin_popcount(a[i] ^ b[i]); +#else + uint8_t x = a[i] ^ b[i]; + x = x - ((x >> 1) & 0x55); + x = (x & 0x33) + ((x >> 2) & 0x33); + distance += (x + (x >> 4)) & 0x0f; +#endif + } + + return (float)distance; +} + #endif // MARK: - @@ -882,6 +948,8 @@ void init_distance_functions_avx512(void) { dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_avx512; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_avx512; + dispatch_distance_table[VECTOR_DISTANCE_HAMMING][VECTOR_TYPE_BIT] = bit1_distance_hamming_avx512; + distance_backend_name = "AVX512"; #endif -} \ No newline at end of file +} diff --git a/src/distance-avx512.h b/src/distance-avx512.h index 3b1198f..265e164 100644 --- a/src/distance-avx512.h +++ b/src/distance-avx512.h @@ -10,6 +10,6 @@ #include -void init_distance_functions_avx512(void); +void init_distance_functions_avx512 (void); #endif diff --git a/src/distance-cpu.c b/src/distance-cpu.c index ae5f4fc..136adcc 100644 --- a/src/distance-cpu.c +++ b/src/distance-cpu.c @@ -693,6 +693,42 @@ float int8_distance_l1_cpu (const void *v1, const void *v2, int n) { return sum; } +// MARK: - BIT - + +static inline int popcount64(uint64_t x) { + #if defined(__GNUC__) || defined(__clang__) + return __builtin_popcountll(x); + #else + // fallback: bit manipulation + x = x - ((x >> 1) & 0x5555555555555555ULL); + x = (x & 0x3333333333333333ULL) + ((x >> 2) & 0x3333333333333333ULL); + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL; + return (x * 0x0101010101010101ULL) >> 56; + #endif +} + +float bit1_distance_hamming_cpu (const void *v1, const void *v2, int n) { + const uint8_t *a = (const uint8_t *)v1; + const uint8_t *b = (const uint8_t *)v2; + + int distance = 0; + int i = 0; + + // process 8 bytes at a time + for (; i + 8 <= n; i += 8) { + uint64_t xa = *(const uint64_t *)(a + i); + uint64_t xb = *(const uint64_t *)(b + i); + distance += popcount64(xa ^ xb); + } + + // handle remainder + for (; i < n; i++) { + distance += popcount64(a[i] ^ b[i]); + } + + return (float)distance; +} + // MARK: - ENTRYPOINT - #if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) @@ -845,6 +881,9 @@ void init_cpu_functions (void) { [VECTOR_TYPE_BF16] = bfloat16_distance_l1_cpu, [VECTOR_TYPE_U8] = uint8_distance_l1_cpu, [VECTOR_TYPE_I8] = int8_distance_l1_cpu, + }, + [VECTOR_DISTANCE_HAMMING] = { + [VECTOR_TYPE_BIT] = bit1_distance_hamming_cpu } }; diff --git a/src/distance-cpu.h b/src/distance-cpu.h index 33a803b..653109b 100644 --- a/src/distance-cpu.h +++ b/src/distance-cpu.h @@ -38,14 +38,16 @@ typedef enum { VECTOR_TYPE_F16, VECTOR_TYPE_BF16, VECTOR_TYPE_U8, - VECTOR_TYPE_I8 + VECTOR_TYPE_I8, + VECTOR_TYPE_BIT } vector_type; -#define VECTOR_TYPE_MAX 6 +#define VECTOR_TYPE_MAX 7 typedef enum { VECTOR_QUANT_AUTO = 0, VECTOR_QUANT_U8BIT = 1, - VECTOR_QUANT_S8BIT = 2 + VECTOR_QUANT_S8BIT = 2, + VECTOR_QUANT_1BIT = 3 } vector_qtype; typedef enum { @@ -54,8 +56,9 @@ typedef enum { VECTOR_DISTANCE_COSINE, VECTOR_DISTANCE_DOT, VECTOR_DISTANCE_L1, + VECTOR_DISTANCE_HAMMING } vector_distance; -#define VECTOR_DISTANCE_MAX 6 +#define VECTOR_DISTANCE_MAX 7 typedef float (*distance_function_t)(const void *v1, const void *v2, int n); diff --git a/src/distance-neon.c b/src/distance-neon.c index 5900438..fbe9220 100644 --- a/src/distance-neon.c +++ b/src/distance-neon.c @@ -1230,6 +1230,38 @@ float int8_distance_l1_neon(const void *v1, const void *v2, int n) { return (float)final; } + +// MARK: - BIT - + +float bit1_distance_hamming_neon (const void *v1, const void *v2, int n) { + const uint8_t *a = (const uint8_t *)v1; + const uint8_t *b = (const uint8_t *)v2; + uint64x2_t acc = vdupq_n_u64(0); + int i = 0; + + // Process 16 bytes at a time + for (; i + 16 <= n; i += 16) { + uint8x16_t va = vld1q_u8(a + i); + uint8x16_t vb = vld1q_u8(b + i); + uint8x16_t xored = veorq_u8(va, vb); + + // vcntq_u8: popcount per byte + uint8x16_t popcnt = vcntq_u8(xored); + + // Sum bytes to 64-bit accumulators + acc = vpadalq_u32(acc, vpaddlq_u16(vpaddlq_u8(popcnt))); + } + + int distance = (int)(vgetq_lane_u64(acc, 0) + vgetq_lane_u64(acc, 1)); + + // Handle remainder + for (; i < n; i++) { + distance += __builtin_popcount(a[i] ^ b[i]); + } + + return (float)distance; +} + #endif // MARK: - @@ -1266,6 +1298,8 @@ void init_distance_functions_neon (void) { dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_neon; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_neon; + dispatch_distance_table[VECTOR_DISTANCE_HAMMING][VECTOR_TYPE_BIT] = bit1_distance_hamming_neon; + distance_backend_name = "NEON"; #endif } diff --git a/src/distance-sse2.c b/src/distance-sse2.c index 0e903a5..b3e685f 100644 --- a/src/distance-sse2.c +++ b/src/distance-sse2.c @@ -1006,6 +1006,67 @@ float int8_distance_cosine_sse2 (const void *v1, const void *v2, int n) { return 1.0f - cosine_sim; } +// MARK: - BIT - + +static inline __m128i popcount_sse2 (__m128i v) { + // Classic parallel bit count algorithm vectorized for SSE2 + + const __m128i mask1 = _mm_set1_epi8(0x55); // 01010101 + const __m128i mask2 = _mm_set1_epi8(0x33); // 00110011 + const __m128i mask4 = _mm_set1_epi8(0x0f); // 00001111 + + // x = x - ((x >> 1) & 0x55555555) + __m128i t = _mm_and_si128(_mm_srli_epi16(v, 1), mask1); + v = _mm_sub_epi8(v, t); + + // x = (x & 0x33333333) + ((x >> 2) & 0x33333333) + t = _mm_and_si128(_mm_srli_epi16(v, 2), mask2); + v = _mm_add_epi8(_mm_and_si128(v, mask2), t); + + // x = (x + (x >> 4)) & 0x0f0f0f0f + t = _mm_srli_epi16(v, 4); + v = _mm_and_si128(_mm_add_epi8(v, t), mask4); + + // Now each byte contains popcount for that byte (0-8) + return v; +} + +float bit1_distance_hamming_sse2 (const void *v1, const void *v2, int n) { + const uint8_t *a = (const uint8_t *)v1; + const uint8_t *b = (const uint8_t *)v2; + __m128i acc = _mm_setzero_si128(); + int i = 0; + + // Process 16 bytes at a time + for (; i + 16 <= n; i += 16) { + __m128i va = _mm_loadu_si128((const __m128i *)(a + i)); + __m128i vb = _mm_loadu_si128((const __m128i *)(b + i)); + __m128i xored = _mm_xor_si128(va, vb); + __m128i popcnt = popcount_sse2(xored); + + // Sum bytes using SAD (sum of absolute differences against zero) + // This sums all 16 bytes into two 64-bit values + acc = _mm_add_epi64(acc, _mm_sad_epu8(popcnt, _mm_setzero_si128())); + } + + // Horizontal sum of the two 64-bit accumulators + int distance = _mm_cvtsi128_si64(acc) + _mm_cvtsi128_si64(_mm_srli_si128(acc, 8)); + + // Handle remainder with scalar code + for (; i < n; i++) { + #if defined(__GNUC__) || defined(__clang__) + distance += __builtin_popcount(a[i] ^ b[i]); + #else + uint8_t x = a[i] ^ b[i]; + x = x - ((x >> 1) & 0x55); + x = (x & 0x33) + ((x >> 2) & 0x33); + distance += (x + (x >> 4)) & 0x0f; + #endif + } + + return (float)distance; +} + #endif // MARK: - @@ -1042,6 +1103,8 @@ void init_distance_functions_sse2 (void) { dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_U8] = uint8_distance_l1_sse2; dispatch_distance_table[VECTOR_DISTANCE_L1][VECTOR_TYPE_I8] = int8_distance_l1_sse2; + dispatch_distance_table[VECTOR_DISTANCE_HAMMING][VECTOR_TYPE_BIT] = bit1_distance_hamming_sse2; + distance_backend_name = "SSE2"; #endif } diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index c87fb6f..be9b915 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -765,25 +765,106 @@ static inline void quantize_i8 (const int8_t *v, uint8_t *q, float offset, float else quantize_i8_to_signed8bit(v, (int8_t *)q, offset, scale, dim); } +static void quantize_binary (const float *input, uint8_t *output, int dim, bool is_binary_mean) { + float threshold = 0.0f; + + if (is_binary_mean) { + // compute mean as threshold + float sum = 0.0f; + for (int i = 0; i < dim; i++) { + sum += input[i]; + } + threshold = sum / dim; + } + + // quantize + memset(output, 0, (dim + 7) / 8); + for (int i = 0; i < dim; i++) { + if (input[i] >= threshold) { + output[i / 8] |= (1 << (i % 8)); + } + } + } + +static void quantize_binary_f16 (const uint16_t *input, uint8_t *output, int dim, bool is_binary_mean) { + float threshold = 0.0f; + + if (is_binary_mean) { + float sum = 0.0f; + for (int i = 0; i < dim; i++) { + sum += float16_to_float32(input[i]); + } + threshold = sum / dim; + } + + memset(output, 0, (dim + 7) / 8); + for (int i = 0; i < dim; i++) { + if (float16_to_float32(input[i]) >= threshold) { + output[i / 8] |= (1 << (i % 8)); + } + } +} + +static void quantize_binary_bf16 (const uint16_t *input, uint8_t *output, int dim, bool is_binary_mean) { + float threshold = 0.0f; + + if (is_binary_mean) { + float sum = 0.0f; + for (int i = 0; i < dim; i++) { + sum += bfloat16_to_float32(input[i]); + } + threshold = sum / dim; + } + + memset(output, 0, (dim + 7) / 8); + for (int i = 0; i < dim; i++) { + if (bfloat16_to_float32(input[i]) >= threshold) { + output[i / 8] |= (1 << (i % 8)); + } + } +} + +static void quantize_binary_u8 (const uint8_t *input, uint8_t *output, int dim) { + // For unsigned 8-bit, threshold at 128 (midpoint) + memset(output, 0, (dim + 7) / 8); + for (int i = 0; i < dim; i++) { + if (input[i] >= 128) { + output[i / 8] |= (1 << (i % 8)); + } + } +} + +static void quantize_binary_i8 (const int8_t *input, uint8_t *output, int dim) { + // For signed 8-bit, threshold at 0 (sign-based) + memset(output, 0, (dim + 7) / 8); + for (int i = 0; i < dim; i++) { + if (input[i] >= 0) { + output[i / 8] |= (1 << (i % 8)); + } + } +} + // MARK: - General Utils - static size_t vector_type_to_size (vector_type type) { switch (type) { - case VECTOR_TYPE_F32: return sizeof(float); - case VECTOR_TYPE_F16: return sizeof(uint16_t); - case VECTOR_TYPE_BF16: return sizeof(uint16_t); - case VECTOR_TYPE_U8: return sizeof(uint8_t); - case VECTOR_TYPE_I8: return sizeof(int8_t); + case VECTOR_TYPE_F32: return sizeof(float); // 4 bytes + case VECTOR_TYPE_F16: return sizeof(uint16_t); // 2 bytes + case VECTOR_TYPE_BF16: return sizeof(uint16_t); // 2 bytes + case VECTOR_TYPE_U8: return sizeof(uint8_t); // 1 byte + case VECTOR_TYPE_I8: return sizeof(int8_t); // 1 byte + case VECTOR_TYPE_BIT: return 0; // Special: use vector_bytes_for_dim() } - return 0; + return SIZE_T_MAX; // error } static vector_type vector_name_to_type (const char *vname) { - if (strcasecmp(vname, "FLOAT32") == 0) return VECTOR_TYPE_F32; - if (strcasecmp(vname, "FLOAT16") == 0) return VECTOR_TYPE_F16; - if (strcasecmp(vname, "FLOATB16") == 0) return VECTOR_TYPE_BF16; - if (strcasecmp(vname, "UINT8") == 0) return VECTOR_TYPE_U8; - if (strcasecmp(vname, "INT8") == 0) return VECTOR_TYPE_I8; + if ((strcasecmp(vname, "F32") == 0) || (strcasecmp(vname, "FLOAT32") == 0)) return VECTOR_TYPE_F32; + if ((strcasecmp(vname, "F16") == 0) || (strcasecmp(vname, "FLOAT16") == 0)) return VECTOR_TYPE_F16; + if ((strcasecmp(vname, "BF16") == 0) || (strcasecmp(vname, "FLOATB16") == 0)) return VECTOR_TYPE_BF16; + if ((strcasecmp(vname, "U8") == 0) || (strcasecmp(vname, "UINT8") == 0)) return VECTOR_TYPE_U8; + if ((strcasecmp(vname, "I8") == 0) || (strcasecmp(vname, "INT8") == 0)) return VECTOR_TYPE_I8; + if (strcasecmp(vname, "BIT") == 0 || strcasecmp(vname, "BINARY") == 0 || strcasecmp(vname, "1BIT") == 0) return VECTOR_TYPE_BIT; return 0; } @@ -794,13 +875,23 @@ const char *vector_type_to_name (vector_type type) { case VECTOR_TYPE_BF16: return "FLOATB16"; case VECTOR_TYPE_U8: return "UINT8"; case VECTOR_TYPE_I8: return "INT8"; + case VECTOR_TYPE_BIT: return "BIT"; } return "N/A"; } +static size_t vector_bytes_for_dim (vector_type type, int dim) { + // returns total bytes needed to store a vector of given type and dimension + if (type == VECTOR_TYPE_BIT) { + return (size_t)((dim + 7) / 8); // Ceil division: pack 8 dimensions per byte + } + return (size_t)dim * vector_type_to_size(type); +} + static vector_qtype quant_name_to_type (const char *qname) { if (strcasecmp(qname, "UINT8") == 0) return VECTOR_QUANT_U8BIT; if (strcasecmp(qname, "INT8") == 0) return VECTOR_QUANT_S8BIT; + if (strcasecmp(qname, "1BIT") == 0 || strcasecmp(qname, "BIT") == 0 || strcasecmp(qname, "BINARY") == 0) return VECTOR_QUANT_1BIT; return -1; } @@ -823,6 +914,7 @@ const char *vector_distance_to_name (vector_distance type) { case VECTOR_DISTANCE_COSINE: return "COSINE"; case VECTOR_DISTANCE_DOT: return "DOT"; case VECTOR_DISTANCE_L1: return "L1"; + case VECTOR_DISTANCE_HAMMING: return "HAMMING"; } return "N/A"; } @@ -861,6 +953,12 @@ static void vector_print (void *buf, vector_type type, int n) { printf("%d,", u[i]); } break; + + case VECTOR_TYPE_BIT: { + uint8_t *b = (uint8_t *)buf; + printf("%d,", (b[i / 8] >> (i % 8)) & 1); + } + break; } } printf("]\n"); @@ -1168,7 +1266,8 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta vector_type type = t_ctx->options.v_type; // compute size of a single quant, format is: rowid + quantize dimensions - size_t q_size = sizeof(int64_t) + (size_t)dim * sizeof(uint8_t); + size_t quant_bytes = (qtype == VECTOR_QUANT_1BIT) ? ((dim + 7) / 8) : (dim * sizeof(uint8_t)); + size_t q_size = sizeof(int64_t) + quant_bytes; if (q_size == 0) { sqlite3_result_error(context, "Vector dimension is zero, which is not possible", -1); return SQLITE_MISUSE; @@ -1223,7 +1322,7 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta if (!blob) continue; int blob_size = sqlite3_column_bytes(vm, 1); - size_t need_bytes = (size_t)dim * (size_t)vector_type_to_size(type); + size_t need_bytes = vector_bytes_for_dim(type, dim); if (blob_size < need_bytes) { context_result_error(context, SQLITE_ERROR, "Invalid vector blob found at rowid %lld", (long long)sqlite3_column_int64(vm, 0)); rc = SQLITE_ERROR; @@ -1309,16 +1408,34 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta data += sizeof(int64_t); // quantize vector - switch (type) { - case VECTOR_TYPE_F32: quantize_float32((const float *)blob, data, offset, scale, dim, qtype); break; - case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)blob, data, offset, scale, dim, qtype); break; - case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)blob, data, offset, scale, dim, qtype); break; - case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)blob, data, offset, scale, dim, qtype); break; - case VECTOR_TYPE_I8: quantize_i8((const int8_t *)blob, data, offset, scale, dim, qtype); break; + if (qtype == VECTOR_QUANT_1BIT) { + // 1-bit quantization: convert source to binary based on type + switch (type) { + case VECTOR_TYPE_F32: quantize_binary((const float *)blob, data, dim, false); break; + case VECTOR_TYPE_F16: quantize_binary_f16((const uint16_t *)blob, data, dim, false); break; + case VECTOR_TYPE_BF16: quantize_binary_bf16((const uint16_t *)blob, data, dim, false); break; + case VECTOR_TYPE_U8: quantize_binary_u8((const uint8_t *)blob, data, dim); break; + case VECTOR_TYPE_I8: quantize_binary_i8((const int8_t *)blob, data, dim); break; + case VECTOR_TYPE_BIT: memcpy(data, blob, (dim + 7) / 8); break; // Already binary + } + } else { + // 8-bit quantization (U8BIT or S8BIT) + switch (type) { + case VECTOR_TYPE_F32: quantize_float32((const float *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_I8: quantize_i8((const int8_t *)blob, data, offset, scale, dim, qtype); break; + case VECTOR_TYPE_BIT: memcpy(data, blob, (dim + 7) / 8); break; // BIT to 8-bit: just copy + } } - VECTOR_PRINT((void *)data, (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8, dim); - data += (dim * sizeof(uint8_t)); + #if DEBUG_VECTOR_SERIALIZATION + vector_type qprint = (qtype == VECTOR_QUANT_1BIT) ? VECTOR_TYPE_BIT : (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8; + VECTOR_PRINT((void *)data, qprint, dim); + #endif + + data += (qtype == VECTOR_QUANT_1BIT) ? ((dim + 7) / 8) : (dim * sizeof(uint8_t)); max_rowid = rowid; ++n_processed; ++tot_processed; @@ -1693,15 +1810,24 @@ static void vector_as_type (sqlite3_context *context, vector_type type, int argc int dimension = (argc == 2) ? sqlite3_value_int(argv[1]) : 0; if (value_type == SQLITE_BLOB) { - // the only check we can perform is that the blob size is an exact multiplier of the vector type - if (value_size % vector_type_to_size(type) != 0) { - context_result_error(context, SQLITE_ERROR, "Invalid BLOB size for format '%s': size must be a multiple of %d bytes", vector_type_to_name(type), vector_type_to_size(type)); - return; - } - if (dimension > 0) { - int expected_size = (int)vector_type_to_size(type) * dimension; - if (value_size != expected_size) { - context_result_error(context, SQLITE_ERROR, "Invalid BLOB size for format '%s': expected dimension should be %d (BLOB is %d bytes instead of %d)", vector_type_to_name(type), dimension, value_size, expected_size); + if (type == VECTOR_TYPE_BIT) { + // For bit vectors, any size is valid (dimensions = size * 8, minus padding) + // Optionally validate against expected dimension if provided + if (dimension > 0) { + size_t expected_size = vector_bytes_for_dim(type, dimension); + if (value_size != expected_size) { + context_result_error(context, SQLITE_ERROR, + "Invalid BLOB size for format '%s': expected %d bytes for %d dimensions (got %d bytes)", + vector_type_to_name(type), (int)expected_size, dimension, value_size); + return; + } + } + } else { + // the only check we can perform is that the blob size is an exact multiplier of the vector type + if (value_size % vector_type_to_size(type) != 0) { + context_result_error(context, SQLITE_ERROR, + "Invalid BLOB size for format '%s': size must be a multiple of %d bytes", + vector_type_to_name(type), vector_type_to_size(type)); return; } } @@ -1969,7 +2095,7 @@ static int vFullScanCursorNext (sqlite3_vtab_cursor *cur){ // QUANTIZATION sizes const size_t rowid_size = sizeof(int64_t); - const size_t vector_size = (size_t)dimension * sizeof(uint8_t); + const size_t vector_size = (size_t)c->stream.vsize; // correctly set by caller for 1-bit or 8-bit const size_t total_stride = rowid_size + vector_size; // QUANTIZED IN-MEMORY @@ -1990,7 +2116,7 @@ static int vFullScanCursorNext (sqlite3_vtab_cursor *cur){ // no NULL vectors here by construction - float distance = distance_fn((const void *)v1, (const void *)vector_data, dimension); + float distance = distance_fn((const void *)v1, (const void *)vector_data, c->stream.vsize); if (nearly_zero_float32(distance)) distance = 0.0f; c->stream.distance = distance; @@ -2016,7 +2142,7 @@ static int vFullScanCursorNext (sqlite3_vtab_cursor *cur){ const uint8_t *current_data = data + (i * total_stride); const uint8_t *vector_data = current_data + rowid_size; - float distance = distance_fn((const void *)v1, (const void *)vector_data, dimension); + float distance = distance_fn((const void *)v1, (const void *)vector_data, c->stream.vsize); if (nearly_zero_float32(distance)) distance = 0.0f; c->stream.distance = distance; @@ -2157,24 +2283,28 @@ static int vQuantRunMemory(vFullScanCursor *c, uint8_t *v, vector_qtype qtype, i const int counter = c->table->precounter; const uint8_t *data = c->table->preloaded; const size_t rowid_size = sizeof(int64_t); - const size_t vector_size = dim * sizeof(uint8_t); + const size_t vector_size = (qtype == VECTOR_QUANT_1BIT) ? ((dim + 7) / 8) : (dim * sizeof(uint8_t)); const size_t total_stride = rowid_size + vector_size; double *distance = c->distance; int64_t *rowids = (int64_t *)c->rowids; int max_index = c->max_index; double current_max = distance[max_index]; - + // compute distance function vector_distance vd = c->table->options.v_distance; vector_type vt = (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8; + if (qtype == VECTOR_QUANT_1BIT) { + vt = VECTOR_TYPE_BIT; + vd = VECTOR_DISTANCE_HAMMING; + } distance_function_t distance_fn = dispatch_distance_table[vd][vt]; - + for (int i = 0; i < counter; ++i) { const uint8_t *current_data = data + (i * total_stride); const uint8_t *vector_data = current_data + rowid_size; - float dist = distance_fn((const void *)v, (const void *)vector_data, dim); + float dist = distance_fn((const void *)v, (const void *)vector_data, (int)vector_size); if (nearly_zero_float32(dist)) dist = 0.0; if (dist < current_max) { @@ -2194,29 +2324,47 @@ static int vQuantRunMemory(vFullScanCursor *c, uint8_t *v, vector_qtype qtype, i static int vQuantRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1size) { // quantize target vector int dimension = c->table->options.v_dim; - uint8_t *v = (uint8_t *)sqlite3_malloc(dimension * sizeof(int8_t)); + vector_qtype qtype = c->table->options.q_type; + size_t alloc_size = (qtype == VECTOR_QUANT_1BIT) ? ((dimension + 7) / 8) : (dimension * sizeof(int8_t)); + uint8_t *v = (uint8_t *)sqlite3_malloc64(alloc_size); if (!v) return SQLITE_NOMEM; // quantize vector - vector_qtype qtype = c->table->options.q_type; float offset = c->table->offset; float scale = c->table->scale; vector_type type = c->table->options.v_type; - - switch (type) { - case VECTOR_TYPE_F32: quantize_float32((const float *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_I8: quantize_i8((const int8_t *)v1, v, offset, scale, dimension, qtype); break; + + if (qtype == VECTOR_QUANT_1BIT) { + // 1-bit quantization: convert source to binary based on type + switch (type) { + case VECTOR_TYPE_F32: quantize_binary((const float *)v1, v, dimension, false); break; + case VECTOR_TYPE_F16: quantize_binary_f16((const uint16_t *)v1, v, dimension, false); break; + case VECTOR_TYPE_BF16: quantize_binary_bf16((const uint16_t *)v1, v, dimension, false); break; + case VECTOR_TYPE_U8: quantize_binary_u8((const uint8_t *)v1, v, dimension); break; + case VECTOR_TYPE_I8: quantize_binary_i8((const int8_t *)v1, v, dimension); break; + case VECTOR_TYPE_BIT: memcpy(v, v1, (dimension + 7) / 8); break; // Already binary + } + } else { + // 8-bit quantization (U8BIT or S8BIT) + switch (type) { + case VECTOR_TYPE_F32: quantize_float32((const float *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_I8: quantize_i8((const int8_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_BIT: memcpy(v, v1, (dimension + 7) / 8); break; // BIT to 8-bit: just copy + } } - + if (c->table->preloaded) { int rc = vQuantRunMemory(c, v, qtype, dimension); if (v) sqlite3_free(v); return rc; } - VECTOR_PRINT((void*)v, (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8, dimension); + #if DEBUG_VECTOR_SERIALIZATION + vector_type qprint = (qtype == VECTOR_QUANT_1BIT) ? VECTOR_TYPE_BIT : (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8; + VECTOR_PRINT((void*)v, qprint, dimension); + #endif char sql[STATIC_SQL_SIZE]; generate_select_quant_table(c->table->t_name, c->table->c_name, sql); @@ -2226,12 +2374,17 @@ static int vQuantRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1siz // precompute constants const size_t rowid_size = sizeof(int64_t); - const size_t vector_size = dimension * sizeof(uint8_t); + const size_t vector_size = (qtype == VECTOR_QUANT_1BIT) ? ((dimension + 7) / 8) : (dimension * sizeof(uint8_t)); const size_t total_stride = rowid_size + vector_size; // compute distance function vector_distance vd = c->table->options.v_distance; vector_type vt = (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8; + if (qtype == VECTOR_QUANT_1BIT) { + // in case of 1BIT quantization force distance to alway be hamming + vt = VECTOR_TYPE_BIT; + vd = VECTOR_DISTANCE_HAMMING; + } distance_function_t distance_fn = dispatch_distance_table[vd][vt]; while (1) { @@ -2248,7 +2401,7 @@ static int vQuantRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1siz for (int i=0; itable->options.v_dim; - uint8_t *v = (uint8_t *)sqlite3_malloc(dimension * sizeof(int8_t)); + vector_qtype qtype = c->table->options.q_type; + size_t alloc_size = (qtype == VECTOR_QUANT_1BIT) ? ((dimension + 7) / 8) : (dimension * sizeof(int8_t)); + uint8_t *v = (uint8_t *)sqlite3_malloc64(alloc_size); if (!v) return SQLITE_NOMEM; // quantize vector - vector_qtype qtype = c->table->options.q_type; float offset = c->table->offset; float scale = c->table->scale; vector_type type = c->table->options.v_type; - - switch (type) { - case VECTOR_TYPE_F32: quantize_float32((const float *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)v1, v, offset, scale, dimension, qtype); break; - case VECTOR_TYPE_I8: quantize_i8((const int8_t *)v1, v, offset, scale, dimension, qtype); break; + + if (qtype == VECTOR_QUANT_1BIT) { + // 1-bit quantization: convert source to binary based on type + switch (type) { + case VECTOR_TYPE_F32: quantize_binary((const float *)v1, v, dimension, false); break; + case VECTOR_TYPE_F16: quantize_binary_f16((const uint16_t *)v1, v, dimension, false); break; + case VECTOR_TYPE_BF16: quantize_binary_bf16((const uint16_t *)v1, v, dimension, false); break; + case VECTOR_TYPE_U8: quantize_binary_u8((const uint8_t *)v1, v, dimension); break; + case VECTOR_TYPE_I8: quantize_binary_i8((const int8_t *)v1, v, dimension); break; + case VECTOR_TYPE_BIT: memcpy(v, v1, (dimension + 7) / 8); break; // Already binary + } + } else { + // 8-bit quantization (U8BIT or S8BIT) + switch (type) { + case VECTOR_TYPE_F32: quantize_float32((const float *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_F16: quantize_float16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_BF16: quantize_bfloat16((const uint16_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_U8: quantize_u8((const uint8_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_I8: quantize_i8((const int8_t *)v1, v, offset, scale, dimension, qtype); break; + case VECTOR_TYPE_BIT: memcpy(v, v1, (dimension + 7) / 8); break; // BIT to 8-bit: just copy + } } - + c->stream.vector = (void *)v; - c->stream.vsize = (int)(dimension * sizeof(int8_t)); + c->stream.vsize = (qtype == VECTOR_QUANT_1BIT) ? (int)((dimension + 7) / 8) : (int)(dimension * sizeof(int8_t)); c->stream.vdim = dimension; // compute distance function vector_distance vd = c->table->options.v_distance; vector_type vt = (qtype == VECTOR_QUANT_U8BIT) ? VECTOR_TYPE_U8 : VECTOR_TYPE_I8; + if (qtype == VECTOR_QUANT_1BIT) { + // in case of 1BIT quantization force distance to always be hamming + vt = VECTOR_TYPE_BIT; + vd = VECTOR_DISTANCE_HAMMING; + } distance_function_t distance_fn = dispatch_distance_table[vd][vt]; c->stream.distance_fn = distance_fn; diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index b2fbd69..5d4f934 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.55" +#define SQLITE_VECTOR_VERSION "0.9.60" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From 0ec52ea781af85f27722bb36df352be8036ba35f Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Thu, 22 Jan 2026 11:01:52 +0100 Subject: [PATCH 105/108] Improved BIT support --- src/sqlite-vector.c | 177 ++++++++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 70 deletions(-) diff --git a/src/sqlite-vector.c b/src/sqlite-vector.c index be9b915..3a6fcf4 100644 --- a/src/sqlite-vector.c +++ b/src/sqlite-vector.c @@ -846,7 +846,7 @@ static void quantize_binary_i8 (const int8_t *input, uint8_t *output, int dim) { // MARK: - General Utils - -static size_t vector_type_to_size (vector_type type) { +static int vector_type_to_size (vector_type type) { switch (type) { case VECTOR_TYPE_F32: return sizeof(float); // 4 bytes case VECTOR_TYPE_F16: return sizeof(uint16_t); // 2 bytes @@ -855,7 +855,7 @@ static size_t vector_type_to_size (vector_type type) { case VECTOR_TYPE_I8: return sizeof(int8_t); // 1 byte case VECTOR_TYPE_BIT: return 0; // Special: use vector_bytes_for_dim() } - return SIZE_T_MAX; // error + return -1; // error } static vector_type vector_name_to_type (const char *vname) { @@ -904,6 +904,7 @@ static vector_distance distance_name_to_type (const char *dname) { if (strcasecmp(dname, "INNER") == 0) return VECTOR_DISTANCE_DOT; if (strcasecmp(dname, "L1") == 0) return VECTOR_DISTANCE_L1; if (strcasecmp(dname, "MANHATTAN") == 0) return VECTOR_DISTANCE_L1; + if (strcasecmp(dname, "HAMMING") == 0) return VECTOR_DISTANCE_HAMMING; return 0; } @@ -1302,63 +1303,65 @@ static int vector_rebuild_quantization (sqlite3_context *context, const char *ta if (rc != SQLITE_OK) goto vector_rebuild_quantization_cleanup; // STEP 1 - // find global min/max across ALL vectors + // find global min/max across ALL vectors (skip for 1BIT quantization which uses fixed threshold) #if defined(_WIN32) || defined(__linux__) float min_val = FLT_MAX; float max_val = -FLT_MAX; - #else + #else float min_val = MAXFLOAT; float max_val = -MAXFLOAT; #endif bool contains_negative = false; - - while (1) { - rc = sqlite3_step(vm); - if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} - else if (rc != SQLITE_ROW) break; - if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; - - const void *blob = (float *)sqlite3_column_blob(vm, 1); - if (!blob) continue; - - int blob_size = sqlite3_column_bytes(vm, 1); - size_t need_bytes = vector_bytes_for_dim(type, dim); - if (blob_size < need_bytes) { - context_result_error(context, SQLITE_ERROR, "Invalid vector blob found at rowid %lld", (long long)sqlite3_column_int64(vm, 0)); - rc = SQLITE_ERROR; - goto vector_rebuild_quantization_cleanup; - } - - for (int i = 0; i < dim; ++i) { - float val = 0.0f; - switch (type) { - case VECTOR_TYPE_F32: - val = ((float *)blob)[i]; - break; - case VECTOR_TYPE_F16: - val = float16_to_float32(((uint16_t *)blob)[i]); - break; - case VECTOR_TYPE_BF16: - val = bfloat16_to_float32(((uint16_t *)blob)[i]); - break; - case VECTOR_TYPE_U8: - val = (float)(((uint8_t *)blob)[i]); - break; - case VECTOR_TYPE_I8: - val = (float)(((int8_t *)blob)[i]); - break; - default: - context_result_error(context, SQLITE_ERROR, "Unsupported vector type"); - rc = SQLITE_ERROR; - goto vector_rebuild_quantization_cleanup; + + if (qtype != VECTOR_QUANT_1BIT) { + while (1) { + rc = sqlite3_step(vm); + if (rc == SQLITE_DONE) {rc = SQLITE_OK; break;} + else if (rc != SQLITE_ROW) break; + if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; + + const void *blob = (float *)sqlite3_column_blob(vm, 1); + if (!blob) continue; + + int blob_size = sqlite3_column_bytes(vm, 1); + size_t need_bytes = vector_bytes_for_dim(type, dim); + if (blob_size < need_bytes) { + context_result_error(context, SQLITE_ERROR, "Invalid vector blob found at rowid %lld", (long long)sqlite3_column_int64(vm, 0)); + rc = SQLITE_ERROR; + goto vector_rebuild_quantization_cleanup; } - if (val < min_val) min_val = val; - if (val > max_val) max_val = val; - if (val < 0.0) contains_negative = true; + for (int i = 0; i < dim; ++i) { + float val = 0.0f; + switch (type) { + case VECTOR_TYPE_F32: + val = ((float *)blob)[i]; + break; + case VECTOR_TYPE_F16: + val = float16_to_float32(((uint16_t *)blob)[i]); + break; + case VECTOR_TYPE_BF16: + val = bfloat16_to_float32(((uint16_t *)blob)[i]); + break; + case VECTOR_TYPE_U8: + val = (float)(((uint8_t *)blob)[i]); + break; + case VECTOR_TYPE_I8: + val = (float)(((int8_t *)blob)[i]); + break; + default: + context_result_error(context, SQLITE_ERROR, "Unsupported vector type for 8-bit quantization"); + rc = SQLITE_ERROR; + goto vector_rebuild_quantization_cleanup; + } + + if (val < min_val) min_val = val; + if (val > max_val) max_val = val; + if (val < 0.0) contains_negative = true; + } } } - + // set proper format if (qtype == VECTOR_QUANT_AUTO) { if (contains_negative == true) qtype = VECTOR_QUANT_S8BIT; @@ -1694,13 +1697,17 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec } // allocate blob + // For BIT type, each JSON element is a single bit, pack 8 per byte size_t item_size = vector_type_to_size(type); - size_t alloc = (estimated_count + 1) * item_size; + size_t alloc = (type == VECTOR_TYPE_BIT) ? ((estimated_count + 1) + 7) / 8 : (estimated_count + 1) * item_size; blob = sqlite3_malloc((int)alloc); if (!blob) { return sqlite_common_set_error(context, vtab, SQLITE_NOMEM, "Out of memory: unable to allocate %lld bytes for BLOB buffer", (long long)alloc); } - + if (type == VECTOR_TYPE_BIT) { + memset(blob, 0, alloc); // Initialize to zero for bit packing + } + // typed pointers float *float_blob = (float *)blob; uint8_t *uint8_blob = (uint8_t *)blob; @@ -1728,17 +1735,19 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Malformed JSON: expected a number at position %d (found '%c')", (int)(p - json) + 1, *p ? *p : '?'); } - if (count >= (int)(alloc / item_size)) { + // check bounds + int max_count = (type == VECTOR_TYPE_BIT) ? (int)(alloc * 8) : (int)(alloc / item_size); + if (count >= max_count) { sqlite3_free(blob); return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Too many elements in JSON array"); } - + // convert to proper type switch (type) { case VECTOR_TYPE_F32: float_blob[count++] = (float)value; break; - + case VECTOR_TYPE_F16: uint16_blob[count++] = float32_to_float16((float)value); break; @@ -1746,7 +1755,7 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec case VECTOR_TYPE_BF16: bfloat16_blob[count++] = float32_to_bfloat16((float)value); break; - + case VECTOR_TYPE_U8: if (value < 0 || value > 255) { sqlite3_free(blob); @@ -1754,7 +1763,7 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec } uint8_blob[count++] = (uint8_t)value; break; - + case VECTOR_TYPE_I8: if (value < -128 || value > 127) { sqlite3_free(blob); @@ -1762,7 +1771,18 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec } int8_blob[count++] = (int8_t)value; break; - + + case VECTOR_TYPE_BIT: + if (value != 0 && value != 1) { + sqlite3_free(blob); + return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Value out of range for BIT: expected 0 or 1"); + } + if ((int)value == 1) { + uint8_blob[count / 8] |= (1 << (count % 8)); + } + count++; + break; + default: sqlite3_free(blob); return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Unsupported vector type"); @@ -1796,8 +1816,8 @@ static void *vector_from_json (sqlite3_context *context, sqlite3_vtab *vtab, vec sqlite3_free(blob); return sqlite_common_set_error(context, vtab, SQLITE_ERROR, "Invalid JSON vector dimension: expected %d but found %d", dimension, count); } - - if (size) *size = (int)(count * item_size); + + if (size) *size = (type == VECTOR_TYPE_BIT) ? (int)((count + 7) / 8) : (int)(count * item_size); return blob; } @@ -1846,8 +1866,10 @@ static void vector_as_type (sqlite3_context *context, vector_type type, int argc char *blob = vector_from_json(context, NULL, type, json, &value_size, dimension); if (!blob) return; // error is set in the context - - VECTOR_PRINT((void *)blob, type, (dimension == 0) ? (value_size / vector_type_to_size(type)) : dimension); + + int print_dim = dimension; + if (print_dim == 0) print_dim = (type == VECTOR_TYPE_BIT) ? value_size * 8 : value_size / vector_type_to_size(type); + VECTOR_PRINT((void *)blob, type, print_dim); sqlite3_result_blob(context, (const void *)blob, value_size, sqlite3_free); return; @@ -1876,6 +1898,10 @@ static void vector_as_i8 (sqlite3_context *context, int argc, sqlite3_value **ar vector_as_type(context, VECTOR_TYPE_I8, argc, argv); } +static void vector_as_bit (sqlite3_context *context, int argc, sqlite3_value **argv) { + vector_as_type(context, VECTOR_TYPE_BIT, argc, argv); +} + // MARK: - Modules - static int vFullScanCursorNext (sqlite3_vtab_cursor *cur); @@ -2073,18 +2099,21 @@ static int vFullScanCursorNext (sqlite3_vtab_cursor *cur){ // FULL-SCAN if (!c->is_quantized) { + // For BIT type, use byte count instead of dimension + vector_type vt = c->table->options.v_type; + int dist_size = (vt == VECTOR_TYPE_BIT) ? ((dimension + 7) / 8) : dimension; while (1) { int rc = sqlite3_step(vm); if (rc == SQLITE_DONE) { c->stream.is_eof = 1; return SQLITE_OK; } else if (rc != SQLITE_ROW) return rc; - + // skip NULL values if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; const float *v2 = (const float *)sqlite3_column_blob(vm, 1); if (v2 == NULL) continue; - float distance = distance_fn((const void *)v1, (const void *)v2, dimension); + float distance = distance_fn((const void *)v1, (const void *)v2, dist_size); if (nearly_zero_float32(distance)) distance = 0.0f; c->stream.distance = distance; @@ -2245,18 +2274,20 @@ static int vFullScanRun (sqlite3 *db, vFullScanCursor *c, const void *v1, int v1 // compute distance function vector_distance vd = c->table->options.v_distance; vector_type vt = c->table->options.v_type; + if (vt == VECTOR_TYPE_BIT) vd = VECTOR_DISTANCE_HAMMING; // Force Hamming for BIT type distance_function_t distance_fn = dispatch_distance_table[vd][vt]; - + int dist_size = (vt == VECTOR_TYPE_BIT) ? ((dimension + 7) / 8) : dimension; + while (1) { rc = sqlite3_step(vm); if (rc == SQLITE_DONE) {rc = SQLITE_OK; goto cleanup;} if (rc != SQLITE_ROW) goto cleanup; if (sqlite3_column_type(vm, 1) == SQLITE_NULL) continue; - + float *v2 = (float *)sqlite3_column_blob(vm, 1); if (v2 == NULL) continue; - - float distance = distance_fn((const void *)v1, (const void *)v2, dimension); + + float distance = distance_fn((const void *)v1, (const void *)v2, dist_size); if (nearly_zero_float32(distance)) distance = 0.0; VECTOR_PRINT((void*)v2, vt, dimension); @@ -2486,14 +2517,15 @@ static int vStreamScanCursorRun (sqlite3 *db, vFullScanCursor *c, const void *v1 // compute distance function vector_distance vd = c->table->options.v_distance; vector_type vt = c->table->options.v_type; + if (vt == VECTOR_TYPE_BIT) vd = VECTOR_DISTANCE_HAMMING; // Force Hamming for BIT type distance_function_t distance_fn = dispatch_distance_table[vd][vt]; - + c->stream.distance_fn = distance_fn; c->stream.vm = vm; - + if (sql) sqlite3_free(sql); return SQLITE_OK; - + cleanup: if (sql) sqlite3_free(sql); if (vm) sqlite3_finalize(vm); @@ -2842,7 +2874,12 @@ SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const s if (rc != SQLITE_OK) goto cleanup; rc = sqlite3_create_function(db, "vector_as_u8", 2, SQLITE_UTF8, ctx, vector_as_u8, NULL, NULL); if (rc != SQLITE_OK) goto cleanup; - + + rc = sqlite3_create_function(db, "vector_as_bit", 1, SQLITE_UTF8, ctx, vector_as_bit, NULL, NULL); + if (rc != SQLITE_OK) goto cleanup; + rc = sqlite3_create_function(db, "vector_as_bit", 2, SQLITE_UTF8, ctx, vector_as_bit, NULL, NULL); + if (rc != SQLITE_OK) goto cleanup; + rc = sqlite3_create_module(db, "vector_full_scan", &vFullScanModule, ctx); if (rc != SQLITE_OK) goto cleanup; From 1821cacc54c7bbe85020b554538cf6601f2cce58 Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 23 Jan 2026 09:24:25 +0100 Subject: [PATCH 106/108] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 57513dc..75c85cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite Vector -**SQLite Vector** is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. It works seamlessly on **iOS, Android, Windows, Linux, and macOS**, using just **30MB of memory** by default. With support for **Float32, Float16, BFloat16, Int8, and UInt8**, and **highly optimized distance functions**, it's the ideal solution for **Edge AI** applications. +**SQLite Vector** is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. It works seamlessly on **iOS, Android, Windows, Linux, and macOS**, using just **30MB of memory** by default. With support for **Float32, Float16, BFloat16, Int8, UInt8 and 1Bit**, and **highly optimized distance functions**, it's the ideal solution for **Edge AI** applications. ## Highlights @@ -175,6 +175,7 @@ You can store your vectors as `BLOB` columns in ordinary tables. Supported forma * `bfloat16` (2 bytes per element) * `int8` (1 byte per element) * `uint8` (1 byte per element) +* `1bit` (1 bit per element) Simply insert a vector as a binary blob into your table. No special table types or schemas are required. @@ -188,6 +189,7 @@ Optimized implementations available: * **L1 Distance (Manhattan)** * **Cosine Distance** * **Dot Product** +* **Hamming Distance** (available only with 1bit vectors) These are implemented in pure C and optimized for SIMD when available, ensuring maximum performance on modern CPUs and mobile devices. From 9835b2e78374b45f57ad7eb27497c3a239ef499f Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Fri, 23 Jan 2026 09:29:06 +0100 Subject: [PATCH 107/108] Update API.md --- API.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/API.md b/API.md index 9571228..89bee5e 100644 --- a/API.md +++ b/API.md @@ -78,6 +78,7 @@ This ensures that each vector can be uniquely identified and efficiently referen * `FLOATB16` * `INT8` * `UINT8` + * `1BIT` * `distance`: Distance function to use. Options: * `L2` (default) @@ -85,6 +86,7 @@ This ensures that each vector can be uniquely identified and efficiently referen * `COSINE` * `DOT` * `L1` + * `HAMMING` **Example:** @@ -115,11 +117,13 @@ If a quantization already exists for the specified table and column, it is repla **Available options:** * `max_memory`: Max memory to use for quantization (default: 30MB) +* `qtype`: Quantization type: `UINT8`, `INT8` or `1BIT` **Example:** ```sql SELECT vector_quantize('documents', 'embedding', 'max_memory=50MB'); +SELECT vector_quantize('documents', 'embedding', 'qtype=BIT'); ``` --- @@ -184,6 +188,8 @@ SELECT vector_quantize_cleanup('documents', 'embedding'); ## `vector_as_u8(value)` +## `vector_as_bit(value)` + **Returns:** `BLOB` **Description:** From ff8c96f945c63486bf96c572d2c525e33ea4c218 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 26 Jan 2026 16:27:02 +0100 Subject: [PATCH 108/108] fix(android): increase max page size to 16kb #36 --- Makefile | 2 +- src/sqlite-vector.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 70ea497..7ae5d70 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ else ifeq ($(PLATFORM),android) endif CC = $(BIN)/$(ARCH)-linux-$(ANDROID_ABI)-clang TARGET := $(DIST_DIR)/vector.so - LDFLAGS += -lm -shared + LDFLAGS += -lm -shared -Wl,-z,max-page-size=16384 STRIP = $(BIN)/llvm-strip --strip-unneeded $@ else ifeq ($(PLATFORM),ios) TARGET := $(DIST_DIR)/vector.dylib diff --git a/src/sqlite-vector.h b/src/sqlite-vector.h index 5d4f934..50ab991 100644 --- a/src/sqlite-vector.h +++ b/src/sqlite-vector.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_VECTOR_VERSION "0.9.60" +#define SQLITE_VECTOR_VERSION "0.9.70" SQLITE_VECTOR_API int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);