Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions sqlite-vec.c
Original file line number Diff line number Diff line change
Expand Up @@ -4042,6 +4042,21 @@ int vec0_get_vector_data(vec0_vtab *pVtab, i64 rowid, int vector_column_idx,
void **outVector, int *outVectorSize) {
vec0_vtab *p = pVtab;
int rc, brc;
i64 chunk_id;
i64 chunk_offset;
assert((vector_column_idx >= 0) &&
(vector_column_idx < pVtab->numVectorColumns));

// Every index type registers its rows in _rowids, so a rowid with no
// position there is no row, whichever table holds the column's vectors.
rc = vec0_get_chunk_position(pVtab, rowid, NULL, &chunk_id, &chunk_offset);
if (rc == SQLITE_EMPTY) {
vtab_set_error(&pVtab->base, "Could not find a row with rowid %lld", rowid);
return rc;
}
if (rc != SQLITE_OK) {
return rc;
}

#if SQLITE_VEC_ENABLE_DISKANN
// DiskANN fast path: read from _vectors table
Expand All @@ -4061,9 +4076,6 @@ int vec0_get_vector_data(vec0_vtab *pVtab, i64 rowid, int vector_column_idx,
}
#endif

i64 chunk_id;
i64 chunk_offset;

#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE
// IVF-indexed columns store vectors in _ivf_cells, not _vector_chunks
if (p->vector_columns[vector_column_idx].index_type == VEC0_INDEX_TYPE_IVF) {
Expand All @@ -4074,8 +4086,6 @@ int vec0_get_vector_data(vec0_vtab *pVtab, i64 rowid, int vector_column_idx,
void *buf = NULL;
int blobOffset;
sqlite3_blob *vectorBlob = NULL;
assert((vector_column_idx >= 0) &&
(vector_column_idx < pVtab->numVectorColumns));

#if SQLITE_VEC_ENABLE_RESCORE
// Rescore columns store float vectors in _rescore_vectors (rowid-keyed)
Expand Down Expand Up @@ -4112,15 +4122,6 @@ int vec0_get_vector_data(vec0_vtab *pVtab, i64 rowid, int vector_column_idx,
}
#endif /* SQLITE_VEC_ENABLE_RESCORE */

rc = vec0_get_chunk_position(pVtab, rowid, NULL, &chunk_id, &chunk_offset);
if (rc == SQLITE_EMPTY) {
vtab_set_error(&pVtab->base, "Could not find a row with rowid %lld", rowid);
goto cleanup;
}
if (rc != SQLITE_OK) {
goto cleanup;
}

rc = sqlite3_blob_open(p->db, p->schemaName,
p->shadowVectorChunksNames[vector_column_idx],
"vectors", chunk_id, 0, &vectorBlob);
Expand Down
20 changes: 20 additions & 0 deletions tests/test-diskann.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,26 @@ def test_diskann_delete_single(db):
assert db.execute("SELECT count(*) FROM t_diskann_nodes00").fetchone()[0] == 2


def test_diskann_point_query_missing_rowid(db):
"""SELECT by a rowid the table does not hold returns no rows."""
db.execute("""
CREATE VIRTUAL TABLE t USING vec0(
emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8)
)
""")
for i in range(1, 3):
db.execute(
"INSERT INTO t(rowid, emb) VALUES (?, ?)",
[i, _f32([float(i)] * 8)],
)
db.execute("DELETE FROM t WHERE rowid = 2")

rows = db.execute("SELECT rowid FROM t WHERE rowid = 1").fetchall()
assert [r[0] for r in rows] == [1]
assert db.execute("SELECT rowid FROM t WHERE rowid = 2").fetchall() == []
assert db.execute("SELECT rowid FROM t WHERE rowid = 999").fetchall() == []


def test_diskann_delete_no_stale_references(db):
"""After delete, no node should reference the deleted rowid."""
db.execute("""
Expand Down
15 changes: 15 additions & 0 deletions tests/test-ivf.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ def test_ivf_delete_flat_mode(db):
assert db.execute("SELECT count(*) FROM t_ivf_rowid_map00").fetchone()[0] == 1


def test_ivf_point_query_missing_rowid(db):
"""SELECT by a rowid the table does not hold returns no rows."""
db.execute(
"CREATE VIRTUAL TABLE t USING vec0(v float[4] indexed by ivf(nlist=4))"
)
db.execute("INSERT INTO t(rowid, v) VALUES (1, ?)", [_f32([1, 2, 3, 4])])
db.execute("INSERT INTO t(rowid, v) VALUES (2, ?)", [_f32([5, 6, 7, 8])])
db.execute("DELETE FROM t WHERE rowid = 2")

rows = db.execute("SELECT rowid FROM t WHERE rowid = 1").fetchall()
assert [r[0] for r in rows] == [1]
assert db.execute("SELECT rowid FROM t WHERE rowid = 2").fetchall() == []
assert db.execute("SELECT rowid FROM t WHERE rowid = 999").fetchall() == []


# ============================================================================
# KNN flat mode tests
# ============================================================================
Expand Down
20 changes: 20 additions & 0 deletions tests/test-rescore.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,26 @@ def test_point_query_returns_float(db):
assert abs(a - b) < 1e-6


@pytest.mark.parametrize("quantizer", ["bit", "int8"])
def test_point_query_missing_rowid(db, quantizer):
"""SELECT by a rowid the table does not hold returns no rows."""
db.execute(
"CREATE VIRTUAL TABLE t USING vec0("
f" embedding float[8] indexed by rescore(quantizer={quantizer})"
")"
)
for rowid in (1, 2):
db.execute(
"INSERT INTO t(rowid, embedding) VALUES (?, ?)",
[rowid, float_vec([0.1 * rowid] * 8)],
)
db.execute("DELETE FROM t WHERE rowid = 2")
rows = db.execute("SELECT rowid FROM t WHERE rowid = 1").fetchall()
assert [r["rowid"] for r in rows] == [1]
assert db.execute("SELECT rowid FROM t WHERE rowid = 2").fetchall() == []
assert db.execute("SELECT rowid FROM t WHERE rowid = 999").fetchall() == []


# ============================================================================
# KNN tests
# ============================================================================
Expand Down