// When opened standalone in an editor, pull in sqlite-vec.c so the LSP // can resolve all types (vec0_vtab, VectorColumnDefinition, etc.). // When #include'd from sqlite-vec.c, SQLITE_VEC_H is already defined. #ifndef SQLITE_VEC_H #include"sqlite-vec.c"// IWYU pragma: keep #endif
// Read value — can be digit or identifier
rc = vec0_scanner_next(scanner, &token);
if (rc != VEC0_TOKEN_RESULT_SOME) return SQLITE_ERROR;
if (token.token_type != TOKEN_TYPE_DIGIT &&
token.token_type != TOKEN_TYPE_IDENTIFIER) return SQLITE_ERROR;
char *val = token.start;
int valLength = token.end - token.start;
if (sqlite3_strnicmp(key, "nlist", keyLength) == 0) {
if (token.token_type != TOKEN_TYPE_DIGIT) return SQLITE_ERROR;
int v = atoi(val);
if (v < 0 || v > VEC0_IVF_MAX_NLIST) return SQLITE_ERROR;
config->nlist = v;
} else if (sqlite3_strnicmp(key, "nprobe", keyLength) == 0) {
if (token.token_type != TOKEN_TYPE_DIGIT) return SQLITE_ERROR;
int v = atoi(val);
if (v < 1 || v > VEC0_IVF_MAX_NLIST) return SQLITE_ERROR;
config->nprobe = v;
nprobe_explicit = 1;
} else if (sqlite3_strnicmp(key, "quantizer", keyLength) == 0) {
if (token.token_type != TOKEN_TYPE_IDENTIFIER) return SQLITE_ERROR;
if (sqlite3_strnicmp(val, "none", valLength) == 0) {
config->quantizer = VEC0_IVF_QUANTIZER_NONE;
} else if (sqlite3_strnicmp(val, "int8", valLength) == 0) {
config->quantizer = VEC0_IVF_QUANTIZER_INT8;
} else if (sqlite3_strnicmp(val, "binary", valLength) == 0) {
config->quantizer = VEC0_IVF_QUANTIZER_BINARY;
} else { return SQLITE_ERROR;
}
} else if (sqlite3_strnicmp(key, "oversample", keyLength) == 0) {
if (token.token_type != TOKEN_TYPE_DIGIT) return SQLITE_ERROR;
int v = atoi(val);
if (v < 1) return SQLITE_ERROR;
config->oversample = v;
} else { return SQLITE_ERROR;
}
rc = vec0_scanner_next(scanner, &token);
if (rc != VEC0_TOKEN_RESULT_SOME) return SQLITE_ERROR;
if (token.token_type == TOKEN_TYPE_RPAREN) break;
if (token.token_type != TOKEN_TYPE_COMMA) return SQLITE_ERROR;
rc = vec0_scanner_next(scanner, &token);
}
if (config->nprobe < 0) config->nprobe = VEC0_IVF_DEFAULT_NPROBE;
if (config->nlist > 0 && config->nprobe > config->nlist) {
if (nprobe_explicit) return SQLITE_ERROR;
config->nprobe = config->nlist;
}
// Validation: oversample > 1 only makes sense with quantization
if (config->oversample > 1 && config->quantizer == VEC0_IVF_QUANTIZER_NONE) { return SQLITE_ERROR;
}
/** *Sizeofastoredvectorinbytes,accountingforquantization.
*/ static int ivf_vec_size(vec0_vtab *p, int col_idx) {
int D = (int)p->vector_columns[col_idx].dimensions; switch (p->vector_columns[col_idx].ivf.quantizer) { case VEC0_IVF_QUANTIZER_INT8: return D; case VEC0_IVF_QUANTIZER_BINARY: return D / 8; default: return D * (int)sizeof(float);
}
}
/** *Sizeofthefull-precisionvectorinbytes(alwaysfloat32).
*/ static int ivf_full_vec_size(vec0_vtab *p, int col_idx) { return (int)(p->vector_columns[col_idx].dimensions * sizeof(float));
}
/** *Quantizefloat32vectortoint8. *Usesunitnormalization:clampto[-1,1],scaleto[-127,127].
*/
IVF_STATIC void ivf_quantize_int8(const float *src, int8_t *dst, int D) {
for (int i = 0; i < D; i++) {
float v = src[i];
if (v > 1.0f) v = 1.0f;
if (v < -1.0f) v = -1.0f;
dst[i] = (int8_t)(v * 127.0f);
}
}
/** *Quantizefloat32vectortobinary(sign-bitquantization). *Eachbit=1ifsrc[i]>0,else0.
*/
IVF_STATIC void ivf_quantize_binary(const float *src, uint8_t *dst, int D) {
memset(dst, 0, D / 8);
for (int i = 0; i < D; i++) {
if (src[i] > 0.0f) {
dst[i / 8] |= (1 << (i % 8));
}
}
}
/** *Quantizeafloat32vectortothetargettypebasedonconfig. *dstmustbepre-allocatedtoivf_vec_size()bytes. *Ifquantizer=none,copiessrcas-is.
*/ staticvoid ivf_quantize(vec0_vtab *p, int col_idx, const float *src, void *dst) {
int D = (int)p->vector_columns[col_idx].dimensions; switch (p->vector_columns[col_idx].ivf.quantizer) { case VEC0_IVF_QUANTIZER_INT8:
ivf_quantize_int8(src, (int8_t *)dst, D); break; case VEC0_IVF_QUANTIZER_BINARY:
ivf_quantize_binary(src, (uint8_t *)dst, D); break; default:
memcpy(dst, src, D * sizeof(float)); break;
}
}
/** *Createanewcellrow.Returnsthenewcell_id(rowid)via*out_cell_id.
*/ static int ivf_cell_create(vec0_vtab *p, int col_idx, i64 centroid_id,
i64 *out_cell_id) {
sqlite3_stmt *stmt = NULL;
int rc;
int cap = VEC0_IVF_CELL_MAX_VECTORS;
int vecSize = ivf_vec_size(p, col_idx);
char *zSql = sqlite3_mprintf( "INSERT INTO " VEC0_SHADOW_IVF_CELLS_NAME " (centroid_id, n_vectors, validity, rowids, vectors) VALUES (?, 0, ?, ?, ?)",
p->schemaName, p->tableName, col_idx);
if (!zSql) return SQLITE_NOMEM;
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
sqlite3_free(zSql);
if (rc != SQLITE_OK) return rc;
sqlite3_bind_int64(stmt, 1, centroid_id);
sqlite3_bind_zeroblob(stmt, 2, cap / 8);
sqlite3_bind_zeroblob(stmt, 3, cap * (int)sizeof(i64));
sqlite3_bind_zeroblob(stmt, 4, cap * vecSize);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return SQLITE_ERROR;
if (out_cell_id) *out_cell_id = sqlite3_last_insert_rowid(p->db); return SQLITE_OK;
}
/** *Findacellwithspaceforthegivencentroid,orcreateone. *Returnscell_id(rowid)andcurrentn_vectors.
*/ static int ivf_cell_find_or_create(vec0_vtab *p, int col_idx, i64 centroid_id,
i64 *out_cell_id, int *out_n) {
int rc; // Find existing cell with space
rc = ivf_ensure_stmt(p, &p->stmtIvfCellMeta[col_idx], "SELECT rowid, n_vectors FROM " VEC0_SHADOW_IVF_CELLS_NAME " WHERE centroid_id = ? AND n_vectors < %d LIMIT 1",
col_idx); // The %d in the format won't work with ivf_ensure_stmt since it only has 3 // format args. Use a direct approach instead.
sqlite3_finalize(p->stmtIvfCellMeta[col_idx]);
p->stmtIvfCellMeta[col_idx] = NULL;
char *zSql = sqlite3_mprintf( "SELECT rowid, n_vectors FROM " VEC0_SHADOW_IVF_CELLS_NAME " WHERE centroid_id = ? AND n_vectors < %d LIMIT 1",
p->schemaName, p->tableName, col_idx, VEC0_IVF_CELL_MAX_VECTORS);
if (!zSql) return SQLITE_NOMEM; // Cache this manually
if (!p->stmtIvfCellMeta[col_idx]) {
rc = sqlite3_prepare_v2(p->db, zSql, -1, &p->stmtIvfCellMeta[col_idx], NULL);
sqlite3_free(zSql);
if (rc != SQLITE_OK) return rc;
} else {
sqlite3_free(zSql);
}
// No cell with space — create new one
rc = ivf_cell_create(p, col_idx, centroid_id, out_cell_id);
*out_n = 0; return rc;
}
/** *Insertvectorintocellatslot=n_vectors(append). *Cellmusthavespace(n_vectors<VEC0_IVF_CELL_MAX_VECTORS).
*/ static int ivf_cell_insert(vec0_vtab *p, int col_idx, i64 centroid_id,
i64 rowid, constvoid *vectorData, int vectorSize) {
int rc;
i64 cell_id;
int n_vectors;
static int ivf_insert(vec0_vtab *p, int col_idx, i64 rowid, constvoid *vectorData, int vectorSize) {
UNUSED_PARAMETER(vectorSize);
int quantizer = p->vector_columns[col_idx].ivf.quantizer;
int qvecSize = ivf_vec_size(p, col_idx);
int rc;
// Quantize the input vector (or copy as-is if no quantization) void *qvec = sqlite3_malloc(qvecSize);
if (!qvec) return SQLITE_NOMEM;
ivf_quantize(p, col_idx, (const float *)vectorData, qvec);
if (!ivf_is_trained(p, col_idx)) {
rc = ivf_cell_insert(p, col_idx, VEC0_IVF_UNASSIGNED_CENTROID_ID,
rowid, qvec, qvecSize);
} else { // Find nearest centroid using quantized distance
int best_centroid = -1;
float min_dist = FLT_MAX;
// Decrement n_vectors
if (p->stmtIvfCellUpdateN[col_idx]) { // This stmt does +1, but we want -1. Use a different cached stmt.
} // Just use inline for decrement (not hot path)
{
sqlite3_stmt *stmtDec = NULL;
char *zSql = sqlite3_mprintf( "UPDATE " VEC0_SHADOW_IVF_CELLS_NAME " SET n_vectors = n_vectors - 1 WHERE rowid = ?",
p->schemaName, p->tableName, col_idx);
if (zSql) {
sqlite3_prepare_v2(p->db, zSql, -1, &stmtDec, NULL); sqlite3_free(zSql);
if (stmtDec) { sqlite3_bind_int64(stmtDec, 1, cell_id); sqlite3_step(stmtDec); sqlite3_finalize(stmtDec); }
}
}
// Delete from rowid_map
ivf_ensure_stmt(p, &p->stmtIvfRowidMapDelete[col_idx], "DELETE FROM " VEC0_SHADOW_IVF_ROWID_MAP_NAME " WHERE rowid = ?", col_idx);
if (p->stmtIvfRowidMapDelete[col_idx]) {
sqlite3_stmt *sd = p->stmtIvfRowidMapDelete[col_idx];
sqlite3_reset(sd);
sqlite3_bind_int64(sd, 1, rowid);
sqlite3_step(sd);
}
// Delete from _ivf_vectors (full-precision KV) when quantized
if (p->vector_columns[col_idx].ivf.quantizer != VEC0_IVF_QUANTIZER_NONE) {
sqlite3_stmt *stmtDelVec = NULL;
char *zSql = sqlite3_mprintf( "DELETE FROM " VEC0_SHADOW_IVF_VECTORS_NAME " WHERE rowid = ?",
p->schemaName, p->tableName, col_idx);
if (zSql) {
sqlite3_prepare_v2(p->db, zSql, -1, &stmtDelVec, NULL); sqlite3_free(zSql);
if (stmtDelVec) { sqlite3_bind_int64(stmtDelVec, 1, rowid); sqlite3_step(stmtDelVec); sqlite3_finalize(stmtDelVec); }
}
}
return SQLITE_OK;
}
// ============================================================================ // Point query // ============================================================================
static int ivf_get_vector_data(vec0_vtab *p, i64 rowid, int col_idx, void **outVector, int *outVectorSize) {
int rc;
int vecSize = ivf_vec_size(p, col_idx);
i64 cell_id = 0;
int slot = -1;
static int ivf_load_all_vectors(vec0_vtab *p, int col_idx,
float **out_vectors, i64 **out_rowids, int *out_N) {
sqlite3_stmt *stmt = NULL;
int rc;
int D = (int)p->vector_columns[col_idx].dimensions;
int vecSize = D * (int)sizeof(float);
int quantizer = p->vector_columns[col_idx].ivf.quantizer;
// When quantized, load full-precision vectors from _ivf_vectors KV table
if (quantizer != VEC0_IVF_QUANTIZER_NONE) {
int total = 0;
char *zSql = sqlite3_mprintf( "SELECT count(*) FROM " VEC0_SHADOW_IVF_VECTORS_NAME,
p->schemaName, p->tableName, col_idx);
if (!zSql) return SQLITE_NOMEM;
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); sqlite3_free(zSql);
if (rc == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW) total = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
if (total == 0) { *out_vectors = NULL; *out_rowids = NULL; *out_N = 0; return SQLITE_OK; }
static int ivf_cmd_compute_centroids(vec0_vtab *p, int col_idx, int nlist_override,
int max_iter, uint32_t seed) {
int rc;
int D = (int)p->vector_columns[col_idx].dimensions;
int vecSize = D * (int)sizeof(float);
int quantizer = p->vector_columns[col_idx].ivf.quantizer;
int nlist = nlist_override > 0 ? nlist_override : p->vector_columns[col_idx].ivf.nlist;
if (nlist <= 0) { vtab_set_error(&p->base, "nlist must be specified"); return SQLITE_ERROR; }
float *vectors = NULL; i64 *rowids = NULL; int N = 0;
rc = ivf_load_all_vectors(p, col_idx, &vectors, &rowids, &N);
if (rc != SQLITE_OK) return rc;
if (N == 0) { vtab_set_error(&p->base, "No vectors"); sqlite3_free(vectors); sqlite3_free(rowids); return SQLITE_ERROR; }
if (nlist > N) nlist = N;
float *centroids = sqlite3_malloc64((i64)nlist * D * sizeof(float));
if (!centroids) { sqlite3_free(vectors); sqlite3_free(rowids); return SQLITE_NOMEM; }
if (ivf_kmeans(vectors, N, D, nlist, max_iter, seed, centroids) != 0) {
sqlite3_free(vectors); sqlite3_free(rowids); sqlite3_free(centroids); return SQLITE_ERROR;
}
// Compute assignments
int *assignments = sqlite3_malloc64((i64)N * sizeof(int));
if (!assignments) { sqlite3_free(vectors); sqlite3_free(rowids); sqlite3_free(centroids); return SQLITE_NOMEM; } // Assignment uses float32 distances (k-means operates in float32 space)
for (int i = 0; i < N; i++) {
float min_d = FLT_MAX;
int best = 0;
for (int c = 0; c < nlist; c++) {
float d = ivf_distance_float(p, col_idx, &vectors[i * D], ¢roids[c * D]);
if (d < min_d) { min_d = d; best = c; }
}
assignments[i] = best;
}
// Invalidate all cached stmts before dropping/recreating tables
ivf_invalidate_cached(p, col_idx);
// Clear all data
ivf_exec(p, "DELETE FROM " VEC0_SHADOW_IVF_CENTROIDS_NAME, col_idx);
ivf_exec(p, "DELETE FROM " VEC0_SHADOW_IVF_CELLS_NAME, col_idx);
ivf_exec(p, "DELETE FROM " VEC0_SHADOW_IVF_ROWID_MAP_NAME, col_idx);
// Write centroids (quantized if quantizer is set)
int qvecSize = ivf_vec_size(p, col_idx); void *qbuf = sqlite3_malloc(qvecSize > vecSize ? qvecSize : vecSize);
if (!qbuf) { rc = SQLITE_NOMEM; goto train_error; }
static int ivf_cmd_set_centroid(vec0_vtab *p, int col_idx, int centroid_id, constvoid *vectorData, int vectorSize) {
sqlite3_stmt *stmt = NULL;
int rc;
int D = (int)p->vector_columns[col_idx].dimensions;
if (vectorSize != (int)(D * sizeof(float))) { vtab_set_error(&p->base, "Dimension mismatch"); return SQLITE_ERROR; }
static int ivf_cmd_assign_vectors(vec0_vtab *p, int col_idx) {
if (!ivf_is_trained(p, col_idx)) { vtab_set_error(&p->base, "No centroids"); return SQLITE_ERROR; }
int D = (int)p->vector_columns[col_idx].dimensions;
int vecSize = D * (int)sizeof(float);
int rc;
sqlite3_stmt *stmt = NULL;
char *zSql;
// Invalidate cached stmts since we'll be modifying cells
ivf_invalidate_cached(p, col_idx);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int n = sqlite3_column_int(stmt, 1); constunsigned char *val = (constunsigned char *)sqlite3_column_blob(stmt, 2); const i64 *rids = (const i64 *)sqlite3_column_blob(stmt, 3); const float *vecs = (const float *)sqlite3_column_blob(stmt, 4);
int valBytes = sqlite3_column_bytes(stmt, 2);
int ridsBytes = sqlite3_column_bytes(stmt, 3);
int vecsBytes = sqlite3_column_bytes(stmt, 4);
if (!val || !rids || !vecs) continue;
int cap = valBytes * 8;
if (ridsBytes / (int)sizeof(i64) < cap) cap = ridsBytes / (int)sizeof(i64);
if (vecsBytes / vecSize < cap) cap = vecsBytes / vecSize;
for (int i = 0; i < cap && n > 0; i++) {
if (!(val[i / 8] & (1 << (i % 8)))) continue;
n--;
int cid = ivf_find_nearest_centroid(p, col_idx, &vecs[i * D], centroids, D, nlist);
// Delete old rowid_map entry
sqlite3_stmt *sd = NULL;
char *zd = sqlite3_mprintf("DELETE FROM " VEC0_SHADOW_IVF_ROWID_MAP_NAME " WHERE rowid = ?",
p->schemaName, p->tableName, col_idx);
if (zd) { sqlite3_prepare_v2(p->db, zd, -1, &sd, NULL); sqlite3_free(zd);
sqlite3_bind_int64(sd, 1, rids[i]); sqlite3_step(sd); sqlite3_finalize(sd); }
static int ivf_cmd_clear_centroids(vec0_vtab *p, int col_idx) {
float *vectors = NULL; i64 *rowids = NULL; int N = 0;
int vecSize = ivf_vec_size(p, col_idx);
int D = (int)p->vector_columns[col_idx].dimensions;
int rc;
sqlite3_stmt *stmt = NULL;
char *zSql;
static int ivf_candidate_cmp(constvoid *a, constvoid *b) {
float da = ((conststruct IvfCandidate *)a)->distance;
float db = ((conststruct IvfCandidate *)b)->distance;
if (da < db) return -1;
if (da > db) return1; return0;
}
/** *Scancellrowsfromapreparedstatement,computingdistancesin-memory. *Thestatementmustreturn(n_vectors,validity,rowids,vectors)columns. *queryVecQisthequantizedquery(sametypeascellvectors). *qvecSizeisthesizeofonequantizedvectorinbytes.
*/ static int ivf_scan_cells_from_stmt(vec0_vtab *p, int col_idx,
sqlite3_stmt *stmt, constvoid *queryVecQ, int qvecSize, struct IvfCandidate **candidates,
int *nCandidates, int *cap) { while (sqlite3_step(stmt) == SQLITE_ROW) {
int n = sqlite3_column_int(stmt, 0);
if (n == 0) continue; constunsigned char *validity = (constunsigned char *)sqlite3_column_blob(stmt, 1); const i64 *rowids = (const i64 *)sqlite3_column_blob(stmt, 2); constunsigned char *vectors = (constunsigned char *)sqlite3_column_blob(stmt, 3);
int valBytes = sqlite3_column_bytes(stmt, 1);
int ridsBytes = sqlite3_column_bytes(stmt, 2);
int vecsBytes = sqlite3_column_bytes(stmt, 3);
if (!validity || !rowids || !vectors) continue;
int cell_cap = valBytes * 8;
if (ridsBytes / (int)sizeof(i64) < cell_cap) cell_cap = ridsBytes / (int)sizeof(i64);
if (vecsBytes / qvecSize < cell_cap) cell_cap = vecsBytes / qvecSize;
int found = 0;
for (int i = 0; i < cell_cap && found < n; i++) {
if (!(validity[i / 8] & (1 << (i % 8)))) continue;
found++;
if (*nCandidates >= *cap) {
*cap *= 2; struct IvfCandidate *tmp = sqlite3_realloc64(*candidates, (i64)*cap * sizeof(struct IvfCandidate));
if (!tmp) return SQLITE_NOMEM;
*candidates = tmp;
}
(*candidates)[*nCandidates].rowid = rowids[i];
(*candidates)[*nCandidates].distance = ivf_distance(p, col_idx,
queryVecQ, &vectors[i * qvecSize]);
(*nCandidates)++;
}
} return SQLITE_OK;
}
static int ivf_query_knn(vec0_vtab *p, int col_idx, constvoid *queryVector, int queryVectorSize,
i64 k, struct vec0_query_knn_data *knn_data) {
UNUSED_PARAMETER(queryVectorSize);
int rc;
int nprobe = p->vector_columns[col_idx].ivf.nprobe;
int trained = ivf_is_trained(p, col_idx);
int quantizer = p->vector_columns[col_idx].ivf.quantizer;
int oversample = p->vector_columns[col_idx].ivf.oversample;
int qvecSize = ivf_vec_size(p, col_idx);
// Quantize query vector for scanning void *queryQ = sqlite3_malloc(qvecSize);
if (!queryQ) return SQLITE_NOMEM;
ivf_quantize(p, col_idx, (const float *)queryVector, queryQ);
// With oversample, collect more candidates for re-ranking
i64 collect_k = (oversample > 1) ? k * oversample : k;
int cap = (collect_k < 1024) ? 1024 : (int)collect_k * 2;
int nCandidates = 0; struct IvfCandidate *candidates = sqlite3_malloc64((i64)cap * sizeof(struct IvfCandidate));
if (!candidates) { sqlite3_free(queryQ); return SQLITE_NOMEM; }
if (trained) { // Find top nprobe centroids using quantized distance
int nlist = 0;
rc = ivf_ensure_stmt(p, &p->stmtIvfCentroidsAll[col_idx], "SELECT centroid_id, centroid FROM " VEC0_SHADOW_IVF_CENTROIDS_NAME, col_idx);
if (rc != SQLITE_OK) { sqlite3_free(queryQ); sqlite3_free(candidates); return rc; }
sqlite3_stmt *stmt = p->stmtIvfCentroidsAll[col_idx];
sqlite3_reset(stmt);
int centroid_cap = 64; struct IvfCentroidDist *cd = sqlite3_malloc64(centroid_cap * sizeof(*cd));
if (!cd) { sqlite3_free(queryQ); sqlite3_free(candidates); return SQLITE_NOMEM; }
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.