diff --git a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c index 32f26762eb..caeb03816e 100644 --- a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c +++ b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c @@ -85673,8 +85673,9 @@ void blobSpotFree(BlobSpot *pBlobSpot); /* * Accessor for node binary format - * - v1 format is the following: - * [u64 nRowid] [u16 nEdges] [node vector] [edge vector] * nEdges [trash vector] * (nMaxEdges - nEdges) ([u64 legacyField] [u64 edgeId]) * nEdges + * - default format is the following: + * [u64 nRowid] [u16 nEdges] [6 byte padding] [node vector] [edge vector] * nEdges [trash vector] * (nMaxEdges - nEdges) ([u32 unused] [f32 distance] [u64 edgeId]) * nEdges + * Note, that 6 byte padding after nEdges required to align [node vector] by word boundary and avoid unaligned reads * Note, that node vector and edge vector can have different representations (and edge vector can be smaller in size than node vector) */ int nodeEdgesMaxCount(const DiskAnnIndex *pIndex); @@ -85713,9 +85714,11 @@ typedef u8 MetricType; /* * 1 - v1 version; node block format: [node meta] [node vector] [edge vectors] ... [ [u64 unused ] [u64 edge rowid] ] ... * 2 - v2 version; node block format: [node meta] [node vector] [edge vectors] ... [ [u32 unused] [f32 distance] [u64 edge rowid] ] ... + * 3 - v3 version; node meta aligned to 8-byte boundary (instead of having u64 + u16 size - we round it up to u64 + u64) */ #define VECTOR_FORMAT_V1 1 -#define VECTOR_FORMAT_DEFAULT 2 +#define VECTOR_FORMAT_V2 2 +#define VECTOR_FORMAT_DEFAULT 3 /* type of the vector index */ #define VECTOR_INDEX_TYPE_PARAM_ID 2 @@ -212727,8 +212730,6 @@ SQLITE_PRIVATE void sqlite3RegisterVectorFunctions(void){ */ #define DISKANN_BLOCK_SIZE_SHIFT 9 -#define VECTOR_NODE_METADATA_SIZE (sizeof(u64) + sizeof(u16)) -#define VECTOR_EDGE_METADATA_SIZE (sizeof(u64) + sizeof(u64)) typedef struct VectorPair VectorPair; typedef struct DiskAnnSearchCtx DiskAnnSearchCtx; @@ -212951,16 +212952,28 @@ void blobSpotFree(BlobSpot *pBlobSpot) { ** Layout specific utilities **************************************************************************/ -int nodeEdgeOverhead(int nEdgeVectorSize){ - return nEdgeVectorSize + VECTOR_EDGE_METADATA_SIZE; +int nodeMetadataSize(int nFormatVersion){ + if( nFormatVersion <= VECTOR_FORMAT_V2 ){ + return (sizeof(u64) + sizeof(u16)); + }else{ + return (sizeof(u64) + sizeof(u64)); + } +} + +int edgeMetadataSize(int nFormatVersion){ + return (sizeof(u64) + sizeof(u64)); +} + +int nodeEdgeOverhead(int nFormatVersion, int nEdgeVectorSize){ + return nEdgeVectorSize + edgeMetadataSize(nFormatVersion); } -int nodeOverhead(int nNodeVectorSize){ - return nNodeVectorSize + VECTOR_NODE_METADATA_SIZE; +int nodeOverhead(int nFormatVersion, int nNodeVectorSize){ + return nNodeVectorSize + nodeMetadataSize(nFormatVersion); } int nodeEdgesMaxCount(const DiskAnnIndex *pIndex){ - unsigned int nMaxEdges = (pIndex->nBlockSize - nodeOverhead(pIndex->nNodeVectorSize)) / nodeEdgeOverhead(pIndex->nEdgeVectorSize); + unsigned int nMaxEdges = (pIndex->nBlockSize - nodeOverhead(pIndex->nFormatVersion, pIndex->nNodeVectorSize)) / nodeEdgeOverhead(pIndex->nFormatVersion, pIndex->nEdgeVectorSize); assert( nMaxEdges > 0); return nMaxEdges; } @@ -212968,29 +212981,29 @@ int nodeEdgesMaxCount(const DiskAnnIndex *pIndex){ int nodeEdgesMetadataOffset(const DiskAnnIndex *pIndex){ unsigned int offset; unsigned int nMaxEdges = nodeEdgesMaxCount(pIndex); - offset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + nMaxEdges * pIndex->nEdgeVectorSize; + offset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + nMaxEdges * pIndex->nEdgeVectorSize; assert( offset <= pIndex->nBlockSize ); return offset; } void nodeBinInit(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, u64 nRowid, Vector *pVector){ - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); memset(pBlobSpot->pBuffer, 0, pBlobSpot->nBufferSize); writeLE64(pBlobSpot->pBuffer, nRowid); // neighbours count already zero after memset - no need to set it explicitly - vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE, pIndex->nNodeVectorSize); + vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion), pIndex->nNodeVectorSize); } void nodeBinVector(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot, Vector *pVector) { - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); - vectorInitStatic(pVector, pIndex->nNodeVectorType, pIndex->nVectorDims, pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE); + vectorInitStatic(pVector, pIndex->nNodeVectorType, pIndex->nVectorDims, pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion)); } u16 nodeBinEdges(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot) { - assert( VECTOR_NODE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); return readLE16(pBlobSpot->pBuffer + sizeof(u64)); } @@ -213000,20 +213013,20 @@ void nodeBinEdge(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot, int iEdg int offset = nodeEdgesMetadataOffset(pIndex); if( pRowid != NULL ){ - assert( offset + (iEdge + 1) * VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); - *pRowid = readLE64(pBlobSpot->pBuffer + offset + iEdge * VECTOR_EDGE_METADATA_SIZE + sizeof(u64)); + assert( offset + (iEdge + 1) * edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); + *pRowid = readLE64(pBlobSpot->pBuffer + offset + iEdge * edgeMetadataSize(pIndex->nFormatVersion) + sizeof(u64)); } if( pIndex->nFormatVersion != VECTOR_FORMAT_V1 && pDistance != NULL ){ - distance = readLE32(pBlobSpot->pBuffer + offset + iEdge * VECTOR_EDGE_METADATA_SIZE + sizeof(u32)); + distance = readLE32(pBlobSpot->pBuffer + offset + iEdge * edgeMetadataSize(pIndex->nFormatVersion) + sizeof(u32)); *pDistance = *((float*)&distance); } if( pVector != NULL ){ - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize < offset ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize < offset ); vectorInitStatic( pVector, pIndex->nEdgeVectorType, pIndex->nVectorDims, - pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize + pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize ); } } @@ -213050,11 +213063,11 @@ void nodeBinReplaceEdge(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, int iRe nEdges++; } - edgeVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iReplace * pIndex->nEdgeVectorSize; - edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iReplace * VECTOR_EDGE_METADATA_SIZE; + edgeVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iReplace * pIndex->nEdgeVectorSize; + edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iReplace * edgeMetadataSize(pIndex->nFormatVersion); assert( edgeVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); - assert( edgeMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( edgeMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + edgeVectorOffset, pIndex->nEdgeVectorSize); writeLE32(pBlobSpot->pBuffer + edgeMetaOffset + sizeof(u32), *((u32*)&distance)); @@ -213070,19 +213083,19 @@ void nodeBinDeleteEdge(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, int iDel assert( 0 <= iDelete && iDelete < nEdges ); - edgeVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iDelete * pIndex->nEdgeVectorSize; - lastVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + (nEdges - 1) * pIndex->nEdgeVectorSize; - edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iDelete * VECTOR_EDGE_METADATA_SIZE; - lastMetaOffset = nodeEdgesMetadataOffset(pIndex) + (nEdges - 1) * VECTOR_EDGE_METADATA_SIZE; + edgeVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iDelete * pIndex->nEdgeVectorSize; + lastVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + (nEdges - 1) * pIndex->nEdgeVectorSize; + edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iDelete * edgeMetadataSize(pIndex->nFormatVersion); + lastMetaOffset = nodeEdgesMetadataOffset(pIndex) + (nEdges - 1) * edgeMetadataSize(pIndex->nFormatVersion); assert( edgeVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); assert( lastVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); - assert( edgeMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); - assert( lastMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( edgeMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); + assert( lastMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); if( edgeVectorOffset < lastVectorOffset ){ memmove(pBlobSpot->pBuffer + edgeVectorOffset, pBlobSpot->pBuffer + lastVectorOffset, pIndex->nEdgeVectorSize); - memmove(pBlobSpot->pBuffer + edgeMetaOffset, pBlobSpot->pBuffer + lastMetaOffset, VECTOR_EDGE_METADATA_SIZE); + memmove(pBlobSpot->pBuffer + edgeMetaOffset, pBlobSpot->pBuffer + lastMetaOffset, edgeMetadataSize(pIndex->nFormatVersion)); } writeLE16(pBlobSpot->pBuffer + sizeof(u64), nEdges - 1); @@ -213168,9 +213181,9 @@ int diskAnnCreateIndex( if( maxNeighborsParam == 0 ){ // 3 D**(1/2) gives good recall values (90%+) // we also want to keep disk overhead at moderate level - 50x of the disk size increase is the current upper bound - maxNeighborsParam = MIN(3 * ((int)(sqrt(dims)) + 1), (50 * nodeOverhead(vectorDataSize(type, dims))) / nodeEdgeOverhead(vectorDataSize(neighbours, dims)) + 1); + maxNeighborsParam = MIN(3 * ((int)(sqrt(dims)) + 1), (50 * nodeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(type, dims))) / nodeEdgeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(neighbours, dims)) + 1); } - blockSizeBytes = nodeOverhead(vectorDataSize(type, dims)) + maxNeighborsParam * (u64)nodeEdgeOverhead(vectorDataSize(neighbours, dims)); + blockSizeBytes = nodeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(type, dims)) + maxNeighborsParam * (u64)nodeEdgeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(neighbours, dims)); if( blockSizeBytes > DISKANN_MAX_BLOCK_SZ ){ return SQLITE_ERROR; } diff --git a/libsql-ffi/bundled/bindings/bindgen.rs b/libsql-ffi/bundled/bindings/bindgen.rs index 9606aed509..9a8fa22ca2 100644 --- a/libsql-ffi/bundled/bindings/bindgen.rs +++ b/libsql-ffi/bundled/bindings/bindgen.rs @@ -940,7 +940,7 @@ extern "C" { extern "C" { pub fn sqlite3_vmprintf( arg1: *const ::std::os::raw::c_char, - arg2: va_list, + arg2: *mut __va_list_tag, ) -> *mut ::std::os::raw::c_char; } extern "C" { @@ -956,7 +956,7 @@ extern "C" { arg1: ::std::os::raw::c_int, arg2: *mut ::std::os::raw::c_char, arg3: *const ::std::os::raw::c_char, - arg4: va_list, + arg4: *mut __va_list_tag, ) -> *mut ::std::os::raw::c_char; } extern "C" { @@ -2503,7 +2503,7 @@ extern "C" { pub fn sqlite3_str_vappendf( arg1: *mut sqlite3_str, zFormat: *const ::std::os::raw::c_char, - arg2: va_list, + arg2: *mut __va_list_tag, ); } extern "C" { @@ -3570,4 +3570,12 @@ extern "C" { extern "C" { pub static sqlite3_wal_manager: libsql_wal_manager; } -pub type __builtin_va_list = *mut ::std::os::raw::c_char; +pub type __builtin_va_list = [__va_list_tag; 1usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __va_list_tag { + pub gp_offset: ::std::os::raw::c_uint, + pub fp_offset: ::std::os::raw::c_uint, + pub overflow_arg_area: *mut ::std::os::raw::c_void, + pub reg_save_area: *mut ::std::os::raw::c_void, +} diff --git a/libsql-ffi/bundled/src/sqlite3.c b/libsql-ffi/bundled/src/sqlite3.c index 32f26762eb..caeb03816e 100644 --- a/libsql-ffi/bundled/src/sqlite3.c +++ b/libsql-ffi/bundled/src/sqlite3.c @@ -85673,8 +85673,9 @@ void blobSpotFree(BlobSpot *pBlobSpot); /* * Accessor for node binary format - * - v1 format is the following: - * [u64 nRowid] [u16 nEdges] [node vector] [edge vector] * nEdges [trash vector] * (nMaxEdges - nEdges) ([u64 legacyField] [u64 edgeId]) * nEdges + * - default format is the following: + * [u64 nRowid] [u16 nEdges] [6 byte padding] [node vector] [edge vector] * nEdges [trash vector] * (nMaxEdges - nEdges) ([u32 unused] [f32 distance] [u64 edgeId]) * nEdges + * Note, that 6 byte padding after nEdges required to align [node vector] by word boundary and avoid unaligned reads * Note, that node vector and edge vector can have different representations (and edge vector can be smaller in size than node vector) */ int nodeEdgesMaxCount(const DiskAnnIndex *pIndex); @@ -85713,9 +85714,11 @@ typedef u8 MetricType; /* * 1 - v1 version; node block format: [node meta] [node vector] [edge vectors] ... [ [u64 unused ] [u64 edge rowid] ] ... * 2 - v2 version; node block format: [node meta] [node vector] [edge vectors] ... [ [u32 unused] [f32 distance] [u64 edge rowid] ] ... + * 3 - v3 version; node meta aligned to 8-byte boundary (instead of having u64 + u16 size - we round it up to u64 + u64) */ #define VECTOR_FORMAT_V1 1 -#define VECTOR_FORMAT_DEFAULT 2 +#define VECTOR_FORMAT_V2 2 +#define VECTOR_FORMAT_DEFAULT 3 /* type of the vector index */ #define VECTOR_INDEX_TYPE_PARAM_ID 2 @@ -212727,8 +212730,6 @@ SQLITE_PRIVATE void sqlite3RegisterVectorFunctions(void){ */ #define DISKANN_BLOCK_SIZE_SHIFT 9 -#define VECTOR_NODE_METADATA_SIZE (sizeof(u64) + sizeof(u16)) -#define VECTOR_EDGE_METADATA_SIZE (sizeof(u64) + sizeof(u64)) typedef struct VectorPair VectorPair; typedef struct DiskAnnSearchCtx DiskAnnSearchCtx; @@ -212951,16 +212952,28 @@ void blobSpotFree(BlobSpot *pBlobSpot) { ** Layout specific utilities **************************************************************************/ -int nodeEdgeOverhead(int nEdgeVectorSize){ - return nEdgeVectorSize + VECTOR_EDGE_METADATA_SIZE; +int nodeMetadataSize(int nFormatVersion){ + if( nFormatVersion <= VECTOR_FORMAT_V2 ){ + return (sizeof(u64) + sizeof(u16)); + }else{ + return (sizeof(u64) + sizeof(u64)); + } +} + +int edgeMetadataSize(int nFormatVersion){ + return (sizeof(u64) + sizeof(u64)); +} + +int nodeEdgeOverhead(int nFormatVersion, int nEdgeVectorSize){ + return nEdgeVectorSize + edgeMetadataSize(nFormatVersion); } -int nodeOverhead(int nNodeVectorSize){ - return nNodeVectorSize + VECTOR_NODE_METADATA_SIZE; +int nodeOverhead(int nFormatVersion, int nNodeVectorSize){ + return nNodeVectorSize + nodeMetadataSize(nFormatVersion); } int nodeEdgesMaxCount(const DiskAnnIndex *pIndex){ - unsigned int nMaxEdges = (pIndex->nBlockSize - nodeOverhead(pIndex->nNodeVectorSize)) / nodeEdgeOverhead(pIndex->nEdgeVectorSize); + unsigned int nMaxEdges = (pIndex->nBlockSize - nodeOverhead(pIndex->nFormatVersion, pIndex->nNodeVectorSize)) / nodeEdgeOverhead(pIndex->nFormatVersion, pIndex->nEdgeVectorSize); assert( nMaxEdges > 0); return nMaxEdges; } @@ -212968,29 +212981,29 @@ int nodeEdgesMaxCount(const DiskAnnIndex *pIndex){ int nodeEdgesMetadataOffset(const DiskAnnIndex *pIndex){ unsigned int offset; unsigned int nMaxEdges = nodeEdgesMaxCount(pIndex); - offset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + nMaxEdges * pIndex->nEdgeVectorSize; + offset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + nMaxEdges * pIndex->nEdgeVectorSize; assert( offset <= pIndex->nBlockSize ); return offset; } void nodeBinInit(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, u64 nRowid, Vector *pVector){ - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); memset(pBlobSpot->pBuffer, 0, pBlobSpot->nBufferSize); writeLE64(pBlobSpot->pBuffer, nRowid); // neighbours count already zero after memset - no need to set it explicitly - vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE, pIndex->nNodeVectorSize); + vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion), pIndex->nNodeVectorSize); } void nodeBinVector(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot, Vector *pVector) { - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); - vectorInitStatic(pVector, pIndex->nNodeVectorType, pIndex->nVectorDims, pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE); + vectorInitStatic(pVector, pIndex->nNodeVectorType, pIndex->nVectorDims, pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion)); } u16 nodeBinEdges(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot) { - assert( VECTOR_NODE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); return readLE16(pBlobSpot->pBuffer + sizeof(u64)); } @@ -213000,20 +213013,20 @@ void nodeBinEdge(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot, int iEdg int offset = nodeEdgesMetadataOffset(pIndex); if( pRowid != NULL ){ - assert( offset + (iEdge + 1) * VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); - *pRowid = readLE64(pBlobSpot->pBuffer + offset + iEdge * VECTOR_EDGE_METADATA_SIZE + sizeof(u64)); + assert( offset + (iEdge + 1) * edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); + *pRowid = readLE64(pBlobSpot->pBuffer + offset + iEdge * edgeMetadataSize(pIndex->nFormatVersion) + sizeof(u64)); } if( pIndex->nFormatVersion != VECTOR_FORMAT_V1 && pDistance != NULL ){ - distance = readLE32(pBlobSpot->pBuffer + offset + iEdge * VECTOR_EDGE_METADATA_SIZE + sizeof(u32)); + distance = readLE32(pBlobSpot->pBuffer + offset + iEdge * edgeMetadataSize(pIndex->nFormatVersion) + sizeof(u32)); *pDistance = *((float*)&distance); } if( pVector != NULL ){ - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize < offset ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize < offset ); vectorInitStatic( pVector, pIndex->nEdgeVectorType, pIndex->nVectorDims, - pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize + pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize ); } } @@ -213050,11 +213063,11 @@ void nodeBinReplaceEdge(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, int iRe nEdges++; } - edgeVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iReplace * pIndex->nEdgeVectorSize; - edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iReplace * VECTOR_EDGE_METADATA_SIZE; + edgeVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iReplace * pIndex->nEdgeVectorSize; + edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iReplace * edgeMetadataSize(pIndex->nFormatVersion); assert( edgeVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); - assert( edgeMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( edgeMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + edgeVectorOffset, pIndex->nEdgeVectorSize); writeLE32(pBlobSpot->pBuffer + edgeMetaOffset + sizeof(u32), *((u32*)&distance)); @@ -213070,19 +213083,19 @@ void nodeBinDeleteEdge(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, int iDel assert( 0 <= iDelete && iDelete < nEdges ); - edgeVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iDelete * pIndex->nEdgeVectorSize; - lastVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + (nEdges - 1) * pIndex->nEdgeVectorSize; - edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iDelete * VECTOR_EDGE_METADATA_SIZE; - lastMetaOffset = nodeEdgesMetadataOffset(pIndex) + (nEdges - 1) * VECTOR_EDGE_METADATA_SIZE; + edgeVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iDelete * pIndex->nEdgeVectorSize; + lastVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + (nEdges - 1) * pIndex->nEdgeVectorSize; + edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iDelete * edgeMetadataSize(pIndex->nFormatVersion); + lastMetaOffset = nodeEdgesMetadataOffset(pIndex) + (nEdges - 1) * edgeMetadataSize(pIndex->nFormatVersion); assert( edgeVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); assert( lastVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); - assert( edgeMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); - assert( lastMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( edgeMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); + assert( lastMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); if( edgeVectorOffset < lastVectorOffset ){ memmove(pBlobSpot->pBuffer + edgeVectorOffset, pBlobSpot->pBuffer + lastVectorOffset, pIndex->nEdgeVectorSize); - memmove(pBlobSpot->pBuffer + edgeMetaOffset, pBlobSpot->pBuffer + lastMetaOffset, VECTOR_EDGE_METADATA_SIZE); + memmove(pBlobSpot->pBuffer + edgeMetaOffset, pBlobSpot->pBuffer + lastMetaOffset, edgeMetadataSize(pIndex->nFormatVersion)); } writeLE16(pBlobSpot->pBuffer + sizeof(u64), nEdges - 1); @@ -213168,9 +213181,9 @@ int diskAnnCreateIndex( if( maxNeighborsParam == 0 ){ // 3 D**(1/2) gives good recall values (90%+) // we also want to keep disk overhead at moderate level - 50x of the disk size increase is the current upper bound - maxNeighborsParam = MIN(3 * ((int)(sqrt(dims)) + 1), (50 * nodeOverhead(vectorDataSize(type, dims))) / nodeEdgeOverhead(vectorDataSize(neighbours, dims)) + 1); + maxNeighborsParam = MIN(3 * ((int)(sqrt(dims)) + 1), (50 * nodeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(type, dims))) / nodeEdgeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(neighbours, dims)) + 1); } - blockSizeBytes = nodeOverhead(vectorDataSize(type, dims)) + maxNeighborsParam * (u64)nodeEdgeOverhead(vectorDataSize(neighbours, dims)); + blockSizeBytes = nodeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(type, dims)) + maxNeighborsParam * (u64)nodeEdgeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(neighbours, dims)); if( blockSizeBytes > DISKANN_MAX_BLOCK_SZ ){ return SQLITE_ERROR; } diff --git a/libsql-sqlite3/.gitignore b/libsql-sqlite3/.gitignore index cdc89aec26..9ca28ced7a 100644 --- a/libsql-sqlite3/.gitignore +++ b/libsql-sqlite3/.gitignore @@ -41,7 +41,10 @@ libsql.pc /sqlite3session.h tclsqlite.lo tclsqlite.o -testdir/ + +testdir/* +!testdir/libsql_vector_index_v2.db + tsrc/ dbhash fuzzcheck diff --git a/libsql-sqlite3/src/vectorIndexInt.h b/libsql-sqlite3/src/vectorIndexInt.h index 1f3ef8c22c..dfc1990097 100644 --- a/libsql-sqlite3/src/vectorIndexInt.h +++ b/libsql-sqlite3/src/vectorIndexInt.h @@ -64,8 +64,9 @@ void blobSpotFree(BlobSpot *pBlobSpot); /* * Accessor for node binary format - * - v1 format is the following: - * [u64 nRowid] [u16 nEdges] [node vector] [edge vector] * nEdges [trash vector] * (nMaxEdges - nEdges) ([u64 legacyField] [u64 edgeId]) * nEdges + * - default format is the following: + * [u64 nRowid] [u16 nEdges] [6 byte padding] [node vector] [edge vector] * nEdges [trash vector] * (nMaxEdges - nEdges) ([u32 unused] [f32 distance] [u64 edgeId]) * nEdges + * Note, that 6 byte padding after nEdges required to align [node vector] by word boundary and avoid unaligned reads * Note, that node vector and edge vector can have different representations (and edge vector can be smaller in size than node vector) */ int nodeEdgesMaxCount(const DiskAnnIndex *pIndex); @@ -104,9 +105,11 @@ typedef u8 MetricType; /* * 1 - v1 version; node block format: [node meta] [node vector] [edge vectors] ... [ [u64 unused ] [u64 edge rowid] ] ... * 2 - v2 version; node block format: [node meta] [node vector] [edge vectors] ... [ [u32 unused] [f32 distance] [u64 edge rowid] ] ... + * 3 - v3 version; node meta aligned to 8-byte boundary (instead of having u64 + u16 size - we round it up to u64 + u64) */ #define VECTOR_FORMAT_V1 1 -#define VECTOR_FORMAT_DEFAULT 2 +#define VECTOR_FORMAT_V2 2 +#define VECTOR_FORMAT_DEFAULT 3 /* type of the vector index */ #define VECTOR_INDEX_TYPE_PARAM_ID 2 diff --git a/libsql-sqlite3/src/vectordiskann.c b/libsql-sqlite3/src/vectordiskann.c index 2585883492..8913c1d2c9 100644 --- a/libsql-sqlite3/src/vectordiskann.c +++ b/libsql-sqlite3/src/vectordiskann.c @@ -76,8 +76,6 @@ */ #define DISKANN_BLOCK_SIZE_SHIFT 9 -#define VECTOR_NODE_METADATA_SIZE (sizeof(u64) + sizeof(u16)) -#define VECTOR_EDGE_METADATA_SIZE (sizeof(u64) + sizeof(u64)) typedef struct VectorPair VectorPair; typedef struct DiskAnnSearchCtx DiskAnnSearchCtx; @@ -300,16 +298,28 @@ void blobSpotFree(BlobSpot *pBlobSpot) { ** Layout specific utilities **************************************************************************/ -int nodeEdgeOverhead(int nEdgeVectorSize){ - return nEdgeVectorSize + VECTOR_EDGE_METADATA_SIZE; +int nodeMetadataSize(int nFormatVersion){ + if( nFormatVersion <= VECTOR_FORMAT_V2 ){ + return (sizeof(u64) + sizeof(u16)); + }else{ + return (sizeof(u64) + sizeof(u64)); + } +} + +int edgeMetadataSize(int nFormatVersion){ + return (sizeof(u64) + sizeof(u64)); +} + +int nodeEdgeOverhead(int nFormatVersion, int nEdgeVectorSize){ + return nEdgeVectorSize + edgeMetadataSize(nFormatVersion); } -int nodeOverhead(int nNodeVectorSize){ - return nNodeVectorSize + VECTOR_NODE_METADATA_SIZE; +int nodeOverhead(int nFormatVersion, int nNodeVectorSize){ + return nNodeVectorSize + nodeMetadataSize(nFormatVersion); } int nodeEdgesMaxCount(const DiskAnnIndex *pIndex){ - unsigned int nMaxEdges = (pIndex->nBlockSize - nodeOverhead(pIndex->nNodeVectorSize)) / nodeEdgeOverhead(pIndex->nEdgeVectorSize); + unsigned int nMaxEdges = (pIndex->nBlockSize - nodeOverhead(pIndex->nFormatVersion, pIndex->nNodeVectorSize)) / nodeEdgeOverhead(pIndex->nFormatVersion, pIndex->nEdgeVectorSize); assert( nMaxEdges > 0); return nMaxEdges; } @@ -317,29 +327,29 @@ int nodeEdgesMaxCount(const DiskAnnIndex *pIndex){ int nodeEdgesMetadataOffset(const DiskAnnIndex *pIndex){ unsigned int offset; unsigned int nMaxEdges = nodeEdgesMaxCount(pIndex); - offset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + nMaxEdges * pIndex->nEdgeVectorSize; + offset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + nMaxEdges * pIndex->nEdgeVectorSize; assert( offset <= pIndex->nBlockSize ); return offset; } void nodeBinInit(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, u64 nRowid, Vector *pVector){ - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); memset(pBlobSpot->pBuffer, 0, pBlobSpot->nBufferSize); writeLE64(pBlobSpot->pBuffer, nRowid); // neighbours count already zero after memset - no need to set it explicitly - vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE, pIndex->nNodeVectorSize); + vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion), pIndex->nNodeVectorSize); } void nodeBinVector(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot, Vector *pVector) { - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize <= pBlobSpot->nBufferSize ); - vectorInitStatic(pVector, pIndex->nNodeVectorType, pIndex->nVectorDims, pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE); + vectorInitStatic(pVector, pIndex->nNodeVectorType, pIndex->nVectorDims, pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion)); } u16 nodeBinEdges(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot) { - assert( VECTOR_NODE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( nodeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); return readLE16(pBlobSpot->pBuffer + sizeof(u64)); } @@ -349,20 +359,20 @@ void nodeBinEdge(const DiskAnnIndex *pIndex, const BlobSpot *pBlobSpot, int iEdg int offset = nodeEdgesMetadataOffset(pIndex); if( pRowid != NULL ){ - assert( offset + (iEdge + 1) * VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); - *pRowid = readLE64(pBlobSpot->pBuffer + offset + iEdge * VECTOR_EDGE_METADATA_SIZE + sizeof(u64)); + assert( offset + (iEdge + 1) * edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); + *pRowid = readLE64(pBlobSpot->pBuffer + offset + iEdge * edgeMetadataSize(pIndex->nFormatVersion) + sizeof(u64)); } if( pIndex->nFormatVersion != VECTOR_FORMAT_V1 && pDistance != NULL ){ - distance = readLE32(pBlobSpot->pBuffer + offset + iEdge * VECTOR_EDGE_METADATA_SIZE + sizeof(u32)); + distance = readLE32(pBlobSpot->pBuffer + offset + iEdge * edgeMetadataSize(pIndex->nFormatVersion) + sizeof(u32)); *pDistance = *((float*)&distance); } if( pVector != NULL ){ - assert( VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize < offset ); + assert( nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize < offset ); vectorInitStatic( pVector, pIndex->nEdgeVectorType, pIndex->nVectorDims, - pBlobSpot->pBuffer + VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize + pBlobSpot->pBuffer + nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iEdge * pIndex->nEdgeVectorSize ); } } @@ -399,11 +409,11 @@ void nodeBinReplaceEdge(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, int iRe nEdges++; } - edgeVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iReplace * pIndex->nEdgeVectorSize; - edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iReplace * VECTOR_EDGE_METADATA_SIZE; + edgeVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iReplace * pIndex->nEdgeVectorSize; + edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iReplace * edgeMetadataSize(pIndex->nFormatVersion); assert( edgeVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); - assert( edgeMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( edgeMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); vectorSerializeToBlob(pVector, pBlobSpot->pBuffer + edgeVectorOffset, pIndex->nEdgeVectorSize); writeLE32(pBlobSpot->pBuffer + edgeMetaOffset + sizeof(u32), *((u32*)&distance)); @@ -419,19 +429,19 @@ void nodeBinDeleteEdge(const DiskAnnIndex *pIndex, BlobSpot *pBlobSpot, int iDel assert( 0 <= iDelete && iDelete < nEdges ); - edgeVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + iDelete * pIndex->nEdgeVectorSize; - lastVectorOffset = VECTOR_NODE_METADATA_SIZE + pIndex->nNodeVectorSize + (nEdges - 1) * pIndex->nEdgeVectorSize; - edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iDelete * VECTOR_EDGE_METADATA_SIZE; - lastMetaOffset = nodeEdgesMetadataOffset(pIndex) + (nEdges - 1) * VECTOR_EDGE_METADATA_SIZE; + edgeVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + iDelete * pIndex->nEdgeVectorSize; + lastVectorOffset = nodeMetadataSize(pIndex->nFormatVersion) + pIndex->nNodeVectorSize + (nEdges - 1) * pIndex->nEdgeVectorSize; + edgeMetaOffset = nodeEdgesMetadataOffset(pIndex) + iDelete * edgeMetadataSize(pIndex->nFormatVersion); + lastMetaOffset = nodeEdgesMetadataOffset(pIndex) + (nEdges - 1) * edgeMetadataSize(pIndex->nFormatVersion); assert( edgeVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); assert( lastVectorOffset + pIndex->nEdgeVectorSize <= pBlobSpot->nBufferSize ); - assert( edgeMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); - assert( lastMetaOffset + VECTOR_EDGE_METADATA_SIZE <= pBlobSpot->nBufferSize ); + assert( edgeMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); + assert( lastMetaOffset + edgeMetadataSize(pIndex->nFormatVersion) <= pBlobSpot->nBufferSize ); if( edgeVectorOffset < lastVectorOffset ){ memmove(pBlobSpot->pBuffer + edgeVectorOffset, pBlobSpot->pBuffer + lastVectorOffset, pIndex->nEdgeVectorSize); - memmove(pBlobSpot->pBuffer + edgeMetaOffset, pBlobSpot->pBuffer + lastMetaOffset, VECTOR_EDGE_METADATA_SIZE); + memmove(pBlobSpot->pBuffer + edgeMetaOffset, pBlobSpot->pBuffer + lastMetaOffset, edgeMetadataSize(pIndex->nFormatVersion)); } writeLE16(pBlobSpot->pBuffer + sizeof(u64), nEdges - 1); @@ -517,9 +527,9 @@ int diskAnnCreateIndex( if( maxNeighborsParam == 0 ){ // 3 D**(1/2) gives good recall values (90%+) // we also want to keep disk overhead at moderate level - 50x of the disk size increase is the current upper bound - maxNeighborsParam = MIN(3 * ((int)(sqrt(dims)) + 1), (50 * nodeOverhead(vectorDataSize(type, dims))) / nodeEdgeOverhead(vectorDataSize(neighbours, dims)) + 1); + maxNeighborsParam = MIN(3 * ((int)(sqrt(dims)) + 1), (50 * nodeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(type, dims))) / nodeEdgeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(neighbours, dims)) + 1); } - blockSizeBytes = nodeOverhead(vectorDataSize(type, dims)) + maxNeighborsParam * (u64)nodeEdgeOverhead(vectorDataSize(neighbours, dims)); + blockSizeBytes = nodeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(type, dims)) + maxNeighborsParam * (u64)nodeEdgeOverhead(VECTOR_FORMAT_DEFAULT, vectorDataSize(neighbours, dims)); if( blockSizeBytes > DISKANN_MAX_BLOCK_SZ ){ return SQLITE_ERROR; } diff --git a/libsql-sqlite3/test/libsql_vector_index.test b/libsql-sqlite3/test/libsql_vector_index.test index c7c7a9dfa8..1ce629bd2b 100644 --- a/libsql-sqlite3/test/libsql_vector_index.test +++ b/libsql-sqlite3/test/libsql_vector_index.test @@ -531,3 +531,37 @@ do_execsql_test vector-index-attach-query { ATTACH DATABASE 'test2.db' AS t; SELECT * FROM vector_top_k('t.t_attach_idx', vector('[3,4,5]'), 4); } {3 4 2 1} + +# protect binary file with test in order to make it more explicit when someone change it (expclitily or not; with malicious intent or not) +do_test vector-index-v2-anti-hacker-check { + md5file libsql_vector_index_v2.db +} {dcd33c78fc2bc53bb8e60236b418711a} + +# v2.db created with simple command (with LibSQL built from commit 4a5f373a298cc751d9ca56662eaaf879eefff279): +# ./sqlite3 v2.db --cmd "CREATE TABLE t (id TEXT, e FLOAT32(4)); CREATE INDEX t_idx ON t (libsql_vector_idx(e)); INSERT INTO t VALUES ('a', vector32('[1,2,3,4]')), ('b', vector32('[-100,-100,-100,-100]')), ('c', vector32('[10,10,-10,-10]')), ('d', vector32('[-1,2,3,4]'))" +sqlite3 dbv2 libsql_vector_index_v2.db + +# select version and check that it equals to 2 +do_test vector-index-v2-read { + execsql { SELECT substr(hex(metadata), 0, 18) FROM libsql_vector_meta_shadow WHERE name = 't_idx'; } dbv2 +} {01020000000000000} + +do_test vector-index-v2-read { + execsql { SELECT vector_extract(e) FROM t; } dbv2 +} {{[1,2,3,4]} {[-100,-100,-100,-100]} {[10,10,-10,-10]} {[-1,2,3,4]}} + +do_test vector-index-v2-query-1 { + execsql { SELECT t.id FROM vector_top_k('t_idx', vector('[1,1,1,1]'), 3) i INNER JOIN t ON t.rowid = i.id; } dbv2 +} {a d c} + +do_test vector-index-v2-query-2 { + execsql { SELECT t.id FROM vector_top_k('t_idx', vector('[-1,-1,-1,-1]'), 3) i INNER JOIN t ON t.rowid = i.id; } dbv2 +} {b c d} + +do_test vector-index-v2-query-3 { + execsql { SELECT t.id FROM vector_top_k('t_idx', vector('[1,1,-1,-1]'), 3) i INNER JOIN t ON t.rowid = i.id; } dbv2 +} {c b a} + +do_test vector-index-v2-query-4 { + execsql { SELECT t.id FROM vector_top_k('t_idx', vector('[-1,1,1,1]'), 3) i INNER JOIN t ON t.rowid = i.id; } dbv2 +} {d a c} diff --git a/libsql-sqlite3/testdir/libsql_vector_index_v2.db b/libsql-sqlite3/testdir/libsql_vector_index_v2.db new file mode 100644 index 0000000000..e64078ed87 Binary files /dev/null and b/libsql-sqlite3/testdir/libsql_vector_index_v2.db differ