/** Check if neighbor slot i is valid. */
int diskann_validity_get(const u8 *validity, int i) { return (validity[i / CHAR_BIT] >> (i % CHAR_BIT)) & 1;
}
/** Set neighbor slot i as valid (1) or invalid (0). */ void diskann_validity_set(u8 *validity, int i, int value) {
if (value) {
validity[i / CHAR_BIT] |= (1 << (i % CHAR_BIT));
} else {
validity[i / CHAR_BIT] &= ~(1 << (i % CHAR_BIT));
}
}
/** Count the number of valid neighbors. */
int diskann_validity_count(const u8 *validity, int n_neighbors) {
int count = 0;
for (int i = 0; i < n_neighbors; i++) {
count += diskann_validity_get(validity, i);
} return count;
}
/** Get the rowid of the neighbor in slot i. */
i64 diskann_neighbor_id_get(const u8 *neighbor_ids, int i) {
i64 result;
memcpy(&result, neighbor_ids + i * sizeof(i64), sizeof(i64)); return result;
}
/** Set the rowid of the neighbor in slot i. */ void diskann_neighbor_id_set(u8 *neighbor_ids, int i, i64 rowid) {
memcpy(neighbor_ids + i * sizeof(i64), &rowid, sizeof(i64));
}
/** Get a pointer to the quantized vector in slot i (read-only). */ const u8 *diskann_neighbor_qvec_get( const u8 *qvecs, int i,
enum Vec0DiskannQuantizerType quantizer_type, size_t dimensions) {
size_t qvec_size = diskann_quantized_vector_byte_size(quantizer_type, dimensions); return qvecs + (size_t)i * qvec_size;
}
/** Copy a quantized vector into slot i. */ void diskann_neighbor_qvec_set(
u8 *qvecs, int i, const u8 *src_qvec,
enum Vec0DiskannQuantizerType quantizer_type, size_t dimensions) {
size_t qvec_size = diskann_quantized_vector_byte_size(quantizer_type, dimensions);
memcpy(qvecs + (size_t)i * qvec_size, src_qvec, qvec_size);
}
/** *Setneighborslotiwitharowidandquantizedvector,andmarkasvalid.
*/ void diskann_node_set_neighbor(
u8 *validity, u8 *neighbor_ids, u8 *qvecs, int i,
i64 neighbor_rowid, const u8 *neighbor_qvec,
enum Vec0DiskannQuantizerType quantizer_type, size_t dimensions) {
diskann_validity_set(validity, i, 1);
diskann_neighbor_id_set(neighbor_ids, i, neighbor_rowid);
diskann_neighbor_qvec_set(qvecs, i, neighbor_qvec, quantizer_type, dimensions);
}
/** *Clearneighborsloti(markinvalid,zerooutdata).
*/ void diskann_node_clear_neighbor(
u8 *validity, u8 *neighbor_ids, u8 *qvecs, int i,
enum Vec0DiskannQuantizerType quantizer_type, size_t dimensions) {
diskann_validity_set(validity, i, 0);
diskann_neighbor_id_set(neighbor_ids, i, 0);
size_t qvec_size = diskann_quantized_vector_byte_size(quantizer_type, dimensions);
memset(qvecs + (size_t)i * qvec_size, 0, qvec_size);
}
// Check for duplicate
for (int i = 0; i < list->count; i++) {
if (list->items[i].rowid == rowid) { // Update distance if better
if (distance < list->items[i].distance) {
list->items[i].distance = distance; // Re-sort this item into position struct Vec0DiskannCandidate tmp = list->items[i];
int j = i - 1; while (j >= 0 && list->items[j].distance > tmp.distance) {
list->items[j + 1] = list->items[j];
j--;
}
list->items[j + 1] = tmp;
} return1;
}
}
// If at capacity, check if new candidate is better than worst
if (list->count >= list->capacity) {
if (distance >= list->items[list->count - 1].distance) { return0; // Discard
}
list->count--; // Make room by dropping the worst
}
// Binary search for insertion point
int lo = 0, hi = list->count; while (lo < hi) {
int mid = (lo + hi) / 2;
if (list->items[mid].distance < distance) {
lo = mid + 1;
} else {
hi = mid;
}
}
// Shift elements to make room
memmove(&list->items[lo + 1], &list->items[lo],
(list->count - lo) * sizeof(struct Vec0DiskannCandidate));
/** *Findtheclosestunvisitedcandidate.Returnsitsindex,or-1ifnone.
*/ static int diskann_candidate_list_next_unvisited( conststruct DiskannCandidateList *list) {
for (int i = 0; i < list->count; i++) {
if (!list->items[i].visited) return i;
} return -1;
}
/** *Simplehashsetfortrackingvisitedrowidsduringsearch. *Usesopenaddressingwithlinearprobing.
*/ struct DiskannVisitedSet {
i64 *slots;
int capacity;
int count;
};
static int diskann_visited_set_init(struct DiskannVisitedSet *set, int capacity) { // Round up to power of 2
int cap = 16; while (cap < capacity) cap *= 2;
set->slots = sqlite3_malloc(cap * sizeof(i64));
if (!set->slots) return SQLITE_NOMEM;
memset(set->slots, 0, cap * sizeof(i64));
set->capacity = cap;
set->count = 0; return SQLITE_OK;
}
// Read the node's neighbor data
u8 *validity = NULL, *neighborIds = NULL, *qvecs = NULL;
int validitySize, neighborIdsSize, qvecsSize;
rc = diskann_node_read(p, vec_col_idx, currentRowid,
&validity, &validitySize,
&neighborIds, &neighborIdsSize,
&qvecs, &qvecsSize);
if (rc != SQLITE_OK) { continue; // Skip if node doesn't exist
}
// Insert all valid neighbors with approximate (quantized) distances
for (int i = 0; i < cfg->n_neighbors; i++) {
if (!diskann_validity_get(validity, i)) continue;
// Add to visited set
diskann_visited_set_insert(&visited, currentRowid);
// Paper line 13: Re-rank p* using full-precision distance // We already have exact distance for medoid; for others, update now void *fullVec = NULL;
int fullVecSize;
rc = diskann_vector_read(p, vec_col_idx, currentRowid, &fullVec, &fullVecSize);
if (rc == SQLITE_OK) {
f32 exactDist = vec0_distance_full(queryVector, fullVec,
dimensions, elementType,
col->distance_metric);
sqlite3_free(fullVec); // Update distance in candidate list and re-sort
diskann_candidate_list_insert(&candidates, currentRowid, exactDist); // Mark as confirmed (vector exists, distance is exact)
for (int ci = 0; ci < candidates.count; ci++) {
if (candidates.items[ci].rowid == currentRowid) {
candidates.items[ci].confirmed = 1; break;
}
}
} // If vector read failed, candidate stays unconfirmed (stale edge to deleted node)
}
// 5. Output results — only include confirmed candidates (whose vectors exist)
int resultCount = 0;
for (int i = 0; i < candidates.count && resultCount < k; i++) {
if (candidates.items[i].confirmed) {
outRowids[resultCount] = candidates.items[i].rowid;
outDistances[resultCount] = candidates.items[i].distance;
resultCount++;
}
}
*outCount = resultCount;
int currentCount = diskann_validity_count(validity, cfg->n_neighbors);
// Check if target is already a neighbor
for (int i = 0; i < cfg->n_neighbors; i++) {
if (diskann_validity_get(validity, i) &&
diskann_neighbor_id_get(neighborIds, i) == target_rowid) {
sqlite3_free(validity);
sqlite3_free(neighborIds);
sqlite3_free(qvecs); return SQLITE_OK;
}
}
if (currentCount < cfg->n_neighbors) { // Room available: find first empty slot
for (int i = 0; i < cfg->n_neighbors; i++) {
if (!diskann_validity_get(validity, i)) {
size_t qvecSize = diskann_quantized_vector_byte_size(
cfg->quantizer_type, col->dimensions);
u8 *qvec = sqlite3_malloc(qvecSize);
if (!qvec) {
sqlite3_free(validity);
sqlite3_free(neighborIds);
sqlite3_free(qvecs); return SQLITE_NOMEM;
}
rc = diskann_node_write(p, vec_col_idx, node_rowid,
validity, validitySize,
neighborIds, neighborIdsSize,
qvecs, qvecsSize);
} else { // Full: lazy replacement — use quantized distances to find the worst // existing neighbor and replace it if target is closer. This avoids // reading all neighbors' float vectors (the expensive RobustPrune path).
// Quantize the node's vector and the target vector for comparison void *nodeVector = NULL;
int nodeVecSize;
rc = diskann_vector_read(p, vec_col_idx, node_rowid,
&nodeVector, &nodeVecSize);
if (rc != SQLITE_OK) {
sqlite3_free(validity);
sqlite3_free(neighborIds);
sqlite3_free(qvecs); return rc;
}
/** *Deleteavectorfromthe_diskann_buffertable.
*/ static int diskann_buffer_delete(vec0_vtab *p, int vec_col_idx, i64 rowid) {
sqlite3_stmt *stmt = NULL;
char *zSql = sqlite3_mprintf( "DELETE FROM " VEC0_SHADOW_DISKANN_BUFFER_N_NAME " WHERE rowid = ?",
p->schemaName, p->tableName, vec_col_idx);
if (!zSql) return SQLITE_NOMEM;
int rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
sqlite3_free(zSql);
if (rc != SQLITE_OK) return rc;
sqlite3_bind_int64(stmt, 1, rowid);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt); return (rc == SQLITE_DONE) ? SQLITE_OK : SQLITE_ERROR;
}
/** *Checkifarowidexistsinthe_diskann_buffertable. *ReturnsSQLITE_OKandsets*existsto1iffound,0ifnot.
*/ static int diskann_buffer_exists(vec0_vtab *p, int vec_col_idx,
i64 rowid, int *exists) {
sqlite3_stmt *stmt = NULL;
char *zSql = sqlite3_mprintf( "SELECT 1 FROM " VEC0_SHADOW_DISKANN_BUFFER_N_NAME " WHERE rowid = ?",
p->schemaName, p->tableName, vec_col_idx);
if (!zSql) return SQLITE_NOMEM;
int rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
sqlite3_free(zSql);
if (rc != SQLITE_OK) return rc;
sqlite3_bind_int64(stmt, 1, rowid);
rc = sqlite3_step(stmt);
*exists = (rc == SQLITE_ROW) ? 1 : 0;
sqlite3_finalize(stmt); return SQLITE_OK;
}
/** *Getthecountofrowsinthe_diskann_buffertable.
*/ static int diskann_buffer_count(vec0_vtab *p, int vec_col_idx, i64 *count) {
sqlite3_stmt *stmt = NULL;
char *zSql = sqlite3_mprintf( "SELECT count(*) FROM " VEC0_SHADOW_DISKANN_BUFFER_N_NAME,
p->schemaName, p->tableName, vec_col_idx);
if (!zSql) return SQLITE_NOMEM;
int rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
sqlite3_free(zSql);
if (rc != SQLITE_OK) return rc;
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
*count = sqlite3_column_int64(stmt, 0);
sqlite3_finalize(stmt); return SQLITE_OK;
}
sqlite3_finalize(stmt); return SQLITE_ERROR;
}
// Forward declaration: diskann_insert_graph does the actual graph insertion static int diskann_insert_graph(vec0_vtab *p, int vec_col_idx,
i64 rowid, constvoid *vector);
/** *FlushallbufferedvectorsintotheDiskANNgraph. *Iteratesover_diskann_bufferrowsandcallsdiskann_insert_graphforeach.
*/ static int diskann_flush_buffer(vec0_vtab *p, int vec_col_idx) {
sqlite3_stmt *stmt = NULL;
char *zSql = sqlite3_mprintf( "SELECT rowid, vector FROM " VEC0_SHADOW_DISKANN_BUFFER_N_NAME,
p->schemaName, p->tableName, vec_col_idx);
if (!zSql) return SQLITE_NOMEM;
int rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
sqlite3_free(zSql);
if (rc != SQLITE_OK) return rc;
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
i64 rowid = sqlite3_column_int64(stmt, 0); constvoid *vector = sqlite3_column_blob(stmt, 1);
if (!vector) continue; // Note: vector is already written to _vectors table, so // diskann_insert_graph will skip re-writing it (vector already exists). // We call the graph-only insert path.
int insertRc = diskann_insert_graph(p, vec_col_idx, rowid, vector);
if (insertRc != SQLITE_OK) {
sqlite3_finalize(stmt); return insertRc;
}
}
sqlite3_finalize(stmt);
// RobustPrune to select neighbors for x
i64 *selectedNeighbors = sqlite3_malloc(cfg->n_neighbors * sizeof(i64));
int selectedCount = 0;
if (!selectedNeighbors) {
sqlite3_free(searchRowids);
sqlite3_free(searchDistances); return SQLITE_NOMEM;
}
// 1. Write full-precision vector to _vectors table (always needed for queries)
rc = diskann_vector_write(p, vec_col_idx, rowid, vector, (int)vectorSize);
if (rc != SQLITE_OK) return rc;
// 2. If buffering is enabled, write to buffer instead of graph
if (cfg->buffer_threshold > 0) {
rc = diskann_buffer_write(p, vec_col_idx, rowid, vector, (int)vectorSize);
if (rc != SQLITE_OK) return rc;
// For each neighbor of the deleted node, fix their neighbor list
for (int dn = 0; dn < deleted_neighbor_count; dn++) {
i64 nodeRowid = deleted_neighbors[dn];
// Find and clear the deleted node's slot
int clearedSlot = -1;
for (int i = 0; i < cfg->n_neighbors; i++) {
if (diskann_validity_get(validity, i) &&
diskann_neighbor_id_get(neighborIds, i) == deleted_rowid) {
diskann_node_clear_neighbor(validity, neighborIds, qvecs, i,
cfg->quantizer_type, col->dimensions);
clearedSlot = i; break;
}
}
if (clearedSlot >= 0) { // Try to fill the cleared slot with one of the deleted node's other neighbors
for (int di = 0; di < deleted_neighbor_count; di++) {
i64 candidate = deleted_neighbors[di];
if (candidate == nodeRowid || candidate == deleted_rowid) continue;
// Check not already a neighbor
int alreadyNeighbor = 0;
for (int ni = 0; ni < cfg->n_neighbors; ni++) {
if (diskann_validity_get(validity, ni) &&
diskann_neighbor_id_get(neighborIds, ni) == candidate) {
alreadyNeighbor = 1; break;
}
}
if (alreadyNeighbor) continue;
// Load, quantize, and set void *candidateVec = NULL;
int cvs;
rc = diskann_vector_read(p, vec_col_idx, candidate, &candidateVec, &cvs);
if (rc != SQLITE_OK) continue;
int nSlots = idsBytes / (int)sizeof(i64);
if (nSlots > cfg->n_neighbors) nSlots = cfg->n_neighbors;
for (int i = 0; i < nSlots; i++) {
if (!diskann_validity_get(validity, i)) continue;
i64 nid = diskann_neighbor_id_get(ids, i);
if (nid == deleted_rowid) {
i64 nodeRowid = sqlite3_column_int64(stmt, 0); // Add to dirty list
if (nDirty >= capDirty) {
capDirty = capDirty ? capDirty * 2 : 16;
i64 *tmp = sqlite3_realloc64(dirty, capDirty * sizeof(i64));
if (!tmp) { sqlite3_free(dirty); sqlite3_finalize(stmt); return SQLITE_NOMEM; }
dirty = tmp;
}
dirty[nDirty++] = nodeRowid; break; // one match per node is enough
}
}
}
sqlite3_finalize(stmt);
// Now do full read/clear/write for each dirty node
for (int d = 0; d < nDirty; d++) {
u8 *val = NULL, *nids = NULL, *qvecs = NULL;
int vs, nis, qs;
rc = diskann_node_read(p, vec_col_idx, dirty[d],
&val, &vs, &nids, &nis, &qvecs, &qs);
if (rc != SQLITE_OK) continue;
int modified = 0;
for (int i = 0; i < cfg->n_neighbors; i++) {
if (diskann_validity_get(val, i) &&
diskann_neighbor_id_get(nids, i) == deleted_rowid) {
diskann_node_clear_neighbor(val, nids, qvecs, i,
cfg->quantizer_type, col->dimensions);
modified = 1;
}
}
sqlite3_free(val);
sqlite3_free(nids);
sqlite3_free(qvecs);
if (rc != SQLITE_OK) break;
}
sqlite3_free(dirty); return rc;
}
static int diskann_delete(vec0_vtab *p, int vec_col_idx, i64 rowid) { struct VectorColumnDefinition *col = &p->vector_columns[vec_col_idx]; struct Vec0DiskannConfig *cfg = &col->diskann;
int rc;
// Check if this rowid is in the buffer (not yet in graph)
if (cfg->buffer_threshold > 0) {
int inBuffer = 0;
rc = diskann_buffer_exists(p, vec_col_idx, rowid, &inBuffer);
if (rc != SQLITE_OK) return rc;
if (inBuffer) { // Just remove from buffer and _vectors, no graph repair needed
rc = diskann_buffer_delete(p, vec_col_idx, rowid);
if (rc == SQLITE_OK) {
rc = diskann_vector_delete(p, vec_col_idx, rowid);
} return rc;
}
}
// 1. Read the node to get its neighbor list
u8 *delValidity = NULL, *delNeighborIds = NULL, *delQvecs = NULL;
int dvs, dnis, dqs;
rc = diskann_node_read(p, vec_col_idx, rowid,
&delValidity, &dvs, &delNeighborIds, &dnis,
&delQvecs, &dqs);
if (rc != SQLITE_OK) { return SQLITE_OK; // Node doesn't exist, nothing to do
}
i64 *deletedNeighbors = sqlite3_malloc(cfg->n_neighbors * sizeof(i64));
int deletedNeighborCount = 0;
if (!deletedNeighbors) {
sqlite3_free(delValidity);
sqlite3_free(delNeighborIds);
sqlite3_free(delQvecs); return SQLITE_NOMEM;
}
for (int i = 0; i < cfg->n_neighbors; i++) {
if (diskann_validity_get(delValidity, i)) {
deletedNeighbors[deletedNeighborCount++] =
diskann_neighbor_id_get(delNeighborIds, i);
}
}
// 5. Scrub stale reverse edges — removes deleted rowid + quantized vector // from any node that still references it (data leak prevention)
if (rc == SQLITE_OK) {
rc = diskann_scrub_deleted_rowid(p, vec_col_idx, rowid);
}
return rc;
}
static int vec0_all_columns_diskann(vec0_vtab *p) {
for (int i = 0; i < p->numVectorColumns; i++) {
if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_DISKANN) return0;
} return p->numVectorColumns > 0;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.