From 2c137b57290dea178fd21b205ede1b8eaa21e1cc Mon Sep 17 00:00:00 2001 From: mhatipog <29236961+mhatipog@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:41:19 +0000 Subject: [PATCH] Fix sparse vector Map serialization order --- src/sparse-vector.js | 12 ++++++++++-- tests/pg.test.js | 5 +++++ tests/sparse-vector.test.js | 15 +++++++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/sparse-vector.js b/src/sparse-vector.js index 940ca63..be14d1d 100644 --- a/src/sparse-vector.js +++ b/src/sparse-vector.js @@ -108,14 +108,22 @@ export class SparseVector { this.dimensions = Number(dimensions); const entries = map instanceof Map ? map.entries() : Object.entries(map); + /** @type {Array<[number, number]>} */ + const elements = []; for (const [i, v] of entries) { const f = Number(v); if (f != 0) { - this.indices.push(Number(i)); - this.values.push(f); + elements.push([Number(i), f]); } } + // PostgreSQL requires sparsevec indices in ascending order. + elements.sort((a, b) => a[0] - b[0]); + for (const [i, v] of elements) { + this.indices.push(i); + this.values.push(v); + } + // TODO uncomment in 0.4.0 // this.#validate(); } diff --git a/tests/pg.test.js b/tests/pg.test.js index 8a88baa..6e0c398 100644 --- a/tests/pg.test.js +++ b/tests/pg.test.js @@ -29,6 +29,11 @@ test('pg example', async () => { assert.deepEqual(rows[0].binary_embedding, '000'); assert.deepEqual(rows[0].sparse_embedding.toArray(), [1, 1, 1]); + // Map insertion order is not necessarily the order required by sparsevec. + const sparse = new SparseVector(new Map([[2, 3], [0, 1]]), 3); + const result = await client.query('INSERT INTO pg_items (sparse_embedding) VALUES ($1) RETURNING sparse_embedding', [sparse]); + assert.deepEqual(result.rows[0].sparse_embedding.toArray(), [1, 0, 3]); + await client.query('CREATE INDEX ON pg_items USING hnsw (embedding vector_l2_ops)'); await client.end(); diff --git a/tests/sparse-vector.test.js b/tests/sparse-vector.test.js index bafd7af..4d6ad9a 100644 --- a/tests/sparse-vector.test.js +++ b/tests/sparse-vector.test.js @@ -34,8 +34,19 @@ test('fromMap', () => { map.set(3, 0); const vec = new SparseVector(map, 6); assert.equal(vec.dimensions, 6); - assert.deepEqual(vec.indices, [2, 4, 0]); - assert.deepEqual(vec.values, [2, 3, 1]); + assert.deepEqual(vec.indices, [0, 2, 4]); + assert.deepEqual(vec.values, [1, 2, 3]); + assert.equal(vec.toPostgres(), '{1:1,3:2,5:3}/6'); + assert.deepEqual(vec.toArray(), [1, 0, 2, 0, 3, 0]); +}); + +test('fromMapUnsortedNegativeValues', () => { + const map = new Map([[5, -4.5], [1, 0], [3, -2], [0, 1.25]]); + const vec = new SparseVector(map, 7); + assert.deepEqual(vec.indices, [0, 3, 5]); + assert.deepEqual(vec.values, [1.25, -2, -4.5]); + assert.equal(vec.toPostgres(), '{1:1.25,4:-2,6:-4.5}/7'); + assert.deepEqual([...map], [[5, -4.5], [1, 0], [3, -2], [0, 1.25]]); }); test('fromMapObject', () => {