while (pVect1 < pEnd1 - 7) { // loading 8 at a time
int8x8_t v1 = vld1_s8(pVect1);
int8x8_t v2 = vld1_s8(pVect2);
pVect1 += 8;
pVect2 += 8;
// widen i8 to i16 for subtraction
int16x8_t v1_wide = vmovl_s8(v1);
int16x8_t v2_wide = vmovl_s8(v2);
int16x8_t diff = vsubq_s16(v1_wide, v2_wide);
// widening multiply: i16*i16 -> i32 to avoid i16 overflow // (diff can be up to 255, so diff*diff can be up to 65025 > INT16_MAX)
int32x4_t sq_lo = vmull_s16(vget_low_s16(diff), vget_low_s16(diff));
int32x4_t sq_hi = vmull_s16(vget_high_s16(diff), vget_high_s16(diff));
int32x4_t sum = vaddq_s32(sq_lo, sq_hi);
while (a <= pEnd - 32) {
__m256i va = _mm256_loadu_si256((const __m256i *)a);
__m256i vb = _mm256_loadu_si256((const __m256i *)b);
__m256i xored = _mm256_xor_si256(va, vb);
// VPSHUFB popcount: split into nibbles, lookup each
__m256i lo = _mm256_and_si256(xored, low_mask);
__m256i hi = _mm256_and_si256(_mm256_srli_epi16(xored, 4), low_mask);
__m256i popcnt = _mm256_add_epi8(_mm256_shuffle_epi8(lookup, lo),
_mm256_shuffle_epi8(lookup, hi));
// Horizontal sum: u8 -> u64 via sad against zero
acc = _mm256_add_epi64(acc, _mm256_sad_epu8(popcnt, _mm256_setzero_si256()));
a += 32;
b += 32;
}
// Horizontal sum of 4 x u64 lanes
u64 tmp[4];
_mm256_storeu_si256((__m256i *)tmp, acc);
u32 sum = (u32)(tmp[0] + tmp[1] + tmp[2] + tmp[3]);
// Scalar tail while (a < pEnd) {
u8 x = *a ^ *b;
x = x - ((x >> 1) & 0x55);
x = (x & 0x33) + ((x >> 2) & 0x33);
sum += (x + (x >> 4)) & 0x0F;
a++;
b++;
}
return (f32)sum;
} #endif
static f32 distance_hamming_u8(u8 *a, u8 *b, size_t n) {
int same = 0;
for (unsigned long i = 0; i < n; i++) {
same += hamdist_table[a[i] ^ b[i]];
} return (f32)same;
}
#ifdef _MSC_VER #if !defined(__clang__) && (defined(_M_ARM) || defined(_M_ARM64)) // From // https://github.com/ngtcp2/ngtcp2/blob/b64f1e77b5e0d880b93d31f474147fae4a1d17cc/lib/ngtcp2_ringbuf.c, // line 34-43 staticunsigned int __builtin_popcountl(unsigned int x) { unsigned int c = 0;
for (; x; ++c) {
x &= x - 1;
} return c;
} #else #include <intrin.h> #define __builtin_popcountl __popcnt64 #endif #endif
static f32 distance_hamming_u64(const u8 *a, const u8 *b, size_t n) {
int same = 0;
for (unsigned long i = 0; i < n; i++) {
u64 va, vb;
memcpy(&va, a + i * sizeof(u64), sizeof(u64));
memcpy(&vb, b + i * sizeof(u64), sizeof(u64));
same += __builtin_popcountl(va ^ vb);
} return (f32)same;
}
static int fvec_from_value(sqlite3_value *value, f32 **vector,
size_t *dimensions, fvec_cleanup *cleanup,
char **pzErr) {
int value_type = sqlite3_value_type(value);
if (value_type == SQLITE_BLOB) { constvoid *blob = sqlite3_value_blob(value);
int bytes = sqlite3_value_bytes(value);
if (bytes == 0) {
*pzErr = sqlite3_mprintf("zero-length vectors are not supported."); return SQLITE_ERROR;
}
if ((bytes % sizeof(f32)) != 0) {
*pzErr = sqlite3_mprintf("invalid float32 vector BLOB length. Must be " "divisible by %d, found %d", sizeof(f32), bytes); return SQLITE_ERROR;
}
f32 *buf = sqlite3_malloc(bytes);
if (!buf) {
*pzErr = sqlite3_mprintf("out of memory"); return SQLITE_NOMEM;
}
memcpy(buf, blob, bytes);
size_t n = bytes / sizeof(f32);
for (size_t i = 0; i < n; i++) {
if (isnan(buf[i]) || isinf(buf[i])) {
*pzErr = sqlite3_mprintf( "invalid float32 vector: element %d is %s",
(int)i, isnan(buf[i]) ? "NaN" : "Inf");
sqlite3_free(buf); return SQLITE_ERROR;
}
}
*vector = buf;
*dimensions = n;
*cleanup = sqlite3_free; return SQLITE_OK;
}
if (value_type == SQLITE_TEXT) { const char *source = (const char *)sqlite3_value_text(value);
int source_len = sqlite3_value_bytes(value);
if (source_len == 0) {
*pzErr = sqlite3_mprintf("zero-length vectors are not supported."); return SQLITE_ERROR;
}
int i = 0;
struct Array x;
int rc = array_init(&x, sizeof(f32), ceil(source_len / 2.0));
if (rc != SQLITE_OK) { return rc;
}
// advance leading whitespace to first '[' while (i < source_len) {
if (vecJsonIsspace(source[i])) {
i++; continue;
}
if (source[i] == '[') { break;
}
*pzErr = sqlite3_mprintf( "JSON array parsing error: Input does not start with '['");
array_cleanup(&x); return SQLITE_ERROR;
}
if (source[i] != '[') {
*pzErr = sqlite3_mprintf( "JSON array parsing error: Input does not start with '['");
array_cleanup(&x); return SQLITE_ERROR;
}
int offset = i + 1;
f32 res = (f32)result;
if (isnan(res) || isinf(res)) {
sqlite3_free(x.z);
*pzErr = sqlite3_mprintf( "invalid float32 vector: element %d is %s",
(int)x.length, isnan(res) ? "NaN" : "Inf"); return SQLITE_ERROR;
}
array_append(&x, (constvoid *)&res);
offset += (endptr - ptr); while (offset < source_len) {
if (vecJsonIsspace(source[offset])) {
offset++; continue;
}
if (source[offset] == ',') {
offset++; continue;
}
if (source[offset] == ']')
goto done; break;
}
}
done:
if (x.length > 0) {
*vector = (f32 *)x.z;
*dimensions = x.length;
*cleanup = sqlite3_free; return SQLITE_OK;
}
sqlite3_free(x.z);
*pzErr = sqlite3_mprintf("zero-length vectors are not supported."); return SQLITE_ERROR;
}
*pzErr = sqlite3_mprintf( "Input must have type BLOB (compact format) or TEXT (JSON), found %s",
type_name(value_type)); return SQLITE_ERROR;
}
static int bitvec_from_value(sqlite3_value *value, u8 **vector,
size_t *dimensions, vector_cleanup *cleanup,
char **pzErr) {
int value_type = sqlite3_value_type(value);
if (value_type == SQLITE_BLOB) { constvoid *blob = sqlite3_value_blob(value);
int bytes = sqlite3_value_bytes(value);
if (bytes == 0) {
*pzErr = sqlite3_mprintf("zero-length vectors are not supported."); return SQLITE_ERROR;
}
*vector = (u8 *)blob;
*dimensions = bytes * CHAR_BIT;
*cleanup = vector_cleanup_noop; return SQLITE_OK;
}
*pzErr = sqlite3_mprintf("Unknown type for bitvector."); return SQLITE_ERROR;
}
static int int8_vec_from_value(sqlite3_value *value, i8 **vector,
size_t *dimensions, vector_cleanup *cleanup,
char **pzErr) {
int value_type = sqlite3_value_type(value);
if (value_type == SQLITE_BLOB) { constvoid *blob = sqlite3_value_blob(value);
int bytes = sqlite3_value_bytes(value);
if (bytes == 0) {
*pzErr = sqlite3_mprintf("zero-length vectors are not supported."); return SQLITE_ERROR;
}
*vector = (i8 *)blob;
*dimensions = bytes;
*cleanup = vector_cleanup_noop; return SQLITE_OK;
}
if (value_type == SQLITE_TEXT) { const char *source = (const char *)sqlite3_value_text(value);
int source_len = sqlite3_value_bytes(value);
int i = 0;
if (source_len == 0) {
*pzErr = sqlite3_mprintf("zero-length vectors are not supported."); return SQLITE_ERROR;
}
struct Array x;
int rc = array_init(&x, sizeof(i8), ceil(source_len / 2.0));
if (rc != SQLITE_OK) { return rc;
}
// advance leading whitespace to first '[' while (i < source_len) {
if (vecJsonIsspace(source[i])) {
i++; continue;
}
if (source[i] == '[') { break;
}
*pzErr = sqlite3_mprintf( "JSON array parsing error: Input does not start with '['");
array_cleanup(&x); return SQLITE_ERROR;
}
if (source[i] != '[') {
*pzErr = sqlite3_mprintf( "JSON array parsing error: Input does not start with '['");
array_cleanup(&x); return SQLITE_ERROR;
}
int offset = i + 1;
if (result < INT8_MIN || result > INT8_MAX) {
sqlite3_free(x.z);
*pzErr =
sqlite3_mprintf("JSON parsing error: value out of range for int8"); return SQLITE_ERROR;
}
i8 res = (i8)result;
array_append(&x, (constvoid *)&res);
offset += (endptr - ptr); while (offset < source_len) {
if (vecJsonIsspace(source[offset])) {
offset++; continue;
}
if (source[offset] == ',') {
offset++; continue;
}
if (source[offset] == ']')
goto done; break;
}
}
done:
if (x.length > 0) {
*vector = (i8 *)x.z;
*dimensions = x.length;
*cleanup = (vector_cleanup)sqlite3_free; return SQLITE_OK;
}
sqlite3_free(x.z);
*pzErr = sqlite3_mprintf("zero-length vectors are not supported."); return SQLITE_ERROR;
}
*pzErr = sqlite3_mprintf("Unknown type for int8 vector."); return SQLITE_ERROR;
}
if (aType != bType) {
*outError =
sqlite3_mprintf("Vector type mistmatch. First vector has type %s, " "while the second has type %s.",
vector_subtype_name(aType), vector_subtype_name(bType));
aCleanup(*a);
bCleanup(*b); return SQLITE_ERROR;
}
if (aDims != bDims) {
*outError = sqlite3_mprintf( "Vector dimension mistmatch. First vector has %ld dimensions, " "while the second has %ld dimensions.",
aDims, bDims);
aCleanup(*a);
bCleanup(*b); return SQLITE_ERROR;
}
*element_type = aType;
*dimensions = aDims;
*outACleanup = aCleanup;
*outBCleanup = bCleanup; return SQLITE_OK;
}
int rc = vector_from_value(argv[0], &vector, &dimensions, &elementType,
&cleanup, &err);
if (rc != SQLITE_OK) {
sqlite3_result_error(context, err, -1);
sqlite3_free(err); return;
}
int start = sqlite3_value_int(argv[1]);
int end = sqlite3_value_int(argv[2]);
if (start < 0) {
sqlite3_result_error(context, "slice 'start' index must be a postive number.", -1);
goto done;
}
if (end < 0) {
sqlite3_result_error(context, "slice 'end' index must be a postive number.",
-1);
goto done;
}
if (((size_t)start) > dimensions) {
sqlite3_result_error(
context, "slice 'start' index is greater than the number of dimensions",
-1);
goto done;
}
if (((size_t)end) > dimensions) {
sqlite3_result_error(
context, "slice 'end' index is greater than the number of dimensions",
-1);
goto done;
}
if (start > end) {
sqlite3_result_error(context, "slice 'start' index is greater than 'end' index", -1);
goto done;
}
if (start == end) {
sqlite3_result_error(context, "slice 'start' index is equal to the 'end' index, " "vectors must have non-zero length",
-1);
goto done;
}
size_t n = end - start;
switch (elementType) { case SQLITE_VEC_ELEMENT_TYPE_FLOAT32: {
int outSize = n * sizeof(f32);
f32 *out = sqlite3_malloc(outSize);
if (!out) {
sqlite3_result_error_nomem(context);
goto done;
}
memset(out, 0, outSize);
for (size_t i = 0; i < n; i++) {
out[i] = ((f32 *)vector)[start + i];
}
sqlite3_result_blob(context, out, outSize, sqlite3_free);
sqlite3_result_subtype(context, SQLITE_VEC_ELEMENT_TYPE_FLOAT32);
goto done;
} case SQLITE_VEC_ELEMENT_TYPE_INT8: {
int outSize = n * sizeof(i8);
i8 *out = sqlite3_malloc(outSize);
if (!out) {
sqlite3_result_error_nomem(context); return;
}
memset(out, 0, outSize);
for (size_t i = 0; i < n; i++) {
out[i] = ((i8 *)vector)[start + i];
}
sqlite3_result_blob(context, out, outSize, sqlite3_free);
sqlite3_result_subtype(context, SQLITE_VEC_ELEMENT_TYPE_INT8);
goto done;
} case SQLITE_VEC_ELEMENT_TYPE_BIT: {
if ((start % CHAR_BIT) != 0) {
sqlite3_result_error(context, "start index must be divisible by 8.", -1);
goto done;
}
if ((end % CHAR_BIT) != 0) {
sqlite3_result_error(context, "end index must be divisible by 8.", -1);
goto done;
}
int outSize = n / CHAR_BIT;
u8 *out = sqlite3_malloc(outSize);
if (!out) {
sqlite3_result_error_nomem(context); return;
}
memset(out, 0, outSize);
for (size_t i = 0; i < n / CHAR_BIT; i++) {
out[i] = ((u8 *)vector)[(start / CHAR_BIT) + i];
}
sqlite3_result_blob(context, out, outSize, sqlite3_free);
sqlite3_result_subtype(context, SQLITE_VEC_ELEMENT_TYPE_BIT);
goto done;
}
}
done:
cleanup(vector);
}
int vec0_parse_table_option(const char *source, int source_length,
char **out_key, int *out_key_length,
char **out_value, int *out_value_length) {
int rc; struct Vec0Scanner scanner; struct Vec0Token token;
char *key;
char *value;
int keyLength, valueLength;
rc = vec0_scanner_next(&scanner, &token);
if (rc == VEC0_TOKEN_RESULT_EOF) {
*out_key = key;
*out_key_length = keyLength;
*out_value = value;
*out_value_length = valueLength; return SQLITE_OK;
} return SQLITE_ERROR;
} /** *@briefParseanargv[i]entryofavec0virtualtabledefinition,andseeif *it'saPARTITIONKEYdefinition. * *@paramsource:argv[i]sourcestring *@paramsource_length:lengthofthesourcestring *@paramout_column_name:Ifitisapartitionkey,theoutputcolumnname.Samelifetime *assource,pointstospecificchar* *@paramout_column_name_length:Lengthofout_column_nameinbytes *@paramout_column_type:SQLITE_TEXTorSQLITE_INTEGER. *@returnint:SQLITE_EMPTYifnotaPK,SQLITE_OKifitis.
*/
int vec0_parse_partition_key_definition(const char *source, int source_length,
char **out_column_name,
int *out_column_name_length,
int *out_column_type) { struct Vec0Scanner scanner; struct Vec0Token token;
char *column_name;
int column_name_length;
int column_type;
vec0_scanner_init(&scanner, source, source_length);
// Check first token is identifier, will be the column name
int rc = vec0_scanner_next(&scanner, &token);
if (rc != VEC0_TOKEN_RESULT_SOME &&
token.token_type != TOKEN_TYPE_IDENTIFIER) { return SQLITE_EMPTY;
}
/** *@briefParseanargv[i]entryofavec0virtualtabledefinition,andseeif *it'sanauxiliarcolumndefinition,ie`+[name][type]`like`+contentstext` * *@paramsource:argv[i]sourcestring *@paramsource_length:lengthofthesourcestring *@paramout_column_name:Ifitisapartitionkey,theoutputcolumnname.Samelifetime *assource,pointstospecificchar* *@paramout_column_name_length:Lengthofout_column_nameinbytes *@paramout_column_type:SQLITE_TEXT,SQLITE_INTEGER,SQLITE_FLOAT,orSQLITE_BLOB. *@returnint:SQLITE_EMPTYifnotanauxcolumn,SQLITE_OKifitis.
*/
int vec0_parse_auxiliary_column_definition(const char *source, int source_length,
char **out_column_name,
int *out_column_name_length,
int *out_column_type) { struct Vec0Scanner scanner; struct Vec0Token token;
char *column_name;
int column_name_length;
int column_type;
vec0_scanner_init(&scanner, source, source_length);
// Check first token is '+', which denotes aux columns
int rc = vec0_scanner_next(&scanner, &token);
if (rc != VEC0_TOKEN_RESULT_SOME ||
token.token_type != TOKEN_TYPE_PLUS) { return SQLITE_EMPTY;
}
/** *@briefParseanargv[i]entryofavec0virtualtabledefinition,andseeif *it'saPRIMARYKEYdefinition. * *@paramsource:argv[i]sourcestring *@paramsource_length:lengthofthesourcestring *@paramout_column_name:IfitisaPK,theoutputcolumnname.Samelifetime *assource,pointstospecificchar* *@paramout_column_name_length:Lengthofout_column_nameinbytes *@paramout_column_type:SQLITE_TEXTorSQLITE_INTEGER. *@returnint:SQLITE_EMPTYifnotaPK,SQLITE_OKifitis.
*/
int vec0_parse_primary_key_definition(const char *source, int source_length,
char **out_column_name,
int *out_column_name_length,
int *out_column_type) { struct Vec0Scanner scanner; struct Vec0Token token;
char *column_name;
int column_name_length;
int column_type;
vec0_scanner_init(&scanner, source, source_length);
// Check first token is identifier, will be the column name
int rc = vec0_scanner_next(&scanner, &token);
if (rc != VEC0_TOKEN_RESULT_SOME &&
token.token_type != TOKEN_TYPE_IDENTIFIER) { return SQLITE_EMPTY;
}
struct Vec0IvfConfig {
int nlist; // number of centroids (0 = deferred)
int nprobe; // cells to probe at query time
int quantizer; // VEC0_IVF_QUANTIZER_NONE / INT8 / BINARY
int oversample; // >= 1 (1 = no oversampling)
}; #else struct Vec0IvfConfig { char _unused; }; #endif
// ============================================================ // DiskANN types and constants // ============================================================
/** *QuantizertypeusedforcompressingneighborvectorsintheDiskANNgraph.
*/
enum Vec0DiskannQuantizerType {
VEC0_DISKANN_QUANTIZER_BINARY = 1, // 1 bit per dimension (1/32 compression)
VEC0_DISKANN_QUANTIZER_INT8 = 2, // 1 byte per dimension (1/4 compression)
};
/** *ConfigurationforaDiskANNindexonasinglevectorcolumn. *Parsedfrom`INDEXEDBYdiskann(neighbor_quantizer=binary,n_neighbors=72)`.
*/ struct Vec0DiskannConfig { // Quantizer type for neighbor vectors
enum Vec0DiskannQuantizerType quantizer_type;
// Maximum number of neighbors per node (R in the paper). Must be divisible by 8.
int n_neighbors;
// Search list size (L in the paper) — unified default for both insert and query.
int search_list_size;
// Per-path overrides (0 = fall back to search_list_size).
int search_list_size_search;
int search_list_size_insert;
// Alpha parameter for RobustPrune (distance scaling factor, typically 1.0-1.5)
f32 alpha;
// Buffer threshold for batched inserts. When > 0, inserts go into a flat // buffer table and are flushed into the graph when the buffer reaches this // size. 0 = disabled (legacy per-row insert behavior).
int buffer_threshold;
};
/** *Representsasinglecandidateduringgreedybeamsearch. *Usedinpriorityqueues/sortedarraysduringLM-Search.
*/ struct Vec0DiskannCandidate {
i64 rowid;
f32 distance;
int visited; // 1 if this candidate's neighbors have been explored
int confirmed; // 1 if full-precision vector was successfully read (node exists)
};
/** *Returnsthebytesizeofaquantizedvectorforthegivenquantizertype *andnumberofdimensions.
*/
size_t diskann_quantized_vector_byte_size(
enum Vec0DiskannQuantizerType quantizer_type, size_t dimensions) { switch (quantizer_type) { case VEC0_DISKANN_QUANTIZER_BINARY: return dimensions / CHAR_BIT; // 1 bit per dimension case VEC0_DISKANN_QUANTIZER_INT8: return dimensions * sizeof(i8); // 1 byte per dimension
} return0;
}
while (1) {
rc = vec0_scanner_next(scanner, &token);
if (rc == VEC0_TOKEN_RESULT_EOF) { break;
} // ')' closes rescore options
if (rc == VEC0_TOKEN_RESULT_SOME && token.token_type == TOKEN_TYPE_RPAREN) { break;
}
if (rc != VEC0_TOKEN_RESULT_SOME || token.token_type != TOKEN_TYPE_IDENTIFIER) {
*pzErr = sqlite3_mprintf("Expected option name in rescore(...)"); return SQLITE_ERROR;
}
char *key = token.start;
int keyLength = token.end - token.start;
// expect '='
rc = vec0_scanner_next(scanner, &token);
if (rc != VEC0_TOKEN_RESULT_SOME || token.token_type != TOKEN_TYPE_EQ) {
*pzErr = sqlite3_mprintf("Expected '=' after option name in rescore(...)"); return SQLITE_ERROR;
}
// value
rc = vec0_scanner_next(scanner, &token);
if (rc != VEC0_TOKEN_RESULT_SOME) {
*pzErr = sqlite3_mprintf("Expected value after '=' in rescore(...)"); return SQLITE_ERROR;
}
if (sqlite3_strnicmp(key, "quantizer", keyLength) == 0) {
if (token.token_type != TOKEN_TYPE_IDENTIFIER) {
*pzErr = sqlite3_mprintf("Expected identifier for quantizer value in rescore(...)"); return SQLITE_ERROR;
}
int valLen = token.end - token.start;
if (sqlite3_strnicmp(token.start, "bit", valLen) == 0) {
outConfig->quantizer_type = VEC0_RESCORE_QUANTIZER_BIT;
} else if (sqlite3_strnicmp(token.start, "int8", valLen) == 0) {
outConfig->quantizer_type = VEC0_RESCORE_QUANTIZER_INT8;
} else {
*pzErr = sqlite3_mprintf("Unknown quantizer type '%.*s' in rescore(...). Expected 'bit' or 'int8'.", valLen, token.start); return SQLITE_ERROR;
}
hasQuantizer = 1;
} else if (sqlite3_strnicmp(key, "oversample", keyLength) == 0) {
if (token.token_type != TOKEN_TYPE_DIGIT) {
*pzErr = sqlite3_mprintf("Expected integer for oversample value in rescore(...)"); return SQLITE_ERROR;
}
outConfig->oversample = atoi(token.start);
if (outConfig->oversample <= 0 || outConfig->oversample > 128) {
*pzErr = sqlite3_mprintf("oversample in rescore(...) must be between 1 and 128, got %d", outConfig->oversample); return SQLITE_ERROR;
}
} else {
*pzErr = sqlite3_mprintf("Unknown option '%.*s' in rescore(...)", keyLength, key); return SQLITE_ERROR;
}
// optional comma between options
rc = vec0_scanner_next(scanner, &token);
if (rc == VEC0_TOKEN_RESULT_EOF) { break;
}
if (rc == VEC0_TOKEN_RESULT_SOME && token.token_type == TOKEN_TYPE_RPAREN) { break;
}
if (rc == VEC0_TOKEN_RESULT_SOME && token.token_type == TOKEN_TYPE_COMMA) { continue;
} // If it's not a comma or rparen, it might be the next key — push back isn't // possible with this scanner, so we'll treat unexpected tokens as errors
*pzErr = sqlite3_mprintf("Unexpected token in rescore(...) options"); return SQLITE_ERROR;
}
if (!hasQuantizer) {
*pzErr = sqlite3_mprintf("rescore(...) requires a 'quantizer' option (quantizer=bit or quantizer=int8)"); return SQLITE_ERROR;
}
// any other tokens left should be column-level options , ex `key=value` // ex `distance_metric=L2 distance_metric=cosine` should error while (1) { // should be EOF or identifier (option key)
rc = vec0_scanner_next(&scanner, &token);
if (rc == VEC0_TOKEN_RESULT_EOF) { break;
}
// vec0 tables with a text primary keys are still backed by int64 primary keys, // since a fixed-length rowid is required for vec0 chunks. But we add a new 'id // text unique' column to emulate a text primary key interface. #define VEC0_SHADOW_ROWIDS_CREATE_PK_TEXT \ "CREATE TABLE " VEC0_SHADOW_ROWIDS_NAME "(" \ "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \ "id TEXT UNIQUE NOT NULL," \ "chunk_id INTEGER," \ "chunk_offset INTEGER" \ ");"
/// 1) schema, 2) original vtab table name #define VEC0_SHADOW_VECTOR_N_NAME "\"%w\".\"%w_vector_chunks%02d\""
/// 1) schema, 2) original vtab table name // // IMPORTANT: "rowid" is declared as PRIMARY KEY but WITHOUT the INTEGER type. // This means it is NOT a true SQLite rowid alias — the user-defined "rowid" // column and the internal SQLite rowid (_rowid_) are two separate values. // When inserting, both must be set explicitly to keep them in sync. See the // _rowid_ bindings in vec0_new_chunk() and the explanation in // SHADOW_TABLE_ROWID_QUIRK below. #define VEC0_SHADOW_VECTOR_N_CREATE \ "CREATE TABLE " VEC0_SHADOW_VECTOR_N_NAME "(" \ "rowid PRIMARY KEY," \ "vectors BLOB NOT NULL" \ ");"
// metadata column that can be filtered, ie "genre text"
SQLITE_VEC0_USER_COLUMN_KIND_METADATA = 4,
} vec0_user_column_kind;
struct vec0_vtab {
sqlite3_vtab base;
// the SQLite connection of the host database
sqlite3 *db;
// True if the primary key of the vec0 table has a column type TEXT. // Will change the schema of the _rowids table, and insert/query logic.
int pkIsText;
// True if the hidden command column (named after the table) exists. // Tables created before v0.1.10 or without _info table don't have it.
int hasCommandColumn;
// number of defined vector columns.
int numVectorColumns;
// number of defined PARTITION KEY columns.
int numPartitionColumns;
// number of defined auxiliary columns
int numAuxiliaryColumns;
// number of defined metadata columns
int numMetadataColumns;
// Name of the schema the table exists on. // Must be freed with sqlite3_free()
char *schemaName;
// Name of the table the table exists on. // Must be freed with sqlite3_free()
char *tableName;
// Name of the _rowids shadow table. // Must be freed with sqlite3_free()
char *shadowRowidsName;
// Name of the _chunks shadow table. // Must be freed with sqlite3_free()
char *shadowChunksName;
// contains enum vec0_user_column_kind values for up to // numVectorColumns + numPartitionColumns entries
vec0_user_column_kind user_column_kinds[VEC0_MAX_VECTOR_COLUMNS + VEC0_MAX_PARTITION_COLUMNS + VEC0_MAX_AUXILIARY_COLUMNS + VEC0_MAX_METADATA_COLUMNS];
// Name of all the vector chunk shadow tables. // Ex '_vector_chunks00' // Only the first numVectorColumns entries will be available. // The first numVectorColumns entries must be freed with sqlite3_free()
char *shadowVectorChunksNames[VEC0_MAX_VECTOR_COLUMNS];
#if SQLITE_VEC_ENABLE_RESCORE // Name of all rescore chunk shadow tables, ie `_rescore_chunks00` // Only populated for vector columns with rescore enabled. // Must be freed with sqlite3_free()
char *shadowRescoreChunksNames[VEC0_MAX_VECTOR_COLUMNS];
// Name of all rescore vector shadow tables, ie `_rescore_vectors00` // Rowid-keyed table for fast random-access float vector reads during rescore. // Only populated for vector columns with rescore enabled. // Must be freed with sqlite3_free()
char *shadowRescoreVectorsNames[VEC0_MAX_VECTOR_COLUMNS]; #endif
// Name of all metadata chunk shadow tables, ie `_metadatachunks00` // Only the first numMetadataColumns entries will be available. // The first numMetadataColumns entries must be freed with sqlite3_free()
char *shadowMetadataChunksNames[VEC0_MAX_METADATA_COLUMNS];
#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE // IVF cached state per vector column
char *shadowIvfCellsNames[VEC0_MAX_VECTOR_COLUMNS]; // table name for blob_open
int ivfTrainedCache[VEC0_MAX_VECTOR_COLUMNS]; // -1=unknown, 0=no, 1=yes
sqlite3_stmt *stmtIvfCellMeta[VEC0_MAX_VECTOR_COLUMNS]; // SELECT n_vectors, length(validity)*8 FROM cells WHERE cell_id=?
sqlite3_stmt *stmtIvfCellUpdateN[VEC0_MAX_VECTOR_COLUMNS]; // UPDATE cells SET n_vectors=n_vectors+? WHERE cell_id=?
sqlite3_stmt *stmtIvfRowidMapInsert[VEC0_MAX_VECTOR_COLUMNS]; // INSERT INTO rowid_map(rowid,cell_id,slot) VALUES(?,?,?)
sqlite3_stmt *stmtIvfRowidMapLookup[VEC0_MAX_VECTOR_COLUMNS]; // SELECT cell_id,slot FROM rowid_map WHERE rowid=?
sqlite3_stmt *stmtIvfRowidMapDelete[VEC0_MAX_VECTOR_COLUMNS]; // DELETE FROM rowid_map WHERE rowid=?
sqlite3_stmt *stmtIvfCentroidsAll[VEC0_MAX_VECTOR_COLUMNS]; // SELECT centroid_id,centroid FROM centroids #endif
// select latest chunk from _chunks, getting chunk_id
sqlite3_stmt *stmtLatestChunk;
// Prepared statements for DiskANN operations (per vector column) // These will be lazily prepared on first use.
sqlite3_stmt *stmtDiskannNodeRead[VEC0_MAX_VECTOR_COLUMNS];
sqlite3_stmt *stmtDiskannNodeWrite[VEC0_MAX_VECTOR_COLUMNS];
sqlite3_stmt *stmtDiskannNodeInsert[VEC0_MAX_VECTOR_COLUMNS];
sqlite3_stmt *stmtVectorsRead[VEC0_MAX_VECTOR_COLUMNS];
sqlite3_stmt *stmtVectorsInsert[VEC0_MAX_VECTOR_COLUMNS]; #endif
};
#if SQLITE_VEC_ENABLE_RESCORE // Forward declarations for rescore functions (defined in sqlite-vec-rescore.c, // included later after all helpers they depend on are defined). static int rescore_create_tables(vec0_vtab *p, sqlite3 *db, char **pzErr); static int rescore_drop_tables(vec0_vtab *p); static int rescore_new_chunk(vec0_vtab *p, i64 chunk_rowid); static int rescore_on_insert(vec0_vtab *p, i64 chunk_rowid, i64 chunk_offset,
i64 rowid, void *vectorDatas[]); static int rescore_on_delete(vec0_vtab *p, i64 chunk_id, u64 chunk_offset, i64 rowid); static int rescore_delete_chunk(vec0_vtab *p, i64 chunk_id); #endif
/** *@briefReturnstheindexofthedistancehiddencolumnforthegivenvec0 *table. * *@parampvec0table *@returnint
*/
int vec0_column_command_idx(vec0_vtab *p) { // Command column is the first hidden column (right after user columns) return VEC0_COLUMN_USERN_START + vec0_num_defined_user_columns(p);
}
int vec0_column_distance_idx(vec0_vtab *p) {
int base = VEC0_COLUMN_USERN_START + vec0_num_defined_user_columns(p); return base + (p->hasCommandColumn ? 1 : 0);
}
int vec0_column_k_idx(vec0_vtab *p) {
int base = VEC0_COLUMN_USERN_START + vec0_num_defined_user_columns(p); return base + (p->hasCommandColumn ? 2 : 1);
}
int vec0_rowid_from_id(vec0_vtab *p, sqlite3_value *valueId, i64 *rowid) {
sqlite3_stmt *stmt = NULL;
int rc;
char *zSql;
zSql = sqlite3_mprintf("SELECT rowid" " FROM " VEC0_SHADOW_ROWIDS_NAME " WHERE id = ?",
p->schemaName, p->tableName);
if (!zSql) {
rc = SQLITE_NOMEM;
goto cleanup;
}
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
sqlite3_free(zSql);
if (rc != SQLITE_OK) {
goto cleanup;
}
sqlite3_bind_value(stmt, 1, valueId);
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
rc = SQLITE_EMPTY;
goto cleanup;
}
if (rc != SQLITE_ROW) {
goto cleanup;
}
*rowid = sqlite3_column_int64(stmt, 0);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
goto cleanup;
}
rc = SQLITE_OK;
cleanup:
sqlite3_finalize(stmt); return rc;
}
int vec0_result_id(vec0_vtab *p, sqlite3_context *context, i64 rowid) {
if (!p->pkIsText) {
sqlite3_result_int64(context, rowid); return SQLITE_OK;
}
sqlite3_value *valueId;
int rc = vec0_get_id_value_from_rowid(p, rowid, &valueId);
if (rc != SQLITE_OK) { return rc;
}
if (!valueId) {
sqlite3_result_error_nomem(context);
} else {
sqlite3_result_value(context, valueId);
sqlite3_value_free(valueId);
} return SQLITE_OK;
}
/** *@brief * *@parampVtab:virtualtabletoquery *@paramrowid:rowtolookup *@paramvector_column_idx:whichvectorcolumntoquery *@paramoutVector:Outputpointertothevectorbuffer. *Mustbesqlite3_free()'ed. *@paramoutVectorSize:PointertoaintwherethesizeofoutVector *willbestored. *@returnintSQLITE_OKonsuccess.
*/ #if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE // Forward declaration — defined in sqlite-vec-ivf.c (included later) static int ivf_get_vector_data(vec0_vtab *p, i64 rowid, int col_idx, void **outVector, int *outVectorSize); #endif
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;
#if SQLITE_VEC_ENABLE_DISKANN // DiskANN fast path: read from _vectors table
if (p->vector_columns[vector_column_idx].index_type == VEC0_INDEX_TYPE_DISKANN) { void *vec = NULL;
int vecSize;
rc = diskann_vector_read(p, vector_column_idx, rowid, &vec, &vecSize);
if (rc != SQLITE_OK) {
vtab_set_error(&pVtab->base, "Could not fetch vector data for %lld from DiskANN vectors table",
rowid); return SQLITE_ERROR;
}
*outVector = vec;
if (outVectorSize) *outVectorSize = vecSize; return SQLITE_OK;
} #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) { return ivf_get_vector_data(p, rowid, vector_column_idx, outVector, outVectorSize);
} #endif
size_t size; 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)
if (p->vector_columns[vector_column_idx].index_type == VEC0_INDEX_TYPE_RESCORE) {
size = vector_column_byte_size(p->vector_columns[vector_column_idx]);
rc = sqlite3_blob_open(p->db, p->schemaName,
p->shadowRescoreVectorsNames[vector_column_idx], "vector", rowid, 0, &vectorBlob);
if (rc != SQLITE_OK) {
vtab_set_error(&pVtab->base, "Could not fetch vector data for %lld from rescore vectors",
rowid);
rc = SQLITE_ERROR;
goto cleanup;
}
buf = sqlite3_malloc(size);
if (!buf) {
rc = SQLITE_NOMEM;
goto cleanup;
}
rc = sqlite3_blob_read(vectorBlob, buf, size, 0);
if (rc != SQLITE_OK) {
sqlite3_free(buf);
buf = NULL;
rc = SQLITE_ERROR;
goto cleanup;
}
*outVector = buf;
if (outVectorSize) {
*outVectorSize = size;
}
rc = SQLITE_OK;
goto cleanup;
} #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;
}
int vec0_get_latest_chunk_rowid(vec0_vtab *p, i64 *chunk_rowid, sqlite3_value ** partitionKeyValues) {
int rc; const char *zSql; // lazy initialize stmtLatestChunk when needed. May be cleared during xSync()
if (!p->stmtLatestChunk) {
if(p->numPartitionColumns > 0) {
sqlite3_str * s = sqlite3_str_new(NULL);
sqlite3_str_appendf(s, "SELECT max(rowid) FROM " VEC0_SHADOW_CHUNKS_NAME " WHERE ",
p->schemaName, p->tableName);
for(int i = 0; i < p->numPartitionColumns; i++) {
sqlite3_bind_value(stmt, 4 + i, partitionKeyValues[i]);
}
rc = sqlite3_step(stmt);
int failed = rc != SQLITE_DONE;
rowid = sqlite3_last_insert_rowid(p->db); #if SQLITE_THREADSAFE
if (sqlite3_mutex_leave) {
sqlite3_mutex_leave(sqlite3_db_mutex(p->db));
} #endif
sqlite3_finalize(stmt);
if (failed) { return SQLITE_ERROR;
}
// Step 2: Create new vector chunks for each vector column, with // that new chunk_rowid. // // SHADOW_TABLE_ROWID_QUIRK: The _vector_chunksNN and _metadatachunksNN // shadow tables declare "rowid PRIMARY KEY" without the INTEGER type, so // the user-defined "rowid" column is NOT an alias for the internal SQLite // rowid (_rowid_). When only appending rows these two happen to stay in // sync, but after a chunk is deleted (vec0Update_Delete_DeleteChunkIfEmpty) // and a new one is created, the auto-assigned _rowid_ can diverge from the // user "rowid" value. Since sqlite3_blob_open() addresses rows by internal // _rowid_, we must explicitly set BOTH _rowid_ and "rowid" to the same // value so that later blob operations can find the row. // // The correct long-term fix is changing the schema to // "rowid INTEGER PRIMARY KEY" // which makes it a true alias, but that would break existing databases.
for (int i = 0; i < vec0_num_defined_user_columns(p); i++) {
if(p->user_column_kinds[i] != SQLITE_VEC0_USER_COLUMN_KIND_VECTOR) { continue;
}
int vector_column_idx = p->user_column_idxs[i];
// Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks
if (p->vector_columns[vector_column_idx].index_type != VEC0_INDEX_TYPE_FLAT) { continue;
}
if (fullscan_data->rowids_stmt) {
sqlite3_finalize(fullscan_data->rowids_stmt);
fullscan_data->rowids_stmt = NULL;
}
}
struct vec0_query_knn_data {
i64 k;
i64 k_used; // Array of rowids of size k. Must be freed with sqlite3_free().
i64 *rowids; // Array of distances of size k. Must be freed with sqlite3_free().
f32 *distances;
i64 current_idx;
}; void vec0_query_knn_data_clear(struct vec0_query_knn_data *knn_data) {
if (!knn_data) return;
if (knn_data->rowids) {
sqlite3_free(knn_data->rowids);
knn_data->rowids = NULL;
}
if (knn_data->distances) {
sqlite3_free(knn_data->distances);
knn_data->distances = NULL;
}
}
struct vec0_query_point_data {
i64 rowid; void *vectors[VEC0_MAX_VECTOR_COLUMNS];
int done;
}; void vec0_query_point_data_clear(struct vec0_query_point_data *point_data) {
if (!point_data) return;
for (int i = 0; i < VEC0_MAX_VECTOR_COLUMNS; i++) {
sqlite3_free(point_data->vectors[i]);
point_data->vectors[i] = NULL;
}
}
typedef enum { // If any values are updated, please update the ARCHITECTURE.md docs accordingly!
// IVF index implementation — #include'd here after all struct/helper definitions #if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE #include"sqlite-vec-ivf-kmeans.c" #include"sqlite-vec-ivf.c" #endif
// Declared chunk_size=N for entire table. // -1 to use the defualt, otherwise will get re-assigned on `chunk_size=N` // option
int chunk_size = -1;
int numVectorColumns = 0;
int numPartitionColumns = 0;
int numAuxiliaryColumns = 0;
int numMetadataColumns = 0;
int user_column_idx = 0;
// track if a "primary key" column is defined
char *pkColumnName = NULL;
int pkColumnNameLength;
int pkColumnType = SQLITE_INTEGER;
for (int i = 3; i < argc; i++) { struct VectorColumnDefinition vecColumn; struct Vec0PartitionColumnDefinition partitionColumn; struct Vec0AuxiliaryColumnDefinition auxColumn; struct Vec0MetadataColumnDefinition metadataColumn;
char *cName = NULL;
int cNameLength;
int cType;
// Scenario #1: Constructor argument is a vector column definition, ie `foo float[1024]`
rc = vec0_parse_vector_column(argv[i], strlen(argv[i]), &vecColumn);
if (rc == SQLITE_ERROR) {
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "could not parse vector column '%s'", argv[i]);
goto error;
}
if (rc == SQLITE_OK) {
if (numVectorColumns >= VEC0_MAX_VECTOR_COLUMNS) {
sqlite3_free(vecColumn.name);
*pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "Too many provided vector columns, maximum %d",
VEC0_MAX_VECTOR_COLUMNS);
goto error;
}
if (vecColumn.dimensions > SQLITE_VEC_VEC0_MAX_DIMENSIONS) {
sqlite3_free(vecColumn.name);
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "Dimension on vector column too large, provided %lld, maximum %lld",
(i64)vecColumn.dimensions, SQLITE_VEC_VEC0_MAX_DIMENSIONS);
goto error;
}
// DiskANN validation
if (vecColumn.index_type == VEC0_INDEX_TYPE_DISKANN) {
if (vecColumn.element_type == SQLITE_VEC_ELEMENT_TYPE_BIT) {
sqlite3_free(vecColumn.name);
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "DiskANN index is not supported on bit vector columns");
goto error;
}
if (vecColumn.diskann.quantizer_type == VEC0_DISKANN_QUANTIZER_BINARY &&
(vecColumn.dimensions % CHAR_BIT) != 0) {
sqlite3_free(vecColumn.name);
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "DiskANN with binary quantizer requires dimensions divisible by 8");
goto error;
}
}
if (numVectorColumns <= 0) {
*pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "At least one vector column is required");
goto error;
}
#if SQLITE_VEC_ENABLE_RESCORE
{
int hasRescore = 0;
for (int i = 0; i < numVectorColumns; i++) {
if (pNew->vector_columns[i].index_type == VEC0_INDEX_TYPE_RESCORE) {
hasRescore = 1; break;
}
}
if (hasRescore) {
if (numMetadataColumns > 0) {
*pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "Metadata columns are not supported with rescore indexes");
goto error;
}
if (numPartitionColumns > 0) {
*pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "Partition key columns are not supported with rescore indexes");
goto error;
}
}
} #endif
// IVF indexes do not support auxiliary, metadata, or partition key columns.
{
int has_ivf = 0;
for (int i = 0; i < numVectorColumns; i++) {
if (pNew->vector_columns[i].index_type == VEC0_INDEX_TYPE_IVF) {
has_ivf = 1; break;
}
}
if (has_ivf) {
if (numPartitionColumns > 0) {
*pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "partition key columns are not supported with IVF indexes");
goto error;
}
if (numMetadataColumns > 0) {
*pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "metadata columns are not supported with IVF indexes");
goto error;
}
}
}
// DiskANN columns cannot coexist with aux/metadata/partition columns
for (int i = 0; i < numVectorColumns; i++) {
if (pNew->vector_columns[i].index_type == VEC0_INDEX_TYPE_DISKANN) {
if (numMetadataColumns > 0) {
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "Metadata columns are not supported with DiskANN-indexed vector columns");
goto error;
}
if (numPartitionColumns > 0) {
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "Partition key columns are not supported with DiskANN-indexed vector columns");
goto error;
} break;
}
}
// Determine whether to add the FTS5-style hidden command column. // New tables (isCreate) always get it; existing tables only if created // with v0.1.10+ (which validated no column name == table name).
int hasCommandColumn = 0;
if (isCreate) { // Validate no user column name conflicts with the table name const char *tblName = argv[2];
int tblNameLen = (int)strlen(tblName);
for (int i = 0; i < numVectorColumns; i++) {
if (pNew->vector_columns[i].name_length == tblNameLen &&
sqlite3_strnicmp(pNew->vector_columns[i].name, tblName, tblNameLen) == 0) {
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "column name '%s' conflicts with table name (reserved for command column)",
tblName);
goto error;
}
}
for (int i = 0; i < numPartitionColumns; i++) {
if (pNew->paritition_columns[i].name_length == tblNameLen &&
sqlite3_strnicmp(pNew->paritition_columns[i].name, tblName, tblNameLen) == 0) {
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "column name '%s' conflicts with table name (reserved for command column)",
tblName);
goto error;
}
}
for (int i = 0; i < numAuxiliaryColumns; i++) {
if (pNew->auxiliary_columns[i].name_length == tblNameLen &&
sqlite3_strnicmp(pNew->auxiliary_columns[i].name, tblName, tblNameLen) == 0) {
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "column name '%s' conflicts with table name (reserved for command column)",
tblName);
goto error;
}
}
for (int i = 0; i < numMetadataColumns; i++) {
if (pNew->metadata_columns[i].name_length == tblNameLen &&
sqlite3_strnicmp(pNew->metadata_columns[i].name, tblName, tblNameLen) == 0) {
*pzErr = sqlite3_mprintf(
VEC_CONSTRUCTOR_ERROR "column name '%s' conflicts with table name (reserved for command column)",
tblName);
goto error;
}
}
hasCommandColumn = 1;
} else { // xConnect: check _info shadow table for version
sqlite3_stmt *stmtInfo = NULL;
char *zInfoSql = sqlite3_mprintf( "SELECT value FROM " VEC0_SHADOW_INFO_NAME " WHERE key = 'CREATE_VERSION_PATCH'",
argv[1], argv[2]);
if (zInfoSql) {
int infoRc = sqlite3_prepare_v2(db, zInfoSql, -1, &stmtInfo, NULL);
sqlite3_free(zInfoSql);
if (infoRc == SQLITE_OK && sqlite3_step(stmtInfo) == SQLITE_ROW) {
int patch = sqlite3_column_int(stmtInfo, 0);
hasCommandColumn = (patch >= 10); // v0.1.10+
} // If _info doesn't exist or has no version, assume old table
sqlite3_finalize(stmtInfo);
}
}
pNew->hasCommandColumn = hasCommandColumn;
sqlite3_str *createStr = sqlite3_str_new(NULL);
sqlite3_str_appendall(createStr, "CREATE TABLE x(");
if (pkColumnName) {
sqlite3_str_appendf(createStr, "\"%.*w\" primary key, ", pkColumnNameLength,
pkColumnName);
} else {
sqlite3_str_appendall(createStr, "rowid, ");
}
for (int i = 0; i < numVectorColumns + numPartitionColumns + numAuxiliaryColumns + numMetadataColumns; i++) { switch(pNew->user_column_kinds[i]) { case SQLITE_VEC0_USER_COLUMN_KIND_VECTOR: {
int vector_idx = pNew->user_column_idxs[i];
sqlite3_str_appendf(createStr, "\"%.*w\", ",
pNew->vector_columns[vector_idx].name_length,
pNew->vector_columns[vector_idx].name); break;
} case SQLITE_VEC0_USER_COLUMN_KIND_PARTITION: {
int partition_idx = pNew->user_column_idxs[i];
sqlite3_str_appendf(createStr, "\"%.*w\", ",
pNew->paritition_columns[partition_idx].name_length,
pNew->paritition_columns[partition_idx].name); break;
} case SQLITE_VEC0_USER_COLUMN_KIND_AUXILIARY: {
int auxiliary_idx = pNew->user_column_idxs[i];
sqlite3_str_appendf(createStr, "\"%.*w\", ",
pNew->auxiliary_columns[auxiliary_idx].name_length,
pNew->auxiliary_columns[auxiliary_idx].name); break;
} case SQLITE_VEC0_USER_COLUMN_KIND_METADATA: {
int metadata_idx = pNew->user_column_idxs[i];
sqlite3_str_appendf(createStr, "\"%.*w\", ",
pNew->metadata_columns[metadata_idx].name_length,
pNew->metadata_columns[metadata_idx].name); break;
}
}
}
if (hasCommandColumn) {
sqlite3_str_appendf(createStr, " \"%w\" hidden, distance hidden, k hidden) ", argv[2]);
} else {
sqlite3_str_appendall(createStr, " distance hidden, k hidden) ");
}
if (pkColumnName) {
sqlite3_str_appendall(createStr, "without rowid ");
}
zSql = sqlite3_str_finish(createStr);
if (!zSql) {
goto error;
}
rc = sqlite3_declare_vtab(db, zSql);
sqlite3_free((void *)zSql);
if (rc != SQLITE_OK) {
*pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "could not declare virtual table, '%s'",
sqlite3_errmsg(db));
goto error;
}
// All the different type of "values" provided to argv/argc in vec0Filter. // These enums denote the use and purpose of all of them. typedef enum { // If any values are updated, please update the ARCHITECTURE.md docs accordingly!
// ~~~ KNN QUERIES ~~~ //
VEC0_IDXSTR_KIND_KNN_MATCH = '{',
VEC0_IDXSTR_KIND_KNN_K = '}',
VEC0_IDXSTR_KIND_KNN_ROWID_IN = '[', // argv[i] is a constraint on a PARTITON KEY column in a KNN query //
VEC0_IDXSTR_KIND_KNN_PARTITON_CONSTRAINT = ']',
// argv[i] is a constraint on the distance column in a KNN query
VEC0_IDXSTR_KIND_KNN_DISTANCE_CONSTRAINT = '*',
// ~~~ POINT QUERIES ~~~ //
VEC0_IDXSTR_KIND_POINT_ID = '!',
// The different SQLITE_INDEX_CONSTRAINT values that vec0 partition key columns // support, but as characters that fit nicely in idxstr. typedef enum { // If any values are updated, please update the ARCHITECTURE.md docs accordingly!
// Equality constraint on a PARTITON KEY column, ex `user_id = 123`
VEC0_PARTITION_OPERATOR_EQ = 'a',
// "Greater than" constraint on a PARTITON KEY column, ex `year > 2024`
VEC0_PARTITION_OPERATOR_GT = 'b',
// "Less than or equal to" constraint on a PARTITON KEY column, ex `year <= 2024`
VEC0_PARTITION_OPERATOR_LE = 'c',
// "Less than" constraint on a PARTITON KEY column, ex `year < 2024`
VEC0_PARTITION_OPERATOR_LT = 'd',
// "Greater than or equal to" constraint on a PARTITON KEY column, ex `year >= 2024`
VEC0_PARTITION_OPERATOR_GE = 'e',
sqlite3_str *idxStr = sqlite3_str_new(NULL);
int rc;
if (iMatchTerm >= 0) {
if (iLimitTerm < 0 && iKTerm < 0) {
vtab_set_error(
pVTab, "A LIMIT or 'k = ?' constraint is required on vec0 knn queries.");
rc = SQLITE_ERROR;
goto done;
}
if (iLimitTerm >= 0 && iKTerm >= 0) {
vtab_set_error(pVTab, "Only LIMIT or 'k =?' can be provided, not both");
rc = SQLITE_ERROR;
goto done;
}
if (pIdxInfo->nOrderBy) {
if (pIdxInfo->nOrderBy > 1) {
vtab_set_error(pVTab, "Only a single 'ORDER BY distance' clause is " "allowed on vec0 KNN queries");
rc = SQLITE_ERROR;
goto done;
}
if (pIdxInfo->aOrderBy[0].iColumn != vec0_column_distance_idx(p)) {
vtab_set_error(pVTab, "Only a single 'ORDER BY distance' clause is allowed on " "vec0 KNN queries, not on other columns");
rc = SQLITE_ERROR;
goto done;
}
if (pIdxInfo->aOrderBy[0].desc) {
vtab_set_error(
pVTab, "Only ascending in ORDER BY distance clause is supported, " "DESC is not supported yet.");
rc = SQLITE_ERROR;
goto done;
}
}
if(hasAuxConstraint) { // IMP: V25623_09693
vtab_set_error(pVTab, "An illegal WHERE constraint was provided on a vec0 auxiliary column in a KNN query.");
rc = SQLITE_ERROR;
goto done;
}
#if COMPILER_SUPPORTS_VTAB_IN
if (iRowidInTerm >= 0) { // already validated as >= SQLite 3.38 bc iRowidInTerm is only >= 0 when // vtabIn == 1
sqlite3_vtab_in(pIdxInfo, iRowidInTerm, 1);
pIdxInfo->aConstraintUsage[iRowidInTerm].argvIndex = argvIndex++;
pIdxInfo->aConstraintUsage[iRowidInTerm].omit = 1;
sqlite3_str_appendchar(idxStr, 1, VEC0_IDXSTR_KIND_KNN_ROWID_IN);
sqlite3_str_appendchar(idxStr, 3, '_');
} #endif
// find any PARTITION KEY column constraints
for (int i = 0; i < pIdxInfo->nConstraint; i++) {
if (!pIdxInfo->aConstraint[i].usable) continue;
int iColumn = pIdxInfo->aConstraint[i].iColumn;
int op = pIdxInfo->aConstraint[i].op;
if(op == SQLITE_INDEX_CONSTRAINT_LIMIT || op == SQLITE_INDEX_CONSTRAINT_OFFSET) { continue;
}
if(!vec0_column_idx_is_partition(p, iColumn)) { continue;
}
int partition_idx = vec0_column_idx_to_partition_idx(p, iColumn);
char value = 0;
switch(op) { case SQLITE_INDEX_CONSTRAINT_EQ: {
value = VEC0_PARTITION_OPERATOR_EQ; break;
} case SQLITE_INDEX_CONSTRAINT_GT: {
value = VEC0_PARTITION_OPERATOR_GT; break;
} case SQLITE_INDEX_CONSTRAINT_LE: {
value = VEC0_PARTITION_OPERATOR_LE; break;
} case SQLITE_INDEX_CONSTRAINT_LT: {
value = VEC0_PARTITION_OPERATOR_LT; break;
} case SQLITE_INDEX_CONSTRAINT_GE: {
value = VEC0_PARTITION_OPERATOR_GE; break;
} case SQLITE_INDEX_CONSTRAINT_NE: {
value = VEC0_PARTITION_OPERATOR_NE; break;
}
}
// find any metadata column constraints
for (int i = 0; i < pIdxInfo->nConstraint; i++) {
if (!pIdxInfo->aConstraint[i].usable) continue;
int iColumn = pIdxInfo->aConstraint[i].iColumn;
int op = pIdxInfo->aConstraint[i].op;
if(op == SQLITE_INDEX_CONSTRAINT_LIMIT || op == SQLITE_INDEX_CONSTRAINT_OFFSET) { continue;
}
if(!vec0_column_idx_is_metadata(p, iColumn)) { continue;
}
int metadata_idx = vec0_column_idx_to_metadata_idx(p, iColumn);
char value = 0;
switch(op) { case SQLITE_INDEX_CONSTRAINT_EQ: {
int vtabIn = 0; #if COMPILER_SUPPORTS_VTAB_IN
if (sqlite3_libversion_number() >= 3038000) {
vtabIn = sqlite3_vtab_in(pIdxInfo, i, -1);
}
if(vtabIn) { switch(p->metadata_columns[metadata_idx].kind) { case VEC0_METADATA_COLUMN_KIND_FLOAT: case VEC0_METADATA_COLUMN_KIND_BOOLEAN: { // IMP: V15248_32086
rc = SQLITE_ERROR;
vtab_set_error(pVTab, "'xxx in (...)' is only available on INTEGER or TEXT metadata columns.");
goto done; break;
} case VEC0_METADATA_COLUMN_KIND_INTEGER: case VEC0_METADATA_COLUMN_KIND_TEXT: { break;
}
}
value = VEC0_METADATA_OPERATOR_IN;
sqlite3_vtab_in(pIdxInfo, i, 1);
}else #endif
{
value = VEC0_PARTITION_OPERATOR_EQ;
} break;
} case SQLITE_INDEX_CONSTRAINT_GT: {
value = VEC0_METADATA_OPERATOR_GT; break;
} case SQLITE_INDEX_CONSTRAINT_LE: {
value = VEC0_METADATA_OPERATOR_LE; break;
} case SQLITE_INDEX_CONSTRAINT_LT: {
value = VEC0_METADATA_OPERATOR_LT; break;
} case SQLITE_INDEX_CONSTRAINT_GE: {
value = VEC0_METADATA_OPERATOR_GE; break;
} case SQLITE_INDEX_CONSTRAINT_NE: {
value = VEC0_METADATA_OPERATOR_NE; break;
} default: { // IMP: V16511_00582
rc = SQLITE_ERROR;
vtab_set_error(pVTab, "An illegal WHERE constraint was provided on a vec0 metadata column in a KNN query. " "Only one of EQUALS, GREATER_THAN, LESS_THAN_OR_EQUAL, LESS_THAN, GREATER_THAN_OR_EQUAL, NOT_EQUALS is allowed."
);
goto done;
}
}
if(p->metadata_columns[metadata_idx].kind == VEC0_METADATA_COLUMN_KIND_BOOLEAN) {
if(!(value == VEC0_METADATA_OPERATOR_EQ || value == VEC0_METADATA_OPERATOR_NE)) { // IMP: V10145_26984
rc = SQLITE_ERROR;
vtab_set_error(pVTab, "ONLY EQUALS (=) or NOT_EQUALS (!=) operators are allowed on boolean metadata columns.");
goto done;
}
}
// find any distance column constraints
for (int i = 0; i < pIdxInfo->nConstraint; i++) {
if (!pIdxInfo->aConstraint[i].usable) continue;
int iColumn = pIdxInfo->aConstraint[i].iColumn;
int op = pIdxInfo->aConstraint[i].op;
if(op == SQLITE_INDEX_CONSTRAINT_LIMIT || op == SQLITE_INDEX_CONSTRAINT_OFFSET) { continue;
}
if(vec0_column_distance_idx(p) != iColumn) { continue;
}
char value = 0; switch(op) { case SQLITE_INDEX_CONSTRAINT_GT: {
value = VEC0_DISTANCE_CONSTRAINT_GT; break;
} case SQLITE_INDEX_CONSTRAINT_GE: {
value = VEC0_DISTANCE_CONSTRAINT_GE; break;
} case SQLITE_INDEX_CONSTRAINT_LT: {
value = VEC0_DISTANCE_CONSTRAINT_LT; break;
} case SQLITE_INDEX_CONSTRAINT_LE: {
value = VEC0_DISTANCE_CONSTRAINT_LE; break;
} default: { // IMP TODO
rc = SQLITE_ERROR;
vtab_set_error(
pVTab, "Illegal WHERE constraint on distance column in a KNN query. " "Only one of GT, GE, LT, LE constraints are allowed."
);
goto done;
}
}
#ifdef SQLITE_VEC_EXPERIMENTAL_MIN_IDX // Max-heap variant: O(n log k) single-pass. // out[0..heap_size-1] stores indices; heap ordered by distances descending // so out[0] is always the index of the LARGEST distance in the top-k.
(void)bTaken;
int heap_size = 0;
int n = 1;
for(int i = 0; i < numValueEntries; i++) {
int idx = 1 + (i * 4);
char kind = idxStr[idx + 0];
if(kind != VEC0_IDXSTR_KIND_KNN_PARTITON_CONSTRAINT) { continue;
}
sqlite3_bind_value(*outStmt, n++, argv[i]);
}
return rc;
}
// a single `xxx in (...)` constraint on a metadata column. TEXT or INTEGER only for now. struct Vec0MetadataIn{ // index of argv[i]` the constraint is on
int argv_idx; // metadata column index of the constraint, derived from idxStr + argv_idx
int metadata_idx; // array of the copied `(...)` values from sqlite3_vtab_in_first()/sqlite3_vtab_in_next() struct Array array;
};
// Array elements for `xxx in (...)` values for a text column. basically just a string struct Vec0MetadataInTextEntry {
int n;
char * zString;
};
int vec0_metadata_filter_text(vec0_vtab * p, sqlite3_value * value, constvoid * buffer, int size, vec0_metadata_operator op, u8* b, int metadata_idx, int chunk_rowid, struct Array * aMetadataIn, int argv_idx) {
int rc;
sqlite3_stmt * stmt = NULL;
i64 * rowids = NULL;
sqlite3_blob * rowidsBlob; const char * sTarget = (const char *) sqlite3_value_text(value);
int nTarget = sqlite3_value_bytes(value);
// TODO(perf): only text metadata news the rowids BLOB. Make it so that // rowids BLOB is re-used when multiple fitlers on text columns, // ex "name BETWEEN 'a' and 'b'""
rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowChunksName, "rowids", chunk_rowid, 0, &rowidsBlob);
if(rc != SQLITE_OK) { return rc;
}
assert(sqlite3_blob_bytes(rowidsBlob) % sizeof(i64) == 0);
assert((sqlite3_blob_bytes(rowidsBlob) / sizeof(i64)) == size);
switch(op) {
int nPrefix;
char * sPrefix;
char *sFull;
int nFull;
u8 * view; case VEC0_METADATA_OPERATOR_EQ: {
for(int i = 0; i < size; i++) {
view = &((u8*) buffer)[i * VEC0_METADATA_TEXT_VIEW_BUFFER_LENGTH];
nPrefix = ((int*) view)[0];
sPrefix = (char *) &view[4];
// for EQ the text lengths must match
if(nPrefix != nTarget) {
bitmap_set(b, i, 0); continue;
}
int cmpPrefix = strncmp(sPrefix, sTarget, min(nPrefix, VEC0_METADATA_TEXT_VIEW_DATA_LENGTH));
// for short strings, use the prefix comparison direclty
if(nPrefix <= VEC0_METADATA_TEXT_VIEW_DATA_LENGTH) {
bitmap_set(b, i, cmpPrefix == 0); continue;
} // for EQ on longs strings, the prefix must match
if(cmpPrefix) {
bitmap_set(b, i, 0); continue;
} // consult the full string
rc = vec0_get_metadata_text_long_value(p, &stmt, metadata_idx, rowids[i], &nFull, &sFull);
if(rc != SQLITE_OK) {
goto done;
}
if(nPrefix != nFull) {
rc = SQLITE_ERROR;
goto done;
}
bitmap_set(b, i, strncmp(sFull, sTarget, nFull) == 0);
} break;
} case VEC0_METADATA_OPERATOR_NE: {
for(int i = 0; i < size; i++) {
view = &((u8*) buffer)[i * VEC0_METADATA_TEXT_VIEW_BUFFER_LENGTH];
nPrefix = ((int*) view)[0];
sPrefix = (char *) &view[4];
// for NE if text lengths dont match, it never will
if(nPrefix != nTarget) {
bitmap_set(b, i, 1); continue;
}
int cmpPrefix = strncmp(sPrefix, sTarget, min(nPrefix, VEC0_METADATA_TEXT_VIEW_DATA_LENGTH));
// for short strings, use the prefix comparison direclty
if(nPrefix <= VEC0_METADATA_TEXT_VIEW_DATA_LENGTH) {
bitmap_set(b, i, cmpPrefix != 0); continue;
} // for NE on longs strings, if prefixes dont match, then long string wont
if(cmpPrefix) {
bitmap_set(b, i, 1); continue;
} // consult the full string
rc = vec0_get_metadata_text_long_value(p, &stmt, metadata_idx, rowids[i], &nFull, &sFull);
if(rc != SQLITE_OK) {
goto done;
}
if(nPrefix != nFull) {
rc = SQLITE_ERROR;
goto done;
}
bitmap_set(b, i, strncmp(sFull, sTarget, nFull) != 0);
} break;
} case VEC0_METADATA_OPERATOR_GT: {
for(int i = 0; i < size; i++) {
view = &((u8*) buffer)[i * VEC0_METADATA_TEXT_VIEW_BUFFER_LENGTH];
nPrefix = ((int*) view)[0];
sPrefix = (char *) &view[4];
int cmpPrefix = strncmp(sPrefix, sTarget, min(min(nPrefix, VEC0_METADATA_TEXT_VIEW_DATA_LENGTH), nTarget));
if(nPrefix < VEC0_METADATA_TEXT_VIEW_DATA_LENGTH) { // if prefix match, check which is longer
if(cmpPrefix == 0) {
bitmap_set(b, i, nPrefix > nTarget);
} else {
bitmap_set(b, i, cmpPrefix > 0);
} continue;
} // TODO(perf): may not need to compare full text in some cases
if(nPrefix < VEC0_METADATA_TEXT_VIEW_DATA_LENGTH) { // if prefix match, check which is longer
if(cmpPrefix == 0) {
bitmap_set(b, i, nPrefix >= nTarget);
} else {
bitmap_set(b, i, cmpPrefix >= 0);
} continue;
} // TODO(perf): may not need to compare full text in some cases
if(nPrefix < VEC0_METADATA_TEXT_VIEW_DATA_LENGTH) { // if prefix match, check which is longer
if(cmpPrefix == 0) {
bitmap_set(b, i, nPrefix <= nTarget);
} else {
bitmap_set(b, i, cmpPrefix <= 0);
} continue;
} // TODO(perf): may not need to compare full text in some cases
if(nPrefix < VEC0_METADATA_TEXT_VIEW_DATA_LENGTH) { // if prefix match, check which is longer
if(cmpPrefix == 0) {
bitmap_set(b, i, nPrefix < nTarget);
} else {
bitmap_set(b, i, cmpPrefix < 0);
} continue;
} // TODO(perf): may not need to compare full text in some cases
vec0_metadata_column_kind kind = p->metadata_columns[metadata_idx].kind;
int szMatch = 0;
int blobSize = sqlite3_blob_bytes(blob); switch(kind) { case VEC0_METADATA_COLUMN_KIND_BOOLEAN: {
szMatch = blobSize == size / CHAR_BIT; break;
} case VEC0_METADATA_COLUMN_KIND_INTEGER: {
szMatch = blobSize == size * sizeof(i64); break;
} case VEC0_METADATA_COLUMN_KIND_FLOAT: {
szMatch = blobSize == size * sizeof(double); break;
} case VEC0_METADATA_COLUMN_KIND_TEXT: {
szMatch = blobSize == size * VEC0_METADATA_TEXT_VIEW_BUFFER_LENGTH; break;
}
}
if(!szMatch) { return SQLITE_ERROR;
} void * buffer = sqlite3_malloc(blobSize);
if(!buffer) { return SQLITE_NOMEM;
}
rc = sqlite3_blob_read(blob, buffer, blobSize, 0);
if(rc != SQLITE_OK) {
goto done;
} switch(kind) { case VEC0_METADATA_COLUMN_KIND_BOOLEAN: {
int target = sqlite3_value_int(value);
if( (target && op == VEC0_METADATA_OPERATOR_EQ) || (!target && op == VEC0_METADATA_OPERATOR_NE)) {
for(int i = 0; i < size; i++) { bitmap_set(b, i, bitmap_get((u8*) buffer, i)); }
} else {
for(int i = 0; i < size; i++) { bitmap_set(b, i, !bitmap_get((u8*) buffer, i)); }
} break;
} case VEC0_METADATA_COLUMN_KIND_INTEGER: {
i64 * array = (i64*) buffer;
i64 target = sqlite3_value_int64(value); switch(op) { case VEC0_METADATA_OPERATOR_EQ: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] == target); } break;
} case VEC0_METADATA_OPERATOR_GT: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] > target); } break;
} case VEC0_METADATA_OPERATOR_LE: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] <= target); } break;
} case VEC0_METADATA_OPERATOR_LT: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] < target); } break;
} case VEC0_METADATA_OPERATOR_GE: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] >= target); } break;
} case VEC0_METADATA_OPERATOR_NE: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] != target); } break;
} case VEC0_METADATA_OPERATOR_IN: {
int metadataInIdx = -1;
for(size_t i = 0; i < aMetadataIn->length; i++) { struct Vec0MetadataIn * metadataIn = &((struct Vec0MetadataIn *) aMetadataIn->z)[i];
if(metadataIn->argv_idx == argv_idx) {
metadataInIdx = i; break;
}
}
if(metadataInIdx < 0) {
rc = SQLITE_ERROR;
goto done;
} struct Vec0MetadataIn * metadataIn = &((struct Vec0MetadataIn *) aMetadataIn->z)[metadataInIdx]; struct Array * aTarget = &(metadataIn->array);
for(int i = 0; i < size; i++) {
for(size_t target_idx = 0; target_idx < aTarget->length; target_idx++) {
if( ((i64*)aTarget->z)[target_idx] == array[i]) {
bitmap_set(b, i, 1); break;
}
}
} break;
}
} break;
} case VEC0_METADATA_COLUMN_KIND_FLOAT: { double * array = (double*) buffer; double target = sqlite3_value_double(value); switch(op) { case VEC0_METADATA_OPERATOR_EQ: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] == target); } break;
} case VEC0_METADATA_OPERATOR_GT: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] > target); } break;
} case VEC0_METADATA_OPERATOR_LE: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] <= target); } break;
} case VEC0_METADATA_OPERATOR_LT: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] < target); } break;
} case VEC0_METADATA_OPERATOR_GE: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] >= target); } break;
} case VEC0_METADATA_OPERATOR_NE: {
for(int i = 0; i < size; i++) { bitmap_set(b, i, array[i] != target); } break;
} case VEC0_METADATA_OPERATOR_IN: { // should never be reached break;
}
} break;
} case VEC0_METADATA_COLUMN_KIND_TEXT: {
rc = vec0_metadata_filter_text(p, value, buffer, size, op, b, metadata_idx, chunk_rowid, aMetadataIn, argv_idx);
if(rc != SQLITE_OK) {
goto done;
} break;
}
}
done:
sqlite3_free(buffer); return rc;
}
int vec0Filter_knn_chunks_iter(vec0_vtab *p, sqlite3_stmt *stmtChunks, struct VectorColumnDefinition *vector_column,
int vectorColumnIdx, struct Array *arrayRowidsIn, struct Array * aMetadataIn, const char * idxStr, int argc, sqlite3_value ** argv, void *queryVector, i64 k, i64 **out_topk_rowids,
f32 **out_topk_distances, i64 *out_used) { // for each chunk, get top min(k, chunk_size) rowid + distances to query vec. // then reconcile all topk_chunks for a true top k. // output only rowids + distances for now
int rc = SQLITE_OK;
sqlite3_blob *blobVectors = NULL;
// open the vector chunk blob for the current chunk
rc = sqlite3_blob_open(p->db, p->schemaName,
p->shadowVectorChunksNames[vectorColumnIdx], "vectors", chunk_id, 0, &blobVectors);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base, "could not open vectors blob for chunk %lld",
chunk_id);
rc = SQLITE_ERROR;
goto cleanup;
}
for (int i = 0; i < p->chunk_size; i++) {
if (!bitmap_get(b, i)) { continue;
};
f32 result; switch (vector_column->element_type) { case SQLITE_VEC_ELEMENT_TYPE_FLOAT32: { const f32 *base_i =
((f32 *)baseVectors) + (i * vector_column->dimensions); switch (vector_column->distance_metric) { case VEC0_DISTANCE_METRIC_L2: {
result = distance_l2_sqr_float(base_i, (f32 *)queryVector,
&vector_column->dimensions); break;
} case VEC0_DISTANCE_METRIC_L1: {
result = distance_l1_f32(base_i, (f32 *)queryVector,
&vector_column->dimensions); break;
} case VEC0_DISTANCE_METRIC_COSINE: {
result = distance_cosine_float(base_i, (f32 *)queryVector,
&vector_column->dimensions); break;
}
} break;
} case SQLITE_VEC_ELEMENT_TYPE_INT8: { const i8 *base_i =
((i8 *)baseVectors) + (i * vector_column->dimensions); switch (vector_column->distance_metric) { case VEC0_DISTANCE_METRIC_L2: {
result = distance_l2_sqr_int8(base_i, (i8 *)queryVector,
&vector_column->dimensions); break;
} case VEC0_DISTANCE_METRIC_L1: {
result = distance_l1_int8(base_i, (i8 *)queryVector,
&vector_column->dimensions); break;
} case VEC0_DISTANCE_METRIC_COSINE: {
result = distance_cosine_int8(base_i, (i8 *)queryVector,
&vector_column->dimensions); break;
}
}
break;
} case SQLITE_VEC_ELEMENT_TYPE_BIT: { const u8 *base_i =
((u8 *)baseVectors) + (i * (vector_column->dimensions / CHAR_BIT));
result = distance_hamming(base_i, (u8 *)queryVector,
&vector_column->dimensions); break;
}
}
chunk_distances[i] = result;
}
if(hasDistanceConstraints) {
for(int i = 0; i < argc; i++) {
int idx = 1 + (i * 4);
char kind = idxStr[idx + 0]; // TODO casts f64 to f32, is that a problem?
f32 target = (f32) sqlite3_value_double(argv[i]);
for (int i = 0; i < used; i++) {
topk_rowids[i] = tmp_topk_rowids[i];
topk_distances[i] = tmp_topk_distances[i];
}
k_used = used; // blobVectors is always opened with read-only permissions, so this never // fails.
sqlite3_blob_close(blobVectors);
blobVectors = NULL;
}
// Scan _diskann_buffer for any buffered (unflushed) vectors and merge // with graph results. This ensures no recall loss for buffered vectors.
{
sqlite3_stmt *bufStmt = NULL;
char *zSql = sqlite3_mprintf( "SELECT rowid, vector FROM " VEC0_SHADOW_DISKANN_BUFFER_N_NAME,
p->schemaName, p->tableName, vectorColumnIdx);
if (!zSql) {
queryVectorCleanup(queryVector);
sqlite3_free(resultRowids);
sqlite3_free(resultDistances);
sqlite3_free(knn_data); return SQLITE_NOMEM;
}
int bufRc = sqlite3_prepare_v2(p->db, zSql, -1, &bufStmt, NULL);
sqlite3_free(zSql);
if (bufRc == SQLITE_OK) { while (sqlite3_step(bufStmt) == SQLITE_ROW) {
i64 bufRowid = sqlite3_column_int64(bufStmt, 0); constvoid *bufVec = sqlite3_column_blob(bufStmt, 1);
f32 dist = vec0_distance_full(
queryVector, bufVec, dimensions, elementType,
vector_column->distance_metric);
// Check if this buffer vector should replace the worst graph result
if (resultCount < (int)k) { // Still have room, just add it
resultRowids[resultCount] = bufRowid;
resultDistances[resultCount] = dist;
resultCount++;
} else { // Find worst (largest distance) in results
int worstIdx = 0;
for (int wi = 1; wi < resultCount; wi++) {
if (resultDistances[wi] > resultDistances[worstIdx]) {
worstIdx = wi;
}
}
if (dist < resultDistances[worstIdx]) {
resultRowids[worstIdx] = bufRowid;
resultDistances[worstIdx] = dist;
}
}
}
sqlite3_finalize(bufStmt);
}
}
queryVectorCleanup(queryVector);
// Sort results by distance (ascending)
for (int si = 0; si < resultCount - 1; si++) {
for (int sj = si + 1; sj < resultCount; sj++) {
if (resultDistances[sj] < resultDistances[si]) {
f32 tmpD = resultDistances[si];
resultDistances[si] = resultDistances[sj];
resultDistances[sj] = tmpD;
i64 tmpR = resultRowids[si];
resultRowids[si] = resultRowids[sj];
resultRowids[sj] = tmpR;
}
}
}
// make sure the query vector matches the vector column (type dimensions etc.)
rc = vector_from_value(argv[query_idx], &queryVector, &dimensions, &elementType,
&queryVectorCleanup, &pzError);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base, "Query vector on the \"%.*s\" column is invalid: %z",
vector_column->name_length, vector_column->name, pzError);
rc = SQLITE_ERROR;
goto cleanup;
}
if (elementType != vector_column->element_type) {
vtab_set_error(
&p->base, "Query vector for the \"%.*s\" column is expected to be of type " "%s, but a %s vector was provided.",
vector_column->name_length, vector_column->name,
vector_subtype_name(vector_column->element_type),
vector_subtype_name(elementType));
rc = SQLITE_ERROR;
goto cleanup;
}
if (dimensions != vector_column->dimensions) {
vtab_set_error(
&p->base, "Dimension mismatch for query vector for the \"%.*s\" column. " "Expected %d dimensions but received %d.",
vector_column->name_length, vector_column->name,
vector_column->dimensions, dimensions);
rc = SQLITE_ERROR;
goto cleanup;
}
i64 k = sqlite3_value_int64(argv[k_idx]);
if (k < 0) {
vtab_set_error(
&p->base, "k value in knn queries must be greater than or equal to 0.");
rc = SQLITE_ERROR;
goto cleanup;
} #define SQLITE_VEC_VEC0_K_MAX 4096
if (k > SQLITE_VEC_VEC0_K_MAX) {
vtab_set_error(
&p->base, "k value in knn query too large, provided %lld and the limit is %lld",
k, SQLITE_VEC_VEC0_K_MAX);
rc = SQLITE_ERROR;
goto cleanup;
}
// handle when a `rowid in (...)` operation was provided // Array of all the rowids that appear in any `rowid in (...)` constraint. // NULL if none were provided, which means a "full" scan. #if COMPILER_SUPPORTS_VTAB_IN
if (rowid_in_idx >= 0) {
sqlite3_value *item;
int rc;
arrayRowidsIn = sqlite3_malloc(sizeof(*arrayRowidsIn));
if (!arrayRowidsIn) {
rc = SQLITE_NOMEM;
goto cleanup;
}
memset(arrayRowidsIn, 0, sizeof(*arrayRowidsIn));
// Option 3: vtab has a user-defined TEXT primary key, so ensure a text value // is provided.
if (p->pkIsText) {
if (sqlite3_value_type(idValue) != SQLITE_TEXT) { // IMP: V04200_21039
vtab_set_error(&p->base, "The %s virtual table was declared with a TEXT primary " "key, but a non-TEXT value was provided in an INSERT.",
p->tableName); return SQLITE_ERROR;
}
// Option 1: User supplied a i64 rowid
if (sqlite3_value_type(idValue) == SQLITE_INTEGER) {
i64 suppliedRowid = sqlite3_value_int64(idValue);
rc = vec0_rowids_insert_rowid(p, suppliedRowid);
if (rc == SQLITE_OK) {
*rowid = suppliedRowid;
} return rc;
}
// Option 2: User did not suppled a rowid
if (sqlite3_value_type(idValue) != SQLITE_NULL) { // IMP: V30855_14925
vtab_set_error(&p->base, "Only integers are allows for primary key values on %s",
p->tableName); return SQLITE_ERROR;
} // NULL to get next auto-incremented value return vec0_rowids_insert_id(p, NULL, rowid);
}
if (rc != SQLITE_OK) {
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "Could not read validity bitmap for %s.%s.%lld",
p->schemaName, p->shadowChunksName, *chunk_rowid);
goto cleanup;
}
// find the next available offset, ie first `0` in the bitmap.
for (int i = 0; i < validitySize; i++) {
if ((*bufferChunksValidity)[i] == 0b11111111) continue;
for (int j = 0; j < CHAR_BIT; j++) {
if (((((*bufferChunksValidity)[i] >> j) & 1) == 0)) {
*chunk_offset = (i * CHAR_BIT) + j;
goto done;
}
}
}
done: // latest chunk was full, so need to create a new one
if (*chunk_offset == -1) {
rc = vec0_new_chunk(p, partitionKeyValues, chunk_rowid);
if (rc != SQLITE_OK) { // IMP: V08441_25279
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "Could not insert a new vector chunk");
rc = SQLITE_ERROR; // otherwise raises a DatabaseError and not operational // error?
goto cleanup;
}
*chunk_offset = 0;
// blobChunksValidity and pValidity are stale, pointing to the previous // (full) chunk. to re-assign them
rc = sqlite3_blob_close(*blobChunksValidity);
sqlite3_free((void *)*bufferChunksValidity);
*blobChunksValidity = NULL;
*bufferChunksValidity = NULL;
if (rc != SQLITE_OK) {
vtab_set_error(&p->base, VEC_INTERAL_ERROR "unknown error, blobChunksValidity could not be closed, " "please file an issue.");
rc = SQLITE_ERROR;
goto cleanup;
}
rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowChunksName, "validity", *chunk_rowid, 1, blobChunksValidity);
if (rc != SQLITE_OK) {
vtab_set_error(
&p->base,
VEC_INTERAL_ERROR "Could not open validity blob for newly created chunk %s.%s.%lld",
p->schemaName, p->shadowChunksName, *chunk_rowid);
goto cleanup;
}
validitySize = sqlite3_blob_bytes(*blobChunksValidity);
if (validitySize != p->chunk_size / CHAR_BIT) {
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "validity blob size mismatch for newly created chunk " "%s.%s.%lld. Exepcted %lld, got %lld",
p->schemaName, p->shadowChunksName, *chunk_rowid,
p->chunk_size / CHAR_BIT, validitySize);
goto cleanup;
}
*bufferChunksValidity = sqlite3_malloc(validitySize);
rc = sqlite3_blob_read(*blobChunksValidity, (void *)*bufferChunksValidity,
validitySize, 0);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "could not read validity blob newly created chunk " "%s.%s.%lld",
p->schemaName, p->shadowChunksName, *chunk_rowid);
goto cleanup;
}
}
rc = SQLITE_OK;
cleanup: return rc;
}
/** *@briefWritethevectordataintotheprovidedvectorblobatthegiven *offset * *@paramblobVectorsSQLiteBLOBtowriteto *@paramchunk_offsetthe"offset"(ievaliditybitmapposition)towritethe *vectorto *@parambVectorpointertothevectorcontainingdata *@paramdimensionshowmanydimensionsthevectorhas *@paramelement_typethevectortype *@returnresultofsqlite3_blob_write,SQLITE_OKonsuccess,otherwisefailure
*/ static int
vec0_write_vector_to_vector_blob(sqlite3_blob *blobVectors, i64 chunk_offset, constvoid *bVector, size_t dimensions,
enum VectorElementType element_type) {
int n;
int offset;
switch (element_type) { case SQLITE_VEC_ELEMENT_TYPE_FLOAT32:
n = dimensions * sizeof(f32);
offset = chunk_offset * dimensions * sizeof(f32); break; case SQLITE_VEC_ELEMENT_TYPE_INT8:
n = dimensions * sizeof(i8);
offset = chunk_offset * dimensions * sizeof(i8); break; case SQLITE_VEC_ELEMENT_TYPE_BIT:
n = dimensions / CHAR_BIT;
offset = chunk_offset * dimensions / CHAR_BIT; break;
}
return sqlite3_blob_write(blobVectors, bVector, n, offset);
}
// mark the validity bit for this row in the chunk's validity bitmap // Get the byte offset of the bitmap
char unsigned bx = bufferChunksValidity[chunk_offset / CHAR_BIT]; // set the bit at the chunk_offset position inside that byte
bx = bx | (1 << (chunk_offset % CHAR_BIT)); // write that 1 byte
rc = sqlite3_blob_write(blobChunksValidity, &bx, 1, chunk_offset / CHAR_BIT);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base, VEC_INTERAL_ERROR "could not mark validity bit "); return rc;
}
// Go insert the vector data into the vector chunk shadow tables
for (int i = 0; i < p->numVectorColumns; i++) { // Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks
if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) continue;
i64 expected =
p->chunk_size * vector_column_byte_size(p->vector_columns[i]);
i64 actual = sqlite3_blob_bytes(blobVectors);
if (actual != expected) { // IMP: V16386_00456
vtab_set_error(
&p->base,
VEC_INTERAL_ERROR "vector blob size mismatch on %s.%s.%lld. Expected %lld, actual %lld",
p->schemaName, p->shadowVectorChunksNames[i], chunk_rowid, expected,
actual);
rc = SQLITE_ERROR; // already error, can ignore result code
sqlite3_blob_close(blobVectors);
goto cleanup;
};
rc = vec0_write_vector_to_vector_blob(
blobVectors, chunk_offset, vectorDatas[i],
p->vector_columns[i].dimensions, p->vector_columns[i].element_type);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "could not write vector blob on %s.%s.%lld",
p->schemaName, p->shadowVectorChunksNames[i], chunk_rowid);
rc = SQLITE_ERROR; // already error, can ignore result code
sqlite3_blob_close(blobVectors);
goto cleanup;
}
rc = sqlite3_blob_close(blobVectors);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "could not close vector blob on %s.%s.%lld",
p->schemaName, p->shadowVectorChunksNames[i], chunk_rowid);
rc = SQLITE_ERROR;
goto cleanup;
}
}
// write the new rowid to the rowids column of the _chunks table
rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowChunksName, "rowids",
chunk_rowid, 1, &blobChunksRowids);
if (rc != SQLITE_OK) { // IMP: V09221_26060
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "could not open rowids blob on %s.%s.%lld",
p->schemaName, p->shadowChunksName, chunk_rowid);
goto cleanup;
}
i64 expected = p->chunk_size * sizeof(i64);
i64 actual = sqlite3_blob_bytes(blobChunksRowids);
if (expected != actual) { // IMP: V12779_29618
vtab_set_error(
&p->base,
VEC_INTERAL_ERROR "rowids blob size mismatch on %s.%s.%lld. Expected %lld, actual %lld",
p->schemaName, p->shadowChunksName, chunk_rowid, expected, actual);
rc = SQLITE_ERROR;
goto cleanup;
}
rc = sqlite3_blob_write(blobChunksRowids, &rowid, sizeof(i64),
chunk_offset * sizeof(i64));
if (rc != SQLITE_OK) {
vtab_set_error(
&p->base, VEC_INTERAL_ERROR "could not write rowids blob on %s.%s.%lld",
p->schemaName, p->shadowChunksName, chunk_rowid);
rc = SQLITE_ERROR;
goto cleanup;
}
// Now with all the vectors inserted, go back and update the _rowids table // with the new chunk_rowid/chunk_offset values
rc = vec0_rowids_update_position(p, rowid, chunk_rowid, chunk_offset);
cleanup:
brc = sqlite3_blob_close(blobChunksRowids);
if ((rc == SQLITE_OK) && (brc != SQLITE_OK)) {
vtab_set_error(
&p->base, VEC_INTERAL_ERROR "could not close rowids blob on %s.%s.%lld",
p->schemaName, p->shadowChunksName, chunk_rowid); return brc;
} return rc;
}
int vec0_write_metadata_value(vec0_vtab *p, int metadata_column_idx, i64 rowid, i64 chunk_id, i64 chunk_offset, sqlite3_value * v, int isupdate) {
int rc; struct Vec0MetadataColumnDefinition * metadata_column = &p->metadata_columns[metadata_column_idx];
vec0_metadata_column_kind kind = metadata_column->kind;
// verify input value matches column type switch(kind) { case VEC0_METADATA_COLUMN_KIND_BOOLEAN: {
if(sqlite3_value_type(v) != SQLITE_INTEGER || ((sqlite3_value_int(v) != 0) && (sqlite3_value_int(v) != 1))) {
rc = SQLITE_ERROR;
vtab_set_error(&p->base, "Expected 0 or 1 for BOOLEAN metadata column %.*s", metadata_column->name_length, metadata_column->name);
goto done;
} break;
} case VEC0_METADATA_COLUMN_KIND_INTEGER: {
if(sqlite3_value_type(v) != SQLITE_INTEGER) {
rc = SQLITE_ERROR;
vtab_set_error(&p->base, "Expected integer for INTEGER metadata column %.*s, received %s", metadata_column->name_length, metadata_column->name, type_name(sqlite3_value_type(v)));
goto done;
} break;
} case VEC0_METADATA_COLUMN_KIND_FLOAT: {
if(sqlite3_value_type(v) != SQLITE_FLOAT) {
rc = SQLITE_ERROR;
vtab_set_error(&p->base, "Expected float for FLOAT metadata column %.*s, received %s", metadata_column->name_length, metadata_column->name, type_name(sqlite3_value_type(v)));
goto done;
} break;
} case VEC0_METADATA_COLUMN_KIND_TEXT: {
if(sqlite3_value_type(v) != SQLITE_TEXT) {
rc = SQLITE_ERROR;
vtab_set_error(&p->base, "Expected text for TEXT metadata column %.*s, received %s", metadata_column->name_length, metadata_column->name, type_name(sqlite3_value_type(v)));
goto done;
} break;
}
}
/** *@briefHandlesINSERTINTOoperationsonavec0table. * *@returnintSQLITE_OKonsuccess,otherwiseerrorcodeonfailure
*/ // Forward declaration: needed for INSERT OR REPLACE handling in vec0Update_Insert
int vec0Update_Delete(sqlite3_vtab *pVTab, sqlite3_value *idValue);
int vec0Update_Insert(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv,
sqlite_int64 *pRowid) {
UNUSED_PARAMETER(argc);
vec0_vtab *p = (vec0_vtab *)pVTab;
int rc; // Rowid for the inserted row, deterimined by the inserted ID + _rowids shadow // table
i64 rowid;
// Array to hold the vector data of the inserted row. Individual elements will // have a lifetime bound to the argv[..] values. void *vectorDatas[VEC0_MAX_VECTOR_COLUMNS]; // Array to hold cleanup functions for vectorDatas[]
vector_cleanup cleanups[VEC0_MAX_VECTOR_COLUMNS];
// Rowid of the chunk in the _chunks shadow table that the row will be a part // of.
i64 chunk_rowid; // offset within the chunk where the rowid belongs
i64 chunk_offset;
// a write-able blob of the validity column for the given chunk. Used to mark // validity bit
sqlite3_blob *blobChunksValidity = NULL; // buffer for the valididty column for the given chunk. Maybe not needed here? constunsigned char *bufferChunksValidity = NULL;
int numReadVectors = 0;
// Read all provided partition key values into partitionKeyValues
for (int i = 0; i < vec0_num_defined_user_columns(p); i++) {
if(p->user_column_kinds[i] != SQLITE_VEC0_USER_COLUMN_KIND_PARTITION) { continue;
}
int partition_key_idx = p->user_column_idxs[i];
partitionKeyValues[partition_key_idx] = argv[2+VEC0_COLUMN_USERN_START + i];
int new_value_type = sqlite3_value_type(partitionKeyValues[partition_key_idx]);
if((new_value_type != SQLITE_NULL) && (new_value_type != p->paritition_columns[partition_key_idx].type)) { // IMP: V11454_28292
vtab_set_error(
pVTab, "Parition key type mismatch: The partition key column %.*s has type %s, but %s was provided.",
p->paritition_columns[partition_key_idx].name_length,
p->paritition_columns[partition_key_idx].name,
type_name(p->paritition_columns[partition_key_idx].type),
type_name(new_value_type)
);
rc = SQLITE_ERROR;
goto cleanup;
}
}
// read all the inserted vectors into vectorDatas, validate their lengths.
for (int i = 0; i < vec0_num_defined_user_columns(p); i++) {
if(p->user_column_kinds[i] != SQLITE_VEC0_USER_COLUMN_KIND_VECTOR) { continue;
}
int vector_column_idx = p->user_column_idxs[i];
sqlite3_value *valueVector = argv[2 + VEC0_COLUMN_USERN_START + i];
size_t dimensions;
numReadVectors++;
if (elementType != p->vector_columns[vector_column_idx].element_type) { // IMP: V08221_25059
vtab_set_error(
pVTab, "Inserted vector for the \"%.*s\" column is expected to be of type " "%s, but a %s vector was provided.",
p->vector_columns[i].name_length, p->vector_columns[i].name,
vector_subtype_name(p->vector_columns[i].element_type),
vector_subtype_name(elementType));
rc = SQLITE_ERROR;
goto cleanup;
}
if (dimensions != p->vector_columns[vector_column_idx].dimensions) { // IMP: V01145_17984
vtab_set_error(
pVTab, "Dimension mismatch for inserted vector for the \"%.*s\" column. " "Expected %d dimensions but received %d.",
p->vector_columns[vector_column_idx].name_length, p->vector_columns[vector_column_idx].name,
p->vector_columns[vector_column_idx].dimensions, dimensions);
rc = SQLITE_ERROR;
goto cleanup;
}
}
// Cannot insert a value in the hidden "distance" column
if (sqlite3_value_type(argv[2 + vec0_column_distance_idx(p)]) !=
SQLITE_NULL) { // IMP: V24228_08298
vtab_set_error(pVTab, "A value was provided for the hidden \"distance\" column.");
rc = SQLITE_ERROR;
goto cleanup;
} // Cannot insert a value in the hidden "k" column
if (sqlite3_value_type(argv[2 + vec0_column_k_idx(p)]) != SQLITE_NULL) { // IMP: V11875_28713
vtab_set_error(pVTab, "A value was provided for the hidden \"k\" column.");
rc = SQLITE_ERROR;
goto cleanup;
}
// Handle INSERT OR REPLACE: if the conflict resolution is REPLACE and the // row already exists, delete the existing row first before inserting.
if (sqlite3_vtab_on_conflict(p->db) == SQLITE_REPLACE) {
sqlite3_value *idValue = argv[2 + VEC0_COLUMN_ID];
int idType = sqlite3_value_type(idValue);
int existingRowExists = 0;
if (existingRowExists) {
rc = vec0Update_Delete(pVTab, idValue);
if (rc != SQLITE_OK) {
goto cleanup;
}
}
}
// Step #1: Insert/get a rowid for this row, from the _rowids table.
rc = vec0Update_InsertRowidStep(p, argv[2 + VEC0_COLUMN_ID], &rowid);
if (rc != SQLITE_OK) {
goto cleanup;
}
if (!vec0_all_columns_diskann(p)) { // Step #2: Find the next "available" position in the _chunks table for this // row.
rc = vec0Update_InsertNextAvailableStep(p, partitionKeyValues,
&chunk_rowid, &chunk_offset,
&blobChunksValidity,
&bufferChunksValidity);
if (rc != SQLITE_OK) {
goto cleanup;
}
// Step #3: With the next available chunk position, write out all the vectors // to their specified location.
rc = vec0Update_InsertWriteFinalStep(p, chunk_rowid, chunk_offset, rowid,
vectorDatas, blobChunksValidity,
bufferChunksValidity);
if (rc != SQLITE_OK) {
goto cleanup;
}
}
#if SQLITE_VEC_ENABLE_DISKANN // Step #4: Insert into DiskANN graph for indexed vector columns
for (int i = 0; i < p->numVectorColumns; i++) {
if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_DISKANN) continue;
rc = diskann_insert(p, i, rowid, vectorDatas[i]);
if (rc != SQLITE_OK) {
goto cleanup;
}
} #endif
for (int i = 0; i < vec0_num_defined_user_columns(p); i++) {
if(p->user_column_kinds[i] != SQLITE_VEC0_USER_COLUMN_KIND_AUXILIARY) { continue;
}
int auxiliary_key_idx = p->user_column_idxs[i];
sqlite3_value * v = argv[2+VEC0_COLUMN_USERN_START + i];
int v_type = sqlite3_value_type(v);
if(v_type != SQLITE_NULL && (v_type != p->auxiliary_columns[auxiliary_key_idx].type)) {
sqlite3_finalize(stmt);
rc = SQLITE_CONSTRAINT;
vtab_set_error(
pVTab, "Auxiliary column type mismatch: The auxiliary column %.*s has type %s, but %s was provided.",
p->auxiliary_columns[auxiliary_key_idx].name_length,
p->auxiliary_columns[auxiliary_key_idx].name,
type_name(p->auxiliary_columns[auxiliary_key_idx].type),
type_name(v_type)
);
goto cleanup;
} // first 1 is for 1-based indexing on sqlite3_bind_*, second 1 is to account for initial rowid parameter
sqlite3_bind_value(stmt, 1 + 1 + auxiliary_key_idx, v);
}
cleanup:
for (int i = 0; i < numReadVectors; i++) {
cleanups[i](vectorDatas[i]);
}
sqlite3_free((void *)bufferChunksValidity);
int brc = sqlite3_blob_close(blobChunksValidity);
if ((rc == SQLITE_OK) && (brc != SQLITE_OK)) {
vtab_set_error(&p->base,
VEC_INTERAL_ERROR "unknown error, blobChunksValidity could " "not be closed, please file an issue"); return brc;
} return rc;
}
int vec0Update_Delete_ClearValidity(vec0_vtab *p, i64 chunk_id,
u64 chunk_offset) {
int rc, brc;
sqlite3_blob *blobChunksValidity = NULL;
char unsigned bx;
int validityOffset = chunk_offset / CHAR_BIT;
// 2. ensure chunks.validity bit is 1, then set to 0
rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowChunksName, "validity",
chunk_id, 1, &blobChunksValidity);
if (rc != SQLITE_OK) { // IMP: V26002_10073
vtab_set_error(&p->base, "could not open validity blob for %s.%s.%lld",
p->schemaName, p->shadowChunksName, chunk_id); return SQLITE_ERROR;
} // will skip the sqlite3_blob_bytes(blobChunksValidity) check for now, // the read below would catch it
rc = sqlite3_blob_read(blobChunksValidity, &bx, sizeof(bx), validityOffset);
if (rc != SQLITE_OK) { // IMP: V21193_05263
vtab_set_error(
&p->base, "could not read validity blob for %s.%s.%lld at %d",
p->schemaName, p->shadowChunksName, chunk_id, validityOffset);
goto cleanup;
}
if (!(bx >> (chunk_offset % CHAR_BIT))) { // IMP: V21193_05263
rc = SQLITE_ERROR;
vtab_set_error(
&p->base, "vec0 deletion error: validity bit is not set for %s.%s.%lld at %d",
p->schemaName, p->shadowChunksName, chunk_id, validityOffset);
goto cleanup;
}
char unsigned mask = ~(1 << (chunk_offset % CHAR_BIT));
char result = bx & mask;
rc = sqlite3_blob_write(blobChunksValidity, &result, sizeof(bx),
validityOffset);
if (rc != SQLITE_OK) {
vtab_set_error(
&p->base, "could not write to validity blob for %s.%s.%lld at %d",
p->schemaName, p->shadowChunksName, chunk_id, validityOffset);
goto cleanup;
}
cleanup:
brc = sqlite3_blob_close(blobChunksValidity);
if (rc != SQLITE_OK) return rc;
if (brc != SQLITE_OK) {
vtab_set_error(&p->base, "vec0 deletion error: Error commiting validity blob " "transaction on %s.%s.%lld at %d",
p->schemaName, p->shadowChunksName, chunk_id,
validityOffset); return brc;
} return SQLITE_OK;
}
int vec0Update_Delete_ClearRowid(vec0_vtab *p, i64 chunk_id,
u64 chunk_offset) {
int rc, brc;
sqlite3_blob *blobChunksRowids = NULL;
i64 zero = 0;
rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowChunksName, "rowids",
chunk_id, 1, &blobChunksRowids);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base, "could not open rowids blob for %s.%s.%lld",
p->schemaName, p->shadowChunksName, chunk_id); return SQLITE_ERROR;
}
rc = sqlite3_blob_write(blobChunksRowids, &zero, sizeof(zero),
chunk_offset * sizeof(i64));
if (rc != SQLITE_OK) {
vtab_set_error(&p->base, "could not write to rowids blob for %s.%s.%lld at %llu",
p->schemaName, p->shadowChunksName, chunk_id, chunk_offset);
}
brc = sqlite3_blob_close(blobChunksRowids);
if (rc != SQLITE_OK) return rc;
if (brc != SQLITE_OK) {
vtab_set_error(&p->base, "vec0 deletion error: Error commiting rowids blob " "transaction on %s.%s.%lld at %llu",
p->schemaName, p->shadowChunksName, chunk_id, chunk_offset); return brc;
} return SQLITE_OK;
}
int vec0Update_Delete_ClearVectors(vec0_vtab *p, i64 chunk_id,
u64 chunk_offset) {
int rc, brc;
for (int i = 0; i < p->numVectorColumns; i++) { // Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks
if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) continue;
sqlite3_blob *blobVectors = NULL;
size_t n = vector_column_byte_size(p->vector_columns[i]);
rc = sqlite3_blob_open(p->db, p->schemaName,
p->shadowVectorChunksNames[i], "vectors",
chunk_id, 1, &blobVectors);
if (rc != SQLITE_OK) {
vtab_set_error(&p->base, "could not open vector blob for %s.%s.%lld column %d",
p->schemaName, p->shadowVectorChunksNames[i], chunk_id, i); return SQLITE_ERROR;
}
// Delete from each _metadatachunksNN
for (int i = 0; i < p->numMetadataColumns; i++) {
zSql = sqlite3_mprintf( "DELETE FROM " VEC0_SHADOW_METADATA_N_NAME " WHERE rowid = ?",
p->schemaName, p->tableName, i);
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, chunk_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return SQLITE_ERROR;
}
// Invalidate cached stmtLatestChunk so it gets re-prepared on next insert
if (p->stmtLatestChunk) {
sqlite3_finalize(p->stmtLatestChunk);
p->stmtLatestChunk = NULL;
}
*deleted = 1; return SQLITE_OK;
}
int vec0Update_Delete_DeleteRowids(vec0_vtab *p, i64 rowid) {
int rc;
sqlite3_stmt *stmt = NULL;
char *zSql =
sqlite3_mprintf("DELETE FROM " VEC0_SHADOW_ROWIDS_NAME " WHERE rowid = ?",
p->schemaName, p->tableName);
if (!zSql) { return SQLITE_NOMEM;
}
// 1. Find chunk position for given rowid // 2. Ensure that validity bit for position is 1, then set to 0 // 3. Zero out rowid in chunks.rowid // 4. Zero out vector data in all vector column chunks // 5. Delete value in _rowids table
#if SQLITE_VEC_ENABLE_DISKANN // DiskANN graph deletion for indexed columns
for (int i = 0; i < p->numVectorColumns; i++) {
if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_DISKANN) continue;
rc = diskann_delete(p, i, rowid);
if (rc != SQLITE_OK) { return rc;
}
} #endif
if (!vec0_all_columns_diskann(p)) { // 1. get chunk_id and chunk_offset from _rowids
rc = vec0_get_chunk_position(p, rowid, NULL, &chunk_id, &chunk_offset);
if (rc != SQLITE_OK) { return rc;
}
// 2. clear validity bit
rc = vec0Update_Delete_ClearValidity(p, chunk_id, chunk_offset);
if (rc != SQLITE_OK) { return rc;
}
// 3. zero out rowid in chunks.rowids
rc = vec0Update_Delete_ClearRowid(p, chunk_id, chunk_offset);
if (rc != SQLITE_OK) { return rc;
}
// 4. zero out any data in vector chunks tables
rc = vec0Update_Delete_ClearVectors(p, chunk_id, chunk_offset);
if (rc != SQLITE_OK) { return rc;
}
#if SQLITE_VEC_ENABLE_RESCORE // 4b. zero out quantized data in rescore chunk tables, delete from rescore vectors
rc = rescore_on_delete(p, chunk_id, chunk_offset, rowid);
if (rc != SQLITE_OK) { return rc;
} #endif
}
// 5. delete from _rowids table
rc = vec0Update_Delete_DeleteRowids(p, rowid);
if (rc != SQLITE_OK) { return rc;
}
// 7. delete metadata and reclaim chunk (only when using chunk-based storage)
if (!vec0_all_columns_diskann(p)) {
for(int i = 0; i < p->numMetadataColumns; i++) {
rc = vec0Update_Delete_ClearMetadata(p, i, rowid, chunk_id, chunk_offset);
if (rc != SQLITE_OK) { return rc;
}
}
// 8. reclaim chunk if fully empty
{
int chunkDeleted;
rc = vec0Update_Delete_DeleteChunkIfEmpty(p, chunk_id, &chunkDeleted);
if (rc != SQLITE_OK) { return rc;
}
}
}
#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE // 7. delete from IVF index
for (int i = 0; i < p->numVectorColumns; i++) {
if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_IVF) continue;
rc = ivf_delete(p, i, rowid);
if (rc != SQLITE_OK) return rc;
} #endif
int vec0Update_UpdateVectorColumn(vec0_vtab *p, i64 chunk_id, i64 chunk_offset,
int i, sqlite3_value *valueVector, i64 rowid) {
int rc; #if !SQLITE_VEC_ENABLE_RESCORE
UNUSED_PARAMETER(rowid); #endif
sqlite3_blob *blobVectors = NULL;
char *pzError;
size_t dimensions;
enum VectorElementType elementType; void *vector;
vector_cleanup cleanup = vector_cleanup_noop; // https://github.com/asg017/sqlite-vec/issues/53
rc = vector_from_value(valueVector, &vector, &dimensions, &elementType,
&cleanup, &pzError);
if (rc != SQLITE_OK) { // IMP: V15203_32042
vtab_set_error(
&p->base, "Updated vector for the \"%.*s\" column is invalid: %z",
p->vector_columns[i].name_length, p->vector_columns[i].name, pzError);
rc = SQLITE_ERROR;
goto cleanup;
}
if (elementType != p->vector_columns[i].element_type) { // IMP: V03643_20481
vtab_set_error(
&p->base, "Updated vector for the \"%.*s\" column is expected to be of type " "%s, but a %s vector was provided.",
p->vector_columns[i].name_length, p->vector_columns[i].name,
vector_subtype_name(p->vector_columns[i].element_type),
vector_subtype_name(elementType));
rc = SQLITE_ERROR;
goto cleanup;
}
if (dimensions != p->vector_columns[i].dimensions) { // IMP: V25739_09810
vtab_set_error(
&p->base, "Dimension mismatch for new updated vector for the \"%.*s\" column. " "Expected %d dimensions but received %d.",
p->vector_columns[i].name_length, p->vector_columns[i].name,
p->vector_columns[i].dimensions, dimensions);
rc = SQLITE_ERROR;
goto cleanup;
}
#if SQLITE_VEC_ENABLE_RESCORE
if (p->vector_columns[i].index_type == VEC0_INDEX_TYPE_RESCORE) { // For rescore columns, update _rescore_vectors and _rescore_chunks struct VectorColumnDefinition *col = &p->vector_columns[i];
size_t qsize = rescore_quantized_byte_size(col);
size_t fsize = vector_column_byte_size(*col);
// 5) iterate over all new vectors, update the vectors
for (int i = 0; i < vec0_num_defined_user_columns(p); i++) {
if(p->user_column_kinds[i] != SQLITE_VEC0_USER_COLUMN_KIND_VECTOR) { continue;
}
int vector_idx = p->user_column_idxs[i];
sqlite3_value *valueVector = argv[2 + VEC0_COLUMN_USERN_START + i]; // in vec0Column, we check sqlite3_vtab_nochange() on vector columns. // If the vector column isn't being changed, we return NULL; // That's not great, that means vector columns can never be NULLABLE // (bc we cant distinguish if an updated vector is truly NULL or nochange). // Also it means that if someone tries to run `UPDATE v SET X = NULL`, // we can't effectively detect and raise an error. // A better solution would be to use a custom result_type for "empty", // but subtypes don't appear to survive xColumn -> xUpdate, it's always 0. // So for now, we'll just use NULL and warn people to not SET X = NULL // in the docs.
if (sqlite3_value_type(valueVector) == SQLITE_NULL) { continue;
}
// Block vector UPDATE for index types that don't implement it — // the DiskANN graph / IVF lists would become stale.
{
enum Vec0IndexType idx_type = p->vector_columns[vector_idx].index_type; const char *idx_name = NULL;
if (idx_type == VEC0_INDEX_TYPE_DISKANN) idx_name = "DiskANN"; #if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE else if (idx_type == VEC0_INDEX_TYPE_IVF) idx_name = "IVF"; #endif
if (idx_name) {
vtab_set_error(
&p->base, "UPDATE on vector column \"%.*s\" is not supported for %s indexes.",
p->vector_columns[vector_idx].name_length,
p->vector_columns[vector_idx].name,
idx_name); return SQLITE_ERROR;
}
}
// Up to VEC0_MAX_METADATA_COLUMNS // TODO be smarter about this man "metadatachunks00", "metadatachunks01", "metadatachunks02", "metadatachunks03", "metadatachunks04", "metadatachunks05", "metadatachunks06", "metadatachunks07", "metadatachunks08", "metadatachunks09", "metadatachunks10", "metadatachunks11", "metadatachunks12", "metadatachunks13", "metadatachunks14", "metadatachunks15",
// Up to "metadatatext00", "metadatatext01", "metadatatext02", "metadatatext03", "metadatatext04", "metadatatext05", "metadatatext06", "metadatatext07", "metadatatext08", "metadatatext09", "metadatatext10", "metadatatext11", "metadatatext12", "metadatatext13", "metadatatext14", "metadatatext15",
};
for (size_t i = 0; i < sizeof(azName) / sizeof(azName[0]); i++) {
if (sqlite3_stricmp(zName, azName[i]) == 0) return1;
} //for(size_t i = 0; i < )"vector_chunks", "metadatachunks" return0;
}
static int vec0Begin(sqlite3_vtab *pVTab) {
UNUSED_PARAMETER(pVTab); return SQLITE_OK;
} static int vec0Sync(sqlite3_vtab *pVTab) {
vec0_free_resources((vec0_vtab *)pVTab); return SQLITE_OK;
} static int vec0Commit(sqlite3_vtab *pVTab) {
UNUSED_PARAMETER(pVTab); return SQLITE_OK;
} static int vec0Rollback(sqlite3_vtab *pVTab) {
UNUSED_PARAMETER(pVTab); return SQLITE_OK;
}
// Auxiliary shadow table (only if auxiliary columns exist)
if (p->numAuxiliaryColumns > 0) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_auxiliary\" RENAME TO \"%w_auxiliary\";",
p->schemaName, p->tableName, zNew);
}
// Per-vector-column shadow tables
for (int i = 0; i < p->numVectorColumns; i++) { // Non-FLAT columns (rescore, IVF, DiskANN) don't create _vector_chunks // (mirror the guard in vec0_init around VEC0_SHADOW_VECTOR_N_CREATE).
if (p->vector_columns[i].index_type == VEC0_INDEX_TYPE_FLAT) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_vector_chunks%02d\" RENAME TO \"%w_vector_chunks%02d\";",
p->schemaName, p->tableName, i, zNew, i);
}
#if SQLITE_VEC_ENABLE_RESCORE
if (p->shadowRescoreChunksNames[i]) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_rescore_chunks%02d\" RENAME TO \"%w_rescore_chunks%02d\";",
p->schemaName, p->tableName, i, zNew, i);
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_rescore_vectors%02d\" RENAME TO \"%w_rescore_vectors%02d\";",
p->schemaName, p->tableName, i, zNew, i);
} #endif
#if SQLITE_VEC_ENABLE_DISKANN
if (p->shadowVectorsNames[i]) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_vectors%02d\" RENAME TO \"%w_vectors%02d\";",
p->schemaName, p->tableName, i, zNew, i);
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_diskann_nodes%02d\" RENAME TO \"%w_diskann_nodes%02d\";",
p->schemaName, p->tableName, i, zNew, i);
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_diskann_buffer%02d\" RENAME TO \"%w_diskann_buffer%02d\";",
p->schemaName, p->tableName, i, zNew, i);
} #endif
}
#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE
for (int i = 0; i < p->numVectorColumns; i++) {
if (p->shadowIvfCellsNames[i]) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_ivf_centroids%02d\" RENAME TO \"%w_ivf_centroids%02d\";",
p->schemaName, p->tableName, i, zNew, i);
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_ivf_cells%02d\" RENAME TO \"%w_ivf_cells%02d\";",
p->schemaName, p->tableName, i, zNew, i);
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_ivf_rowid_map%02d\" RENAME TO \"%w_ivf_rowid_map%02d\";",
p->schemaName, p->tableName, i, zNew, i); // _ivf_vectors is only created when quantizer != none // (mirror ivf_create_shadow_tables in sqlite-vec-ivf.c).
if (p->vector_columns[i].ivf.quantizer != VEC0_IVF_QUANTIZER_NONE) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_ivf_vectors%02d\" RENAME TO \"%w_ivf_vectors%02d\";",
p->schemaName, p->tableName, i, zNew, i);
}
}
} #endif
// Per-metadata-column shadow tables
for (int i = 0; i < p->numMetadataColumns; i++) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_metadatachunks%02d\" RENAME TO \"%w_metadatachunks%02d\";",
p->schemaName, p->tableName, i, zNew, i);
if (p->metadata_columns[i].kind == VEC0_METADATA_COLUMN_KIND_TEXT) {
sqlite3_str_appendf(s, "ALTER TABLE \"%w\".\"%w_metadatatext%02d\" RENAME TO \"%w_metadatatext%02d\";",
p->schemaName, p->tableName, i, zNew, i);
}
}
char *zSql = sqlite3_str_finish(s);
if (!zSql) { 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.