Skip to content

Commit 7da0869

Browse files
Christopher Krahchrikrah
authored andcommitted
fix: sort sparse coordinates when building a SparseVector from SciPy
`tocoo()` returns coordinates in whatever order the source array holds them, so a coo_array built from unordered triplets produced a SparseVector whose indices descend. `sparsevec_recv` rejects that with "sparsevec indices must be in ascending order", which is the error an asyncpg or psycopg user sees on insert, and `_from_dict` already sorts for the same reason.
1 parent 99a6776 commit 7da0869

2 files changed

Lines changed: 19 additions & 3 deletions

File tree

‎pgvector/sparsevec.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,15 @@ def _from_sparse(self, arr: sparray | spmatrix, /) -> None:
115115

116116
if hasattr(value, 'coords'):
117117
# scipy 1.13+
118-
self._indices = value.coords[-1].tolist()
118+
indices = value.coords[-1].tolist()
119119
else:
120-
self._indices = value.col.tolist()
121-
self._values = [float(v) for v in value.data]
120+
indices = value.col.tolist()
121+
122+
# coordinates are not necessarily ordered, and sparsevec requires ascending indices
123+
elements = sorted(zip(indices, value.data))
124+
125+
self._indices = [int(v[0]) for v in elements]
126+
self._values = [float(v[1]) for v in elements]
122127

123128
def _from_dense(self, value: list[float] | ndarray, /) -> None:
124129
self._dim = len(value)

‎tests/test_sparse_vector.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ def test_coo_array(self) -> None:
6060
assert vec.indices() == [0, 2, 4]
6161
assert isinstance(vec.values()[0], float)
6262

63+
def test_coo_array_unordered(self) -> None:
64+
if np is None or sparse is None:
65+
pytest.skip('NumPy and SciPy required')
66+
67+
arr = sparse.coo_array(([3, 1, 2], ([4, 0, 2],)), shape=(6,))
68+
vec = SparseVector(arr)
69+
assert vec.indices() == [0, 2, 4]
70+
assert vec.values() == [1, 2, 3]
71+
assert vec.to_text() == '{1:1.0,3:2.0,5:3.0}/6'
72+
assert vec.to_binary() == pack('>iii3i3f', 6, 3, 0, 0, 2, 4, 1, 2, 3)
73+
6374
def test_coo_array_dimensions(self) -> None:
6475
if np is None or sparse is None:
6576
pytest.skip('NumPy and SciPy required')

0 commit comments

Comments
 (0)