From 85cf4153972c2848831655bafd57cb5e03c5b20d Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 12:57:06 -0700 Subject: [PATCH 01/24] Remove dead typedef macros and harmful u_int*_t redefinitions The UINT32_TYPE/UINT16_TYPE/INT16_TYPE/UINT8_TYPE/INT8_TYPE/LONGDOUBLE_TYPE macros (copied from sqlite3.c) were never used anywhere in sqlite-vec. The u_int8_t/u_int16_t/u_int64_t typedefs redefined standard types using BSD-only types despite already being included, breaking builds on musl/Alpine, strict C99, and requiring reactive platform guards. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 51 --------------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index abdafe0..e4cfbc1 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -22,61 +22,10 @@ SQLITE_EXTENSION_INIT1 #include "sqlite3.h" #endif -#ifndef UINT32_TYPE -#ifdef HAVE_UINT32_T -#define UINT32_TYPE uint32_t -#else -#define UINT32_TYPE unsigned int -#endif -#endif -#ifndef UINT16_TYPE -#ifdef HAVE_UINT16_T -#define UINT16_TYPE uint16_t -#else -#define UINT16_TYPE unsigned short int -#endif -#endif -#ifndef INT16_TYPE -#ifdef HAVE_INT16_T -#define INT16_TYPE int16_t -#else -#define INT16_TYPE short int -#endif -#endif -#ifndef UINT8_TYPE -#ifdef HAVE_UINT8_T -#define UINT8_TYPE uint8_t -#else -#define UINT8_TYPE unsigned char -#endif -#endif -#ifndef INT8_TYPE -#ifdef HAVE_INT8_T -#define INT8_TYPE int8_t -#else -#define INT8_TYPE signed char -#endif -#endif -#ifndef LONGDOUBLE_TYPE -#define LONGDOUBLE_TYPE long double -#endif - #ifndef SQLITE_VEC_ENABLE_DISKANN #define SQLITE_VEC_ENABLE_DISKANN 1 #endif -#ifndef _WIN32 -#ifndef __EMSCRIPTEN__ -#ifndef __COSMOPOLITAN__ -#ifndef __wasi__ -typedef u_int8_t uint8_t; -typedef u_int16_t uint16_t; -typedef u_int64_t uint64_t; -#endif -#endif -#endif -#endif - typedef int8_t i8; typedef uint8_t u8; typedef int16_t i16; From 3cfc2e0c1f08ed8120547b61c316afa0ee97b837 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 13:14:18 -0700 Subject: [PATCH 02/24] Fix broken unzip -d line in vendor.sh Remove stray incomplete `unzip -d` command that would error on CI. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/vendor.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/vendor.sh b/scripts/vendor.sh index 0706aa5..033ea1e 100755 --- a/scripts/vendor.sh +++ b/scripts/vendor.sh @@ -1,7 +1,6 @@ #!/bin/bash mkdir -p vendor curl -o sqlite-amalgamation.zip https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip -unzip -d unzip sqlite-amalgamation.zip mv sqlite-amalgamation-3450300/* vendor/ rmdir sqlite-amalgamation-3450300 From 07f56e3cbe28cac5b6046da53dd0fffbbc7109a1 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 13:51:08 -0700 Subject: [PATCH 03/24] Fix #if SQLITE_VEC_ENABLE_RESCORE guards wrapping non-rescore logic Six sites used #if SQLITE_VEC_ENABLE_RESCORE to guard _vector_chunks skip logic that applies to ALL non-flat index types. When RESCORE was compiled out, DiskANN and IVF columns would incorrectly access flat chunk tables. Two sites also missed DiskANN in the skip enumeration, which would break mixed flat+DiskANN tables. Fix: replace all six compile-time guards with unconditional runtime `!= VEC0_INDEX_TYPE_FLAT` checks. Also move rescore_on_delete inside the !vec0_all_columns_diskann guard to prevent use of uninitialized chunk_id/chunk_offset, and initialize those variables to 0. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 51 ++++++++++++++++----------------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index e4cfbc1..db79d0b 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -4625,16 +4625,10 @@ int vec0_new_chunk(vec0_vtab *p, sqlite3_value ** partitionKeyValues, i64 *chunk } int vector_column_idx = p->user_column_idxs[i]; -#if SQLITE_VEC_ENABLE_RESCORE - // Rescore and IVF columns don't use _vector_chunks for float storage - if (p->vector_columns[vector_column_idx].index_type == VEC0_INDEX_TYPE_RESCORE -#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE - || p->vector_columns[vector_column_idx].index_type == VEC0_INDEX_TYPE_IVF -#endif - ) { + // 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; } -#endif i64 vectorsSize = p->chunk_size * vector_column_byte_size(p->vector_columns[vector_column_idx]); @@ -5418,11 +5412,9 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_finalize(stmt); for (int i = 0; i < pNew->numVectorColumns; i++) { -#if SQLITE_VEC_ENABLE_RESCORE - // Non-FLAT columns don't use _vector_chunks + // Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks if (pNew->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) continue; -#endif char *zSql = sqlite3_mprintf(VEC0_SHADOW_VECTOR_N_CREATE, pNew->schemaName, pNew->tableName, i); if (!zSql) { @@ -5711,10 +5703,9 @@ static int vec0Destroy(sqlite3_vtab *pVtab) { continue; } #endif -#if SQLITE_VEC_ENABLE_RESCORE + // Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) continue; -#endif zSql = sqlite3_mprintf("DROP TABLE \"%w\".\"%w\"", p->schemaName, p->shadowVectorChunksNames[i]); rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, 0); @@ -8764,15 +8755,9 @@ int vec0Update_InsertWriteFinalStep(vec0_vtab *p, i64 chunk_rowid, // Go insert the vector data into the vector chunk shadow tables for (int i = 0; i < p->numVectorColumns; i++) { -#if SQLITE_VEC_ENABLE_RESCORE - // Rescore and IVF columns don't use _vector_chunks - if (p->vector_columns[i].index_type == VEC0_INDEX_TYPE_RESCORE -#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE - || p->vector_columns[i].index_type == VEC0_INDEX_TYPE_IVF -#endif - ) + // Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) continue; -#endif sqlite3_blob *blobVectors; rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowVectorChunksNames[i], @@ -9398,11 +9383,9 @@ int vec0Update_Delete_ClearVectors(vec0_vtab *p, i64 chunk_id, u64 chunk_offset) { int rc, brc; for (int i = 0; i < p->numVectorColumns; i++) { -#if SQLITE_VEC_ENABLE_RESCORE - // Non-FLAT columns don't use _vector_chunks + // Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) continue; -#endif sqlite3_blob *blobVectors = NULL; size_t n = vector_column_byte_size(p->vector_columns[i]); @@ -9514,10 +9497,9 @@ int vec0Update_Delete_DeleteChunkIfEmpty(vec0_vtab *p, i64 chunk_id, // Delete from each _vector_chunksNN for (int i = 0; i < p->numVectorColumns; i++) { -#if SQLITE_VEC_ENABLE_RESCORE + // Non-FLAT columns (rescore, IVF, DiskANN) don't use _vector_chunks if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) continue; -#endif zSql = sqlite3_mprintf( "DELETE FROM " VEC0_SHADOW_VECTOR_N_NAME " WHERE rowid = ?", p->schemaName, p->tableName, i); @@ -9711,8 +9693,8 @@ int vec0Update_Delete(sqlite3_vtab *pVTab, sqlite3_value *idValue) { vec0_vtab *p = (vec0_vtab *)pVTab; int rc; i64 rowid; - i64 chunk_id; - i64 chunk_offset; + i64 chunk_id = 0; + i64 chunk_offset = 0; if (p->pkIsText) { rc = vec0_rowid_from_id(p, idValue, &rowid); @@ -9764,16 +9746,15 @@ int vec0Update_Delete(sqlite3_vtab *pVTab, sqlite3_value *idValue) { 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; - } + // 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); From 9df59b4c03e2a882cb16609f825aa1e7726bce1d Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 14:08:08 -0700 Subject: [PATCH 04/24] Temporarily block vector UPDATE for DiskANN and IVF indexes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vec0Update_UpdateVectorColumn writes to flat chunk blobs but does not update DiskANN graph or IVF index structures, silently corrupting KNN results. Now returns a clear error for these index types. Rescore UPDATE is unaffected — it already has a full implementation that updates both quantized chunks and float vectors. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 20 ++++++++++++++++++++ tests/test-diskann.py | 15 +++------------ tests/test-ivf-mutations.py | 12 ++++++++++++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index db79d0b..53c4635 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -10055,6 +10055,26 @@ int vec0Update_Update(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv) { 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; + } + } + rc = vec0Update_UpdateVectorColumn(p, chunk_id, chunk_offset, vector_idx, valueVector, rowid); if (rc != SQLITE_OK) { diff --git a/tests/test-diskann.py b/tests/test-diskann.py index 4c049ce..4fad96b 100644 --- a/tests/test-diskann.py +++ b/tests/test-diskann.py @@ -891,7 +891,7 @@ def test_diskann_delete_preserves_graph_connectivity(db): # ====================================================================== def test_diskann_update_vector(db): - """UPDATE a vector on DiskANN table may not be supported; verify it either works or errors cleanly.""" + """UPDATE a vector on DiskANN table should error (will be implemented soon).""" db.execute(""" CREATE VIRTUAL TABLE t USING vec0( emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8) @@ -901,17 +901,8 @@ def test_diskann_update_vector(db): db.execute("INSERT INTO t(rowid, emb) VALUES (2, ?)", [_f32([0, 1, 0, 0, 0, 0, 0, 0])]) db.execute("INSERT INTO t(rowid, emb) VALUES (3, ?)", [_f32([0, 0, 1, 0, 0, 0, 0, 0])]) - # UPDATE may not be fully supported for DiskANN yet; verify no crash - result = exec(db, "UPDATE t SET emb = ? WHERE rowid = 1", [_f32([0, 0.9, 0.1, 0, 0, 0, 0, 0])]) - if "error" not in result: - # If UPDATE succeeded, verify KNN reflects the new value - rows = db.execute( - "SELECT rowid, distance FROM t WHERE emb MATCH ? AND k=3", - [_f32([0, 1, 0, 0, 0, 0, 0, 0])], - ).fetchall() - assert len(rows) == 3 - # rowid 2 should still be closest (exact match) - assert rows[0][0] == 2 + with pytest.raises(sqlite3.OperationalError, match="UPDATE on vector column.*not supported for DiskANN"): + db.execute("UPDATE t SET emb = ? WHERE rowid = 1", [_f32([0, 0.9, 0.1, 0, 0, 0, 0, 0])]) # ====================================================================== diff --git a/tests/test-ivf-mutations.py b/tests/test-ivf-mutations.py index 5c61119..fce1832 100644 --- a/tests/test-ivf-mutations.py +++ b/tests/test-ivf-mutations.py @@ -573,3 +573,15 @@ def test_interleaved_ops_correctness(db): # Verify we get the right count (25 odd + 15 new - 10 deleted new = 30) expected_alive = set(range(1, 50, 2)) | set(range(50, 60)) | set(range(70, 75)) assert rowids.issubset(expected_alive) + + +def test_ivf_update_vector_blocked(db): + """UPDATE on a vector column with IVF index should error (index would become stale).""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(emb float[4] indexed by ivf(nlist=2))" + ) + db.execute("INSERT INTO t(rowid, emb) VALUES (1, ?)", [_f32([1, 0, 0, 0])]) + db.execute("INSERT INTO t(rowid, emb) VALUES (2, ?)", [_f32([0, 1, 0, 0])]) + + with pytest.raises(sqlite3.OperationalError, match="UPDATE on vector column.*not supported for IVF"): + db.execute("UPDATE t SET emb = ? WHERE rowid = 1", [_f32([0, 0, 1, 0])]) From 82f4eb08bfe781b676041f6177af5a1fb2466ff7 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 14:31:49 -0700 Subject: [PATCH 05/24] Add NULL checks after sqlite3_column_blob in rescore and DiskANN sqlite3_column_blob() returns NULL for zero-length blobs or on OOM. Several call sites in rescore KNN and DiskANN node/vector read passed the result directly to memcpy without checking, risking NULL deref on corrupt or empty databases. IVF already had proper NULL checks. Adds corruption regression tests that truncate shadow table blobs and verify the query errors cleanly instead of crashing. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec-diskann.c | 20 ++++++++++++++++---- sqlite-vec-rescore.c | 4 ++++ tests/test-diskann.py | 27 +++++++++++++++++++++++++++ tests/test-rescore.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/sqlite-vec-diskann.c b/sqlite-vec-diskann.c index 1a5fd2b..7d4da6e 100644 --- a/sqlite-vec-diskann.c +++ b/sqlite-vec-diskann.c @@ -410,9 +410,18 @@ static int diskann_node_read(vec0_vtab *p, int vec_col_idx, i64 rowid, return SQLITE_NOMEM; } - memcpy(v, sqlite3_column_blob(stmt, 0), vs); - memcpy(ids, sqlite3_column_blob(stmt, 1), is); - memcpy(qv, sqlite3_column_blob(stmt, 2), qs); + const void *blobV = sqlite3_column_blob(stmt, 0); + const void *blobIds = sqlite3_column_blob(stmt, 1); + const void *blobQv = sqlite3_column_blob(stmt, 2); + if (!blobV || !blobIds || !blobQv) { + sqlite3_free(v); + sqlite3_free(ids); + sqlite3_free(qv); + return SQLITE_ERROR; + } + memcpy(v, blobV, vs); + memcpy(ids, blobIds, is); + memcpy(qv, blobQv, qs); *outValidity = v; *outValiditySize = vs; *outNeighborIds = ids; *outNeighborIdsSize = is; @@ -480,9 +489,11 @@ static int diskann_vector_read(vec0_vtab *p, int vec_col_idx, i64 rowid, } int sz = sqlite3_column_bytes(stmt, 0); + const void *blob = sqlite3_column_blob(stmt, 0); + if (!blob || sz == 0) return SQLITE_ERROR; void *vec = sqlite3_malloc(sz); if (!vec) return SQLITE_NOMEM; - memcpy(vec, sqlite3_column_blob(stmt, 0), sz); + memcpy(vec, blob, sz); *outVector = vec; *outVectorSize = sz; @@ -1325,6 +1336,7 @@ static int diskann_flush_buffer(vec0_vtab *p, int vec_col_idx) { while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { i64 rowid = sqlite3_column_int64(stmt, 0); const void *vector = sqlite3_column_blob(stmt, 1); + if (!vector) continue; // Note: vector is already written to _vectors table, so // diskann_insert_graph will skip re-writing it (vector already exists). // We call the graph-only insert path. diff --git a/sqlite-vec-rescore.c b/sqlite-vec-rescore.c index ef4e692..ef0a35c 100644 --- a/sqlite-vec-rescore.c +++ b/sqlite-vec-rescore.c @@ -426,6 +426,10 @@ static int rescore_knn(vec0_vtab *p, vec0_cursor *pCur, unsigned char *chunkValidity = (unsigned char *)sqlite3_column_blob(stmtChunks, 1); i64 *chunkRowids = (i64 *)sqlite3_column_blob(stmtChunks, 2); + if (!chunkValidity || !chunkRowids) { + rc = SQLITE_ERROR; + goto cleanup; + } memset(chunk_distances, 0, p->chunk_size * sizeof(f32)); memset(chunk_topk_idxs, 0, k_oversample * sizeof(i32)); diff --git a/tests/test-diskann.py b/tests/test-diskann.py index 4fad96b..d71769c 100644 --- a/tests/test-diskann.py +++ b/tests/test-diskann.py @@ -1149,3 +1149,30 @@ def test_diskann_large_batch_insert_500(db): distances = [r[1] for r in rows] for i in range(len(distances) - 1): assert distances[i] <= distances[i + 1] + + +def test_corrupt_truncated_node_blob(db): + """KNN should error (not crash) when DiskANN node blob is truncated.""" + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8) + ) + """) + for i in range(5): + vec = [0.0] * 8 + vec[i % 8] = 1.0 + db.execute("INSERT INTO t(rowid, emb) VALUES (?, ?)", [i + 1, _f32(vec)]) + + # Corrupt a DiskANN node: truncate neighbor_ids to 1 byte (wrong size) + db.execute( + "UPDATE t_diskann_nodes00 SET neighbor_ids = x'00' WHERE rowid = 1" + ) + + # Should not crash — may return wrong results or error + try: + db.execute( + "SELECT rowid FROM t WHERE emb MATCH ? AND k=3", + [_f32([1, 0, 0, 0, 0, 0, 0, 0])], + ).fetchall() + except sqlite3.OperationalError: + pass # Error is acceptable — crash is not diff --git a/tests/test-rescore.py b/tests/test-rescore.py index 5025857..1dc6cd7 100644 --- a/tests/test-rescore.py +++ b/tests/test-rescore.py @@ -566,3 +566,32 @@ def test_multiple_vector_columns(db): [float_vec([1.0] * 8)], ).fetchall() assert rows[0]["rowid"] == 2 + + +def test_corrupt_zeroblob_validity(db): + """KNN should error (not crash) when rescore chunk rowids blob is zeroed out.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([1, 0, 0, 0, 0, 0, 0, 0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([0, 1, 0, 0, 0, 0, 0, 0])], + ) + + # Corrupt: replace rowids with a truncated blob (wrong size) + db.execute("UPDATE t_chunks SET rowids = x'00'") + + # Should not crash — may return wrong results or error + try: + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1, 0, 0, 0, 0, 0, 0, 0])], + ).fetchall() + except sqlite3.OperationalError: + pass # Error is acceptable — crash is not From 5e4c557f931a5103e21ba6a29197a9e475927790 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 14:35:55 -0700 Subject: [PATCH 06/24] Initialize rescore distance variable to FLT_MAX The `dist` variable in rescore KNN quantized distance computation was uninitialized. If the switch on quantizer_type or distance_metric didn't match any case, the uninitialized value would propagate into the top-k heap, potentially returning garbage results. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec-rescore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlite-vec-rescore.c b/sqlite-vec-rescore.c index ef0a35c..1cf67bf 100644 --- a/sqlite-vec-rescore.c +++ b/sqlite-vec-rescore.c @@ -465,7 +465,7 @@ static int rescore_knn(vec0_vtab *p, vec0_cursor *pCur, for (int j = 0; j < p->chunk_size; j++) { if (!bitmap_get(b, j)) continue; - f32 dist; + f32 dist = FLT_MAX; switch (vector_column->rescore.quantizer_type) { case VEC0_RESCORE_QUANTIZER_BIT: { const u8 *base_j = ((u8 *)baseVectors) + (j * (qdim / CHAR_BIT)); From 4bee88384bf6339d4794da1e6644aaca29a5678f Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 14:51:27 -0700 Subject: [PATCH 07/24] Reject IVF binary quantizer when dimensions not divisible by 8 The binary quantizer uses D/8 for buffer sizes and memset, which truncates for non-multiple-of-8 dimensions, causing OOB writes. Rather than using ceiling division, enforce the constraint at table creation time with a clear parse error. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 3 +++ tests/test-ivf-quantization.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/sqlite-vec.c b/sqlite-vec.c index 53c4635..d12e25d 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -3074,6 +3074,9 @@ int vec0_parse_vector_column(const char *source, int source_length, if (rc != SQLITE_OK) { return SQLITE_ERROR; } + if (ivfConfig.quantizer == VEC0_IVF_QUANTIZER_BINARY && (dimensions % 8) != 0) { + return SQLITE_ERROR; + } #else return SQLITE_ERROR; // IVF not compiled in #endif diff --git a/tests/test-ivf-quantization.py b/tests/test-ivf-quantization.py index 9790680..b4d6ae3 100644 --- a/tests/test-ivf-quantization.py +++ b/tests/test-ivf-quantization.py @@ -253,3 +253,20 @@ def test_ivf_quantized_delete(db): db.execute("DELETE FROM t WHERE rowid = 5") # _ivf_vectors should have 9 rows assert db.execute("SELECT count(*) FROM t_ivf_vectors00").fetchone()[0] == 9 + + +def test_ivf_binary_rejects_non_multiple_of_8_dims(db): + """Binary quantizer requires dimensions divisible by 8.""" + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " v float[12] indexed by ivf(quantizer=binary)" + ")" + ) + + # Dimensions divisible by 8 should work + db.execute( + "CREATE VIRTUAL TABLE t2 USING vec0(" + " v float[16] indexed by ivf(quantizer=binary)" + ")" + ) From 7de925be70b9b2c3cc6e17ffa65eb5e688f12e67 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 14:55:37 -0700 Subject: [PATCH 08/24] Fix int16 overflow in l2_sqr_int8_neon SIMD distance vmulq_s16(diff, diff) produced int16 results, but diff can be up to 255 for int8 vectors (-128 vs 127), and 255^2 = 65025 overflows int16 (max 32767). This caused NaN/wrong results for int8 vectors with large differences. Fix: use vmull_s16 (widening multiply) to produce int32 results directly, avoiding the intermediate int16 overflow. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 11 +++++++---- tests/test-loadable.py | 8 +++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index d12e25d..5379f29 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -258,13 +258,16 @@ static f32 l2_sqr_int8_neon(const void *pVect1v, const void *pVect2v, pVect1 += 8; pVect2 += 8; - // widen to protect against overflow + // 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); - int16x8_t squared_diff = vmulq_s16(diff, diff); - int32x4_t sum = vpaddlq_s16(squared_diff); + + // 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); sum_scalar += vgetq_lane_s32(sum, 0) + vgetq_lane_s32(sum, 1) + vgetq_lane_s32(sum, 2) + vgetq_lane_s32(sum, 3); diff --git a/tests/test-loadable.py b/tests/test-loadable.py index 40c6a5e..1ac0cf3 100644 --- a/tests/test-loadable.py +++ b/tests/test-loadable.py @@ -381,11 +381,17 @@ def test_vec_distance_l2(): x = vec_distance_l2(a_sql_t, b_sql_t, a=transform, b=transform) y = npy_l2(np.array(a), np.array(b)) - assert isclose(x, y, abs_tol=1e-6) + assert isclose(x, y, rel_tol=1e-5, abs_tol=1e-6) check([1.2, 0.1], [0.4, -0.4]) check([-1.2, -0.1], [-0.4, 0.4]) check([1, 2, 3], [-9, -8, -7], dtype=np.int8) + # Extreme int8 values: diff=255, squared=65025 which overflows i16 + # This tests the NEON widening multiply fix (slight float rounding expected) + check([-128] * 8, [127] * 8, dtype=np.int8) + check([-128] * 16, [127] * 16, dtype=np.int8) + check([-128, 127, -128, 127, -128, 127, -128, 127], + [127, -128, 127, -128, 127, -128, 127, -128], dtype=np.int8) def test_vec_length(): From 2f4c2e4bdb9a0ef78ee2950ff746cdd45df3c2b1 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 14:57:01 -0700 Subject: [PATCH 09/24] Fix alignment UB in distance_hamming_u64 Casting unaligned blob pointers to u64* is undefined behavior on strict-alignment architectures. Use memcpy to safely load u64 values from potentially unaligned memory (compilers optimize this to native loads on architectures that support unaligned access). Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index 5379f29..f8ab4f9 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -734,10 +734,13 @@ static unsigned int __builtin_popcountl(unsigned int x) { #endif #endif -static f32 distance_hamming_u64(u64 *a, u64 *b, size_t n) { +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++) { - same += __builtin_popcountl(a[i] ^ b[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; } @@ -761,7 +764,7 @@ static f32 distance_hamming(const void *a, const void *b, const void *d) { #endif if ((dimensions % 64) == 0) { - return distance_hamming_u64((u64 *)a, (u64 *)b, n_bytes / sizeof(u64)); + return distance_hamming_u64((const u8 *)a, (const u8 *)b, n_bytes / sizeof(u64)); } return distance_hamming_u8((u8 *)a, (u8 *)b, n_bytes); } From b00865429b519ba6de0a2e094087052870f3d037 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 17:13:29 -0700 Subject: [PATCH 10/24] Filter deleted nodes from DiskANN search results and add delete tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DiskANN's delete repair only fixes forward edges (nodes the deleted node pointed to). Stale reverse edges can cause deleted rowids to appear in search results. Fix: track a 'confirmed' flag on each search candidate, set when the full-precision vector is successfully read during re-ranking. Only confirmed candidates are included in output. Zero additional SQL queries — piggybacks on the existing re-rank vector read. Also adds delete hardening tests: - Rescore: interleaved delete+KNN, rowid_in after deletes, full delete+reinsert cycle - DiskANN: delete+reinsert cycles with KNN verification, interleaved delete+KNN Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec-diskann.c | 27 ++++++--- sqlite-vec.c | 3 +- tests/test-diskann.py | 70 +++++++++++++++++++++++ tests/test-rescore-mutations.py | 98 +++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 8 deletions(-) diff --git a/sqlite-vec-diskann.c b/sqlite-vec-diskann.c index 7d4da6e..ab9db6a 100644 --- a/sqlite-vec-diskann.c +++ b/sqlite-vec-diskann.c @@ -608,6 +608,7 @@ static int diskann_candidate_list_insert( list->items[lo].rowid = rowid; list->items[lo].distance = distance; list->items[lo].visited = 0; + list->items[lo].confirmed = 0; list->count++; return 1; } @@ -741,8 +742,9 @@ static int diskann_search( return rc; } - // Seed with medoid + // Seed with medoid (confirmed — we already read its vector above) diskann_candidate_list_insert(&candidates, medoid, medoidDist); + candidates.items[0].confirmed = 1; // Pre-quantize query vector once for all quantized distance comparisons u8 *queryQuantized = NULL; @@ -815,16 +817,27 @@ static int diskann_search( sqlite3_free(fullVec); // Update distance in candidate list and re-sort diskann_candidate_list_insert(&candidates, currentRowid, exactDist); + // Mark as confirmed (vector exists, distance is exact) + for (int ci = 0; ci < candidates.count; ci++) { + if (candidates.items[ci].rowid == currentRowid) { + candidates.items[ci].confirmed = 1; + break; + } + } } + // If vector read failed, candidate stays unconfirmed (stale edge to deleted node) } - // 5. Output results (candidates are already sorted by distance) - int resultCount = (candidates.count < k) ? candidates.count : k; - *outCount = resultCount; - for (int i = 0; i < resultCount; i++) { - outRowids[i] = candidates.items[i].rowid; - outDistances[i] = candidates.items[i].distance; + // 5. Output results — only include confirmed candidates (whose vectors exist) + int resultCount = 0; + for (int i = 0; i < candidates.count && resultCount < k; i++) { + if (candidates.items[i].confirmed) { + outRowids[resultCount] = candidates.items[i].rowid; + outDistances[resultCount] = candidates.items[i].distance; + resultCount++; + } } + *outCount = resultCount; sqlite3_free(queryQuantized); diskann_candidate_list_free(&candidates); diff --git a/sqlite-vec.c b/sqlite-vec.c index f8ab4f9..cb597dd 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -2586,7 +2586,8 @@ struct Vec0DiskannConfig { struct Vec0DiskannCandidate { i64 rowid; f32 distance; - int visited; // 1 if this candidate's neighbors have been explored + int visited; // 1 if this candidate's neighbors have been explored + int confirmed; // 1 if full-precision vector was successfully read (node exists) }; /** diff --git a/tests/test-diskann.py b/tests/test-diskann.py index d71769c..f2a56a1 100644 --- a/tests/test-diskann.py +++ b/tests/test-diskann.py @@ -1176,3 +1176,73 @@ def test_corrupt_truncated_node_blob(db): ).fetchall() except sqlite3.OperationalError: pass # Error is acceptable — crash is not + + +def test_diskann_delete_reinsert_cycle_knn(db): + """Repeatedly delete and reinsert rows, verify KNN stays correct.""" + import random + random.seed(101) + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8) + ) + """) + N = 30 + vecs = {} + for i in range(1, N + 1): + v = [random.gauss(0, 1) for _ in range(8)] + vecs[i] = v + db.execute("INSERT INTO t(rowid, emb) VALUES (?, ?)", [i, _f32(v)]) + + # 3 cycles: delete half, reinsert with new vectors, verify KNN + for cycle in range(3): + to_delete = random.sample(sorted(vecs.keys()), len(vecs) // 2) + for r in to_delete: + db.execute("DELETE FROM t WHERE rowid = ?", [r]) + del vecs[r] + + # Reinsert with new rowids + new_start = 100 + cycle * 50 + for i in range(len(to_delete)): + rid = new_start + i + v = [random.gauss(0, 1) for _ in range(8)] + vecs[rid] = v + db.execute("INSERT INTO t(rowid, emb) VALUES (?, ?)", [rid, _f32(v)]) + + # KNN should return only alive rows + query = [0.0] * 8 + rows = db.execute( + "SELECT rowid FROM t WHERE emb MATCH ? AND k=10", + [_f32(query)], + ).fetchall() + returned = {r["rowid"] for r in rows} + assert returned.issubset(set(vecs.keys())), \ + f"Cycle {cycle}: deleted rowid in KNN results" + assert len(rows) >= 1 + + +def test_diskann_delete_interleaved_with_knn(db): + """Delete one row at a time, querying KNN after each delete.""" + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8) + ) + """) + N = 20 + for i in range(1, N + 1): + vec = [0.0] * 8 + vec[i % 8] = float(i) + db.execute("INSERT INTO t(rowid, emb) VALUES (?, ?)", [i, _f32(vec)]) + + alive = set(range(1, N + 1)) + for to_del in [1, 5, 10, 15, 20]: + db.execute("DELETE FROM t WHERE rowid = ?", [to_del]) + alive.discard(to_del) + + rows = db.execute( + "SELECT rowid FROM t WHERE emb MATCH ? AND k=5", + [_f32([1, 0, 0, 0, 0, 0, 0, 0])], + ).fetchall() + returned = {r["rowid"] for r in rows} + assert returned.issubset(alive), \ + f"Deleted rowid {to_del} found in KNN results" diff --git a/tests/test-rescore-mutations.py b/tests/test-rescore-mutations.py index 28495c2..dbb802a 100644 --- a/tests/test-rescore-mutations.py +++ b/tests/test-rescore-mutations.py @@ -443,6 +443,104 @@ def test_insert_batch_recall(db): # ============================================================================ +def test_delete_interleaved_with_knn(db): + """Delete rows one at a time, running KNN after each delete to verify correctness.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + N = 30 + random.seed(42) + vecs = {i: [random.gauss(0, 1) for _ in range(8)] for i in range(1, N + 1)} + for rowid, vec in vecs.items(): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [rowid, float_vec(vec)], + ) + + alive = set(vecs.keys()) + query = [0.0] * 8 + + for to_del in [5, 10, 15, 20, 25]: + db.execute("DELETE FROM t WHERE rowid = ?", [to_del]) + alive.discard(to_del) + + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [float_vec(query)], + ).fetchall() + returned = {r["rowid"] for r in rows} + # All returned rows must be alive (not deleted) + assert returned.issubset(alive), f"Deleted rowid found in KNN after deleting {to_del}" + # Count should match alive set (up to k) + assert len(rows) == min(10, len(alive)) + + +def test_delete_with_rowid_in_constraint(db): + """Delete rows and verify KNN with rowid_in filter excludes deleted rows.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + for i in range(1, 11): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i, float_vec([float(i)] * 8)], + ) + + # Delete rows 3, 5, 7 + for r in [3, 5, 7]: + db.execute("DELETE FROM t WHERE rowid = ?", [r]) + + # KNN with rowid IN (1,2,3,4,5) — should only return 1, 2, 4 (3 and 5 deleted) + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? AND k = 5 AND rowid IN (1, 2, 3, 4, 5)", + [float_vec([1.0] * 8)], + ).fetchall() + returned = {r["rowid"] for r in rows} + assert 3 not in returned + assert 5 not in returned + assert returned.issubset({1, 2, 4}) + + +def test_delete_all_then_reinsert_batch(db): + """Delete all rows, reinsert a new batch, verify KNN only returns new rows.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + # First batch + for i in range(1, 21): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i, float_vec([float(i)] * 8)], + ) + + # Delete all + for i in range(1, 21): + db.execute("DELETE FROM t WHERE rowid = ?", [i]) + + assert db.execute("SELECT count(*) FROM t").fetchone()[0] == 0 + + # Second batch with different rowids and vectors + for i in range(100, 110): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i, float_vec([float(i - 100)] * 8)], + ) + + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 5", + [float_vec([0.0] * 8)], + ).fetchall() + returned = {r["rowid"] for r in rows} + # All returned rowids should be from the second batch + assert returned.issubset(set(range(100, 110))) + + def test_knn_int8_cosine(db): """Rescore with quantizer=int8 and distance_metric=cosine.""" db.execute( From d033bf57283c6d389742c6806da3b662ea3ce6d5 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 17:13:40 -0700 Subject: [PATCH 11/24] Add delete recall benchmark suite New benchmarks-ann/bench-delete/ directory measures KNN recall degradation after random row deletion across index types (flat, rescore, IVF, DiskANN). For each config and delete percentage: builds index, measures baseline recall, copies DB, deletes random rows, measures post-delete recall, VACUUMs and records size savings. Includes Makefile targets, self-contained smoke test with synthetic data, and results DB for analysis. Co-Authored-By: Claude Opus 4.6 (1M context) --- benchmarks-ann/bench-delete/.gitignore | 3 + benchmarks-ann/bench-delete/Makefile | 41 ++ benchmarks-ann/bench-delete/README.md | 69 +++ benchmarks-ann/bench-delete/bench_delete.py | 593 ++++++++++++++++++++ benchmarks-ann/bench-delete/test_smoke.py | 124 ++++ 5 files changed, 830 insertions(+) create mode 100644 benchmarks-ann/bench-delete/.gitignore create mode 100644 benchmarks-ann/bench-delete/Makefile create mode 100644 benchmarks-ann/bench-delete/README.md create mode 100644 benchmarks-ann/bench-delete/bench_delete.py create mode 100644 benchmarks-ann/bench-delete/test_smoke.py diff --git a/benchmarks-ann/bench-delete/.gitignore b/benchmarks-ann/bench-delete/.gitignore new file mode 100644 index 0000000..0184df8 --- /dev/null +++ b/benchmarks-ann/bench-delete/.gitignore @@ -0,0 +1,3 @@ +runs/ +*.db +__pycache__/ diff --git a/benchmarks-ann/bench-delete/Makefile b/benchmarks-ann/bench-delete/Makefile new file mode 100644 index 0000000..681847b --- /dev/null +++ b/benchmarks-ann/bench-delete/Makefile @@ -0,0 +1,41 @@ +BENCH = python bench_delete.py +EXT = ../../dist/vec0 + +# --- Configs to test --- +FLAT = "flat:type=vec0-flat,variant=float" +RESCORE_BIT = "rescore-bit:type=rescore,quantizer=bit,oversample=8" +RESCORE_INT8 = "rescore-int8:type=rescore,quantizer=int8,oversample=8" +DISKANN_R48 = "diskann-R48:type=diskann,R=48,L=128,quantizer=binary" +DISKANN_R72 = "diskann-R72:type=diskann,R=72,L=128,quantizer=binary" + +ALL_CONFIGS = $(FLAT) $(RESCORE_BIT) $(RESCORE_INT8) $(DISKANN_R48) $(DISKANN_R72) + +DELETE_PCTS = 5,10,25,50,75,90 + +.PHONY: smoke bench-10k bench-50k bench-all report clean + +# Quick smoke test (small dataset, few queries) +smoke: + $(BENCH) --subset-size 5000 --delete-pct 10,50 -k 10 -n 20 \ + --dataset cohere1m --ext $(EXT) \ + $(FLAT) $(DISKANN_R48) + +# Standard benchmarks +bench-10k: + $(BENCH) --subset-size 10000 --delete-pct $(DELETE_PCTS) -k 10 -n 50 \ + --dataset cohere1m --ext $(EXT) $(ALL_CONFIGS) + +bench-50k: + $(BENCH) --subset-size 50000 --delete-pct $(DELETE_PCTS) -k 10 -n 50 \ + --dataset cohere1m --ext $(EXT) $(ALL_CONFIGS) + +bench-all: bench-10k bench-50k + +# Query saved results +report: + @echo "Query results:" + @echo " sqlite3 runs/cohere1m/10000/delete_results.db \\" + @echo " \"SELECT config_name, delete_pct, recall, query_mean_ms, vacuum_size_mb FROM delete_runs ORDER BY config_name, delete_pct\"" + +clean: + rm -rf runs/ diff --git a/benchmarks-ann/bench-delete/README.md b/benchmarks-ann/bench-delete/README.md new file mode 100644 index 0000000..8155566 --- /dev/null +++ b/benchmarks-ann/bench-delete/README.md @@ -0,0 +1,69 @@ +# bench-delete: Recall degradation after random deletion + +Measures how KNN recall changes after deleting a random percentage of rows +from different index types (flat, rescore, DiskANN). + +## Quick start + +```bash +# Ensure dataset exists +make -C ../datasets/cohere1m + +# Ensure extension is built +make -C ../.. loadable + +# Quick smoke test +make smoke + +# Full benchmark at 10k vectors +make bench-10k +``` + +## Usage + +```bash +python bench_delete.py --subset-size 10000 --delete-pct 10,25,50,75 \ + "flat:type=vec0-flat,variant=float" \ + "diskann-R72:type=diskann,R=72,L=128,quantizer=binary" \ + "rescore-bit:type=rescore,quantizer=bit,oversample=8" +``` + +## What it measures + +For each config and delete percentage: + +| Metric | Description | +|--------|-------------| +| **recall** | KNN recall@k after deletion (ground truth recomputed over surviving rows) | +| **delta** | Recall change vs 0% baseline | +| **query latency** | Mean/median query time after deletion | +| **db_size_mb** | DB file size before VACUUM | +| **vacuum_size_mb** | DB file size after VACUUM (space reclaimed) | +| **delete_time_s** | Wall time for the DELETE operations | + +## How it works + +1. Build index with N vectors (one copy per config) +2. Measure recall at k=10 (pre-delete baseline) +3. For each delete %: + - Copy the master DB + - Delete a random selection of rows (deterministic seed) + - Measure recall (ground truth recomputed over surviving rows only) + - VACUUM and measure size savings +4. Print comparison table + +## Expected behavior + +- **Flat index**: Recall should be 1.0 at all delete percentages (brute-force is always exact) +- **Rescore**: Recall should stay close to baseline (quantized scan + rescore is robust) +- **DiskANN**: Recall may degrade at high delete % due to graph fragmentation (dangling edges, broken connectivity) + +## Results DB + +Results are stored in `runs///delete_results.db`: + +```sql +SELECT config_name, delete_pct, recall, vacuum_size_mb +FROM delete_runs +ORDER BY config_name, delete_pct; +``` diff --git a/benchmarks-ann/bench-delete/bench_delete.py b/benchmarks-ann/bench-delete/bench_delete.py new file mode 100644 index 0000000..802f0a4 --- /dev/null +++ b/benchmarks-ann/bench-delete/bench_delete.py @@ -0,0 +1,593 @@ +#!/usr/bin/env python3 +"""Benchmark: measure recall degradation after random row deletion. + +Given a dataset and index config, this script: + 1. Builds the index (flat + ANN) + 2. Measures recall at k=10 (pre-delete baseline) + 3. Deletes a random % of rows + 4. Measures recall again (post-delete) + 5. Records DB size before/after deletion, recall delta, timings + +Usage: + python bench_delete.py --subset-size 10000 --delete-pct 25 \ + "diskann-R48:type=diskann,R=48,L=128,quantizer=binary" + + # Multiple delete percentages in one run: + python bench_delete.py --subset-size 10000 --delete-pct 10,25,50,75 \ + "diskann-R48:type=diskann,R=48,L=128,quantizer=binary" +""" +import argparse +import json +import os +import random +import shutil +import sqlite3 +import statistics +import struct +import time + +_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +_BENCH_DIR = os.path.join(_SCRIPT_DIR, "..") +_ROOT_DIR = os.path.join(_BENCH_DIR, "..") + +EXT_PATH = os.path.join(_ROOT_DIR, "dist", "vec0") +DATASETS_DIR = os.path.join(_BENCH_DIR, "datasets") + +DATASETS = { + "cohere1m": {"base_db": os.path.join(DATASETS_DIR, "cohere1m", "base.db"), "dimensions": 768}, + "cohere10m": {"base_db": os.path.join(DATASETS_DIR, "cohere10m", "base.db"), "dimensions": 768}, + "nyt": {"base_db": os.path.join(DATASETS_DIR, "nyt", "base.db"), "dimensions": 256}, + "nyt-768": {"base_db": os.path.join(DATASETS_DIR, "nyt-768", "base.db"), "dimensions": 768}, + "nyt-1024": {"base_db": os.path.join(DATASETS_DIR, "nyt-1024", "base.db"), "dimensions": 1024}, + "nyt-384": {"base_db": os.path.join(DATASETS_DIR, "nyt-384", "base.db"), "dimensions": 384}, +} + +INSERT_BATCH_SIZE = 1000 + + +# ============================================================================ +# Timing helpers +# ============================================================================ + +def now_ns(): + return time.time_ns() + +def ns_to_s(ns): + return ns / 1_000_000_000 + +def ns_to_ms(ns): + return ns / 1_000_000 + + +# ============================================================================ +# Index registry (subset of bench.py — only types relevant to deletion) +# ============================================================================ + +def _vec0_flat_create(p): + dims = p["dimensions"] + variant = p.get("variant", "float") + col = f"embedding float[{dims}]" + if variant == "int8": + col = f"embedding int8[{dims}]" + elif variant == "bit": + col = f"embedding bit[{dims}]" + return f"CREATE VIRTUAL TABLE vec_items USING vec0(id INTEGER PRIMARY KEY, {col})" + +def _rescore_create(p): + dims = p["dimensions"] + q = p.get("quantizer", "bit") + os_val = p.get("oversample", 8) + return ( + f"CREATE VIRTUAL TABLE vec_items USING vec0(" + f"id INTEGER PRIMARY KEY, " + f"embedding float[{dims}] indexed by rescore(quantizer={q}, oversample={os_val}))" + ) + +def _diskann_create(p): + dims = p["dimensions"] + R = p.get("R", 72) + L = p.get("L", 128) + q = p.get("quantizer", "binary") + bt = p.get("buffer_threshold", 0) + sl_insert = p.get("search_list_size_insert", 0) + sl_search = p.get("search_list_size_search", 0) + parts = [ + f"neighbor_quantizer={q}", + f"n_neighbors={R}", + f"buffer_threshold={bt}", + ] + if sl_insert or sl_search: + # Per-path overrides — don't also set search_list_size + if sl_insert: + parts.append(f"search_list_size_insert={sl_insert}") + if sl_search: + parts.append(f"search_list_size_search={sl_search}") + else: + parts.append(f"search_list_size={L}") + opts = ", ".join(parts) + return ( + f"CREATE VIRTUAL TABLE vec_items USING vec0(" + f"id INTEGER PRIMARY KEY, " + f"embedding float[{dims}] indexed by diskann({opts}))" + ) + +def _ivf_create(p): + dims = p["dimensions"] + nlist = p.get("nlist", 128) + nprobe = p.get("nprobe", 16) + q = p.get("quantizer", "none") + os_val = p.get("oversample", 1) + parts = [f"nlist={nlist}", f"nprobe={nprobe}"] + if q != "none": + parts.append(f"quantizer={q}") + if os_val > 1: + parts.append(f"oversample={os_val}") + opts = ", ".join(parts) + return ( + f"CREATE VIRTUAL TABLE vec_items USING vec0(" + f"id INTEGER PRIMARY KEY, " + f"embedding float[{dims}] indexed by ivf({opts}))" + ) + + +INDEX_REGISTRY = { + "vec0-flat": { + "defaults": {"variant": "float"}, + "create_table_sql": _vec0_flat_create, + "post_insert_hook": None, + }, + "rescore": { + "defaults": {"quantizer": "bit", "oversample": 8}, + "create_table_sql": _rescore_create, + "post_insert_hook": None, + }, + "ivf": { + "defaults": {"nlist": 128, "nprobe": 16, "quantizer": "none", + "oversample": 1}, + "create_table_sql": _ivf_create, + "post_insert_hook": lambda conn, params: _ivf_train(conn), + }, + "diskann": { + "defaults": {"R": 72, "L": 128, "quantizer": "binary", + "buffer_threshold": 0}, + "create_table_sql": _diskann_create, + "post_insert_hook": None, + }, +} + + +def _ivf_train(conn): + """Trigger built-in k-means training for IVF.""" + t0 = now_ns() + conn.execute("INSERT INTO vec_items(id) VALUES ('compute-centroids')") + conn.commit() + return ns_to_s(now_ns() - t0) + + +# ============================================================================ +# Config parsing (same format as bench.py) +# ============================================================================ + +INT_KEYS = {"R", "L", "oversample", "nlist", "nprobe", "buffer_threshold", + "search_list_size_insert", "search_list_size_search"} + +def parse_config(spec): + if ":" not in spec: + raise ValueError(f"Config must be 'name:key=val,...': {spec}") + name, rest = spec.split(":", 1) + params = {} + for kv in rest.split(","): + k, v = kv.split("=", 1) + k = k.strip() + v = v.strip() + if k in INT_KEYS: + v = int(v) + params[k] = v + index_type = params.pop("type", None) + if not index_type or index_type not in INDEX_REGISTRY: + raise ValueError(f"Unknown index type: {index_type}") + params["index_type"] = index_type + merged = dict(INDEX_REGISTRY[index_type]["defaults"]) + merged.update(params) + return name, merged + + +# ============================================================================ +# DB helpers +# ============================================================================ + +def create_bench_db(db_path, ext_path, base_db, page_size=4096): + if os.path.exists(db_path): + os.remove(db_path) + conn = sqlite3.connect(db_path) + conn.execute(f"PRAGMA page_size={page_size}") + conn.execute("PRAGMA journal_mode=WAL") + conn.enable_load_extension(True) + conn.load_extension(ext_path) + conn.execute(f"ATTACH DATABASE '{base_db}' AS base") + return conn + + +def load_query_vectors(base_db, n): + conn = sqlite3.connect(base_db) + rows = conn.execute( + "SELECT id, vector FROM query_vectors LIMIT ?", (n,) + ).fetchall() + conn.close() + return rows + + +def insert_loop(conn, subset_size, label, start_from=0): + insert_sql = ( + "INSERT INTO vec_items(id, embedding) " + "SELECT id, vector FROM base.train " + "WHERE id >= :lo AND id < :hi" + ) + total = 0 + for lo in range(start_from, subset_size, INSERT_BATCH_SIZE): + hi = min(lo + INSERT_BATCH_SIZE, subset_size) + conn.execute(insert_sql, {"lo": lo, "hi": hi}) + conn.commit() + total += hi - lo + if total % 5000 == 0 or total == subset_size - start_from: + print(f" [{label}] inserted {total + start_from}/{subset_size}", flush=True) + + +# ============================================================================ +# Recall measurement +# ============================================================================ + +def measure_recall(conn, base_db, query_vectors, subset_size, k, alive_ids=None): + """Measure KNN recall. If alive_ids is provided, ground truth is computed + only over those IDs (to match post-delete state).""" + recalls = [] + times_ms = [] + + for qid, query in query_vectors: + t0 = now_ns() + results = conn.execute( + "SELECT id, distance FROM vec_items " + "WHERE embedding MATCH :query AND k = :k", + {"query": query, "k": k}, + ).fetchall() + t1 = now_ns() + times_ms.append(ns_to_ms(t1 - t0)) + + result_ids = set(r[0] for r in results) + + # Ground truth: brute-force cosine over surviving rows + if alive_ids is not None: + # After deletion — compute GT only over alive IDs + # Use a temp table for the alive set for efficiency + gt_rows = conn.execute( + "SELECT id FROM (" + " SELECT id, vec_distance_l2(vector, :query) as dist " + " FROM base.train WHERE id < :n ORDER BY dist LIMIT :k2" + ")", + {"query": query, "k2": k * 5, "n": subset_size}, + ).fetchall() + # Filter to only alive IDs, take top k + gt_alive = [r[0] for r in gt_rows if r[0] in alive_ids][:k] + gt_ids = set(gt_alive) + else: + gt_rows = conn.execute( + "SELECT id FROM (" + " SELECT id, vec_distance_l2(vector, :query) as dist " + " FROM base.train WHERE id < :n ORDER BY dist LIMIT :k" + ")", + {"query": query, "k": k, "n": subset_size}, + ).fetchall() + gt_ids = set(r[0] for r in gt_rows) + + if gt_ids: + recalls.append(len(result_ids & gt_ids) / len(gt_ids)) + else: + recalls.append(0.0) + + return { + "recall": round(statistics.mean(recalls), 4) if recalls else 0.0, + "mean_ms": round(statistics.mean(times_ms), 2) if times_ms else 0.0, + "median_ms": round(statistics.median(times_ms), 2) if times_ms else 0.0, + } + + +# ============================================================================ +# Delete benchmark core +# ============================================================================ + +def run_delete_benchmark(name, params, base_db, ext_path, subset_size, dims, + delete_pcts, k, n_queries, out_dir, seed_val): + params["dimensions"] = dims + reg = INDEX_REGISTRY[params["index_type"]] + create_sql = reg["create_table_sql"](params) + + results = [] + + # Build once, copy for each delete % + print(f"\n{'='*60}") + print(f"Config: {name} (type={params['index_type']})") + print(f"{'='*60}") + + os.makedirs(out_dir, exist_ok=True) + master_db_path = os.path.join(out_dir, f"{name}.{subset_size}.db") + print(f" Building index ({subset_size} vectors)...") + build_t0 = now_ns() + conn = create_bench_db(master_db_path, ext_path, base_db) + conn.execute(create_sql) + insert_loop(conn, subset_size, name) + hook = reg.get("post_insert_hook") + if hook: + print(f" Training...") + hook(conn, params) + conn.close() + build_time_s = ns_to_s(now_ns() - build_t0) + master_size = os.path.getsize(master_db_path) + print(f" Built in {build_time_s:.1f}s ({master_size / (1024*1024):.1f} MB)") + + # Load query vectors once + query_vectors = load_query_vectors(base_db, n_queries) + + # Measure pre-delete baseline on the master copy + print(f"\n --- 0% deleted (baseline) ---") + conn = sqlite3.connect(master_db_path) + conn.enable_load_extension(True) + conn.load_extension(ext_path) + conn.execute(f"ATTACH DATABASE '{base_db}' AS base") + baseline = measure_recall(conn, base_db, query_vectors, subset_size, k) + conn.close() + print(f" recall={baseline['recall']:.4f} " + f"query={baseline['mean_ms']:.2f}ms") + + results.append({ + "name": name, + "index_type": params["index_type"], + "subset_size": subset_size, + "delete_pct": 0, + "n_deleted": 0, + "n_remaining": subset_size, + "recall": baseline["recall"], + "query_mean_ms": baseline["mean_ms"], + "query_median_ms": baseline["median_ms"], + "db_size_mb": round(master_size / (1024 * 1024), 2), + "build_time_s": round(build_time_s, 1), + "delete_time_s": 0.0, + "vacuum_size_mb": round(master_size / (1024 * 1024), 2), + }) + + # All IDs in the dataset + all_ids = list(range(subset_size)) + + for pct in sorted(delete_pcts): + n_delete = int(subset_size * pct / 100) + print(f"\n --- {pct}% deleted ({n_delete} rows) ---") + + # Copy master DB and work on the copy + copy_path = os.path.join(out_dir, f"{name}.{subset_size}.del{pct}.db") + shutil.copy2(master_db_path, copy_path) + # Also copy WAL/SHM if they exist + for suffix in ["-wal", "-shm"]: + src = master_db_path + suffix + if os.path.exists(src): + shutil.copy2(src, copy_path + suffix) + + conn = sqlite3.connect(copy_path) + conn.enable_load_extension(True) + conn.load_extension(ext_path) + conn.execute(f"ATTACH DATABASE '{base_db}' AS base") + + # Pick random IDs to delete (deterministic per pct) + rng = random.Random(seed_val + pct) + to_delete = set(rng.sample(all_ids, n_delete)) + alive_ids = set(all_ids) - to_delete + + # Delete + delete_t0 = now_ns() + batch = [] + for i, rid in enumerate(to_delete): + batch.append(rid) + if len(batch) >= 500 or i == len(to_delete) - 1: + placeholders = ",".join("?" for _ in batch) + conn.execute( + f"DELETE FROM vec_items WHERE id IN ({placeholders})", + batch, + ) + conn.commit() + batch = [] + delete_time_s = ns_to_s(now_ns() - delete_t0) + + remaining = conn.execute("SELECT count(*) FROM vec_items").fetchone()[0] + pre_vacuum_size = os.path.getsize(copy_path) + print(f" deleted {n_delete} rows in {delete_time_s:.2f}s " + f"({remaining} remaining)") + + # Measure post-delete recall + post = measure_recall(conn, base_db, query_vectors, subset_size, k, + alive_ids=alive_ids) + print(f" recall={post['recall']:.4f} " + f"(delta={post['recall'] - baseline['recall']:+.4f}) " + f"query={post['mean_ms']:.2f}ms") + + # VACUUM and measure size savings — close fully, reopen without base + conn.close() + vconn = sqlite3.connect(copy_path) + vconn.execute("VACUUM") + vconn.close() + post_vacuum_size = os.path.getsize(copy_path) + saved_mb = (pre_vacuum_size - post_vacuum_size) / (1024 * 1024) + print(f" size: {pre_vacuum_size/(1024*1024):.1f} MB -> " + f"{post_vacuum_size/(1024*1024):.1f} MB after VACUUM " + f"(saved {saved_mb:.1f} MB)") + + results.append({ + "name": name, + "index_type": params["index_type"], + "subset_size": subset_size, + "delete_pct": pct, + "n_deleted": n_delete, + "n_remaining": remaining, + "recall": post["recall"], + "query_mean_ms": post["mean_ms"], + "query_median_ms": post["median_ms"], + "db_size_mb": round(pre_vacuum_size / (1024 * 1024), 2), + "build_time_s": round(build_time_s, 1), + "delete_time_s": round(delete_time_s, 2), + "vacuum_size_mb": round(post_vacuum_size / (1024 * 1024), 2), + }) + + return results + + +# ============================================================================ +# Results DB +# ============================================================================ + +RESULTS_SCHEMA = """\ +CREATE TABLE IF NOT EXISTS delete_runs ( + run_id INTEGER PRIMARY KEY, + config_name TEXT NOT NULL, + index_type TEXT NOT NULL, + params TEXT, + dataset TEXT NOT NULL, + subset_size INTEGER NOT NULL, + delete_pct INTEGER NOT NULL, + n_deleted INTEGER NOT NULL, + n_remaining INTEGER NOT NULL, + k INTEGER NOT NULL, + n_queries INTEGER NOT NULL, + seed INTEGER NOT NULL, + recall REAL, + query_mean_ms REAL, + query_median_ms REAL, + db_size_mb REAL, + vacuum_size_mb REAL, + build_time_s REAL, + delete_time_s REAL, + created_at TEXT DEFAULT (datetime('now')) +); +""" + +def save_results(results, out_dir, dataset, subset_size, params_json, k, n_queries, seed_val): + db_path = os.path.join(out_dir, "delete_results.db") + db = sqlite3.connect(db_path) + db.execute("PRAGMA journal_mode=WAL") + db.executescript(RESULTS_SCHEMA) + for r in results: + db.execute( + "INSERT INTO delete_runs " + "(config_name, index_type, params, dataset, subset_size, " + " delete_pct, n_deleted, n_remaining, k, n_queries, seed, " + " recall, query_mean_ms, query_median_ms, " + " db_size_mb, vacuum_size_mb, build_time_s, delete_time_s) " + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + ( + r["name"], r["index_type"], params_json, dataset, r["subset_size"], + r["delete_pct"], r["n_deleted"], r["n_remaining"], k, n_queries, seed_val, + r["recall"], r["query_mean_ms"], r["query_median_ms"], + r["db_size_mb"], r["vacuum_size_mb"], r["build_time_s"], r["delete_time_s"], + ), + ) + db.commit() + db.close() + return db_path + + +# ============================================================================ +# Reporting +# ============================================================================ + +def print_report(all_results): + print(f"\n{'name':>22} {'del%':>5} {'deleted':>8} {'remain':>8} " + f"{'recall':>7} {'delta':>7} {'qry(ms)':>8} " + f"{'size(MB)':>9} {'vacuumed':>9} {'del(s)':>7}") + print("-" * 110) + + # Group by config name + configs = {} + for r in all_results: + configs.setdefault(r["name"], []).append(r) + + for name, rows in configs.items(): + baseline_recall = rows[0]["recall"] # 0% delete is always first + for r in rows: + delta = r["recall"] - baseline_recall + delta_str = f"{delta:+.4f}" if r["delete_pct"] > 0 else "-" + print( + f"{r['name']:>22} {r['delete_pct']:>4}% " + f"{r['n_deleted']:>8} {r['n_remaining']:>8} " + f"{r['recall']:>7.4f} {delta_str:>7} {r['query_mean_ms']:>8.2f} " + f"{r['db_size_mb']:>9.1f} {r['vacuum_size_mb']:>9.1f} " + f"{r['delete_time_s']:>7.2f}" + ) + print() + + +# ============================================================================ +# Main +# ============================================================================ + +def main(): + parser = argparse.ArgumentParser( + description="Benchmark recall degradation after random row deletion", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__, + ) + parser.add_argument("configs", nargs="+", + help="config specs (name:type=X,key=val,...)") + parser.add_argument("--subset-size", type=int, default=10000, + help="number of vectors to build (default: 10000)") + parser.add_argument("--delete-pct", type=str, default="10,25,50", + help="comma-separated delete percentages (default: 10,25,50)") + parser.add_argument("-k", type=int, default=10, help="KNN k (default 10)") + parser.add_argument("-n", type=int, default=50, + help="number of queries (default 50)") + parser.add_argument("--dataset", default="cohere1m", + choices=list(DATASETS.keys())) + parser.add_argument("--ext", default=EXT_PATH) + parser.add_argument("-o", "--out-dir", + default=os.path.join(_SCRIPT_DIR, "runs")) + parser.add_argument("--seed", type=int, default=42, + help="random seed for delete selection (default: 42)") + args = parser.parse_args() + + ds = DATASETS[args.dataset] + base_db = ds["base_db"] + dims = ds["dimensions"] + if not os.path.exists(base_db): + print(f"Error: dataset not found at {base_db}") + print(f"Run: make -C {os.path.dirname(base_db)}") + return 1 + + delete_pcts = [int(x.strip()) for x in args.delete_pct.split(",")] + for p in delete_pcts: + if not 0 < p < 100: + print(f"Error: delete percentage must be 1-99, got {p}") + return 1 + + out_dir = os.path.join(args.out_dir, args.dataset, str(args.subset_size)) + os.makedirs(out_dir, exist_ok=True) + + all_results = [] + for spec in args.configs: + name, params = parse_config(spec) + params_json = json.dumps(params) + results = run_delete_benchmark( + name, params, base_db, args.ext, args.subset_size, dims, + delete_pcts, args.k, args.n, out_dir, args.seed, + ) + all_results.extend(results) + + save_results(results, out_dir, args.dataset, args.subset_size, + params_json, args.k, args.n, args.seed) + + print_report(all_results) + + results_path = os.path.join(out_dir, "delete_results.db") + print(f"\nResults saved to: {results_path}") + print(f"Query: sqlite3 {results_path} " + f"\"SELECT config_name, delete_pct, recall, vacuum_size_mb " + f"FROM delete_runs ORDER BY config_name, delete_pct\"") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks-ann/bench-delete/test_smoke.py b/benchmarks-ann/bench-delete/test_smoke.py new file mode 100644 index 0000000..0caba19 --- /dev/null +++ b/benchmarks-ann/bench-delete/test_smoke.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Quick self-contained smoke test using a synthetic dataset. +Creates a tiny base.db in a temp dir, runs the delete benchmark, verifies output. +""" +import os +import random +import sqlite3 +import struct +import sys +import tempfile + +_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +_ROOT_DIR = os.path.join(_SCRIPT_DIR, "..", "..") +EXT_PATH = os.path.join(_ROOT_DIR, "dist", "vec0") + +DIMS = 8 +N_TRAIN = 200 +N_QUERIES = 10 +K_NEIGHBORS = 5 + + +def _f32(vals): + return struct.pack(f"{len(vals)}f", *vals) + + +def make_synthetic_base_db(path): + """Create a minimal base.db with train vectors and query vectors.""" + rng = random.Random(123) + db = sqlite3.connect(path) + db.execute("CREATE TABLE train(id INTEGER PRIMARY KEY, vector BLOB)") + db.execute("CREATE TABLE query_vectors(id INTEGER PRIMARY KEY, vector BLOB)") + + for i in range(N_TRAIN): + vec = [rng.gauss(0, 1) for _ in range(DIMS)] + db.execute("INSERT INTO train VALUES (?, ?)", (i, _f32(vec))) + + for i in range(N_QUERIES): + vec = [rng.gauss(0, 1) for _ in range(DIMS)] + db.execute("INSERT INTO query_vectors VALUES (?, ?)", (i, _f32(vec))) + + db.commit() + db.close() + + +def main(): + if not os.path.exists(EXT_PATH + ".dylib") and not os.path.exists(EXT_PATH + ".so"): + # Try bare path (sqlite handles extension) + pass + + with tempfile.TemporaryDirectory() as tmpdir: + base_db = os.path.join(tmpdir, "base.db") + make_synthetic_base_db(base_db) + + # Patch DATASETS to use our synthetic DB + import bench_delete + bench_delete.DATASETS["synthetic"] = { + "base_db": base_db, + "dimensions": DIMS, + } + + out_dir = os.path.join(tmpdir, "runs") + + # Test flat index + print("=== Testing flat index ===") + name, params = bench_delete.parse_config("flat:type=vec0-flat,variant=float") + params["dimensions"] = DIMS + results = bench_delete.run_delete_benchmark( + name, params, base_db, EXT_PATH, + subset_size=N_TRAIN, dims=DIMS, + delete_pcts=[25, 50], k=K_NEIGHBORS, n_queries=N_QUERIES, + out_dir=out_dir, seed_val=42, + ) + + bench_delete.print_report(results) + + # Flat recall should be 1.0 at all delete % + for r in results: + assert r["recall"] == 1.0, \ + f"Flat recall should be 1.0, got {r['recall']} at {r['delete_pct']}%" + print("\n PASS: flat recall is 1.0 at all delete percentages\n") + + # Test DiskANN + print("=== Testing DiskANN ===") + name2, params2 = bench_delete.parse_config( + "diskann:type=diskann,R=8,L=32,quantizer=binary" + ) + params2["dimensions"] = DIMS + results2 = bench_delete.run_delete_benchmark( + name2, params2, base_db, EXT_PATH, + subset_size=N_TRAIN, dims=DIMS, + delete_pcts=[25, 50], k=K_NEIGHBORS, n_queries=N_QUERIES, + out_dir=out_dir, seed_val=42, + ) + + bench_delete.print_report(results2) + + # DiskANN baseline (0%) should have decent recall + baseline = results2[0] + assert baseline["recall"] > 0.0, \ + f"DiskANN baseline recall is zero" + print(f" PASS: DiskANN baseline recall={baseline['recall']}") + + # Test rescore + print("\n=== Testing rescore ===") + name3, params3 = bench_delete.parse_config( + "rescore:type=rescore,quantizer=bit,oversample=4" + ) + params3["dimensions"] = DIMS + results3 = bench_delete.run_delete_benchmark( + name3, params3, base_db, EXT_PATH, + subset_size=N_TRAIN, dims=DIMS, + delete_pcts=[25, 50], k=K_NEIGHBORS, n_queries=N_QUERIES, + out_dir=out_dir, seed_val=42, + ) + + bench_delete.print_report(results3) + print(f" PASS: rescore baseline recall={results3[0]['recall']}") + + print("\n ALL SMOKE TESTS PASSED") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From d684178a12385184fb1319d91cb9021a24679505 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 17:39:41 -0700 Subject: [PATCH 12/24] Add AVX2-optimized Hamming distance using VPSHUFB popcount Implements distance_hamming_avx2() which processes 32 bytes per iteration using the standard VPSHUFB nibble-lookup popcount pattern. Dispatched when SQLITE_VEC_ENABLE_AVX is defined and input >= 32 bytes. Falls back to u64 scalar or u8 byte-at-a-time for smaller inputs. Also adds -mavx2 flag to Makefile for x86-64 targets alongside existing -mavx. Co-Authored-By: Claude Opus 4.6 (1M context) --- Makefile | 4 ++-- sqlite-vec.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 89907fa..175ab16 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ endif ifndef OMIT_SIMD ifeq ($(shell uname -sm),Darwin x86_64) - CFLAGS += -mavx -DSQLITE_VEC_ENABLE_AVX + CFLAGS += -mavx -mavx2 -DSQLITE_VEC_ENABLE_AVX endif ifeq ($(shell uname -sm),Darwin arm64) CFLAGS += -mcpu=apple-m1 -DSQLITE_VEC_ENABLE_NEON @@ -45,7 +45,7 @@ ifndef OMIT_SIMD ifeq ($(shell uname -s),Linux) ifeq ($(findstring android,$(CC)),) ifneq ($(filter avx,$(shell grep -o 'avx[^ ]*' /proc/cpuinfo 2>/dev/null | head -1)),) - CFLAGS += -mavx -DSQLITE_VEC_ENABLE_AVX + CFLAGS += -mavx -mavx2 -DSQLITE_VEC_ENABLE_AVX endif endif endif diff --git a/sqlite-vec.c b/sqlite-vec.c index cb597dd..f239d47 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -708,6 +708,58 @@ static f32 distance_hamming_neon(const u8 *a, const u8 *b, size_t n_bytes) { } #endif +#ifdef SQLITE_VEC_ENABLE_AVX +/** + * AVX2 Hamming distance using VPSHUFB-based popcount. + * Processes 32 bytes (256 bits) per iteration. + */ +static f32 distance_hamming_avx2(const u8 *a, const u8 *b, size_t n_bytes) { + const u8 *pEnd = a + n_bytes; + + // VPSHUFB lookup table: popcount of low nibble + const __m256i lookup = _mm256_setr_epi8( + 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, + 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4); + const __m256i low_mask = _mm256_set1_epi8(0x0f); + + __m256i acc = _mm256_setzero_si256(); + + 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++) { @@ -762,6 +814,11 @@ static f32 distance_hamming(const void *a, const void *b, const void *d) { return distance_hamming_neon((const u8 *)a, (const u8 *)b, n_bytes); } #endif +#ifdef SQLITE_VEC_ENABLE_AVX + if (n_bytes >= 32) { + return distance_hamming_avx2((const u8 *)a, (const u8 *)b, n_bytes); + } +#endif if ((dimensions % 64) == 0) { return distance_hamming_u64((const u8 *)a, (const u8 *)b, n_bytes / sizeof(u64)); From f2c9fb8f087c0a37e21e3f05f158207834826abd Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 17:43:49 -0700 Subject: [PATCH 13/24] Add text PK, WAL concurrency tests, and fix bench-smoke config Infrastructure improvements: - Fix benchmarks-ann Makefile: type=baseline -> type=vec0-flat (baseline was never a valid INDEX_REGISTRY key) - Add DiskANN + text primary key test: insert, KNN, delete, KNN - Add rescore + text primary key test: insert, KNN, delete, KNN - Add WAL concurrency test: reader sees snapshot isolation while writer has an open transaction, KNN works on reader's snapshot Co-Authored-By: Claude Opus 4.6 (1M context) --- benchmarks-ann/Makefile | 8 +++--- tests/test-diskann.py | 43 +++++++++++++++++++++++++++++ tests/test-insert-delete.py | 54 +++++++++++++++++++++++++++++++++++++ tests/test-rescore.py | 37 +++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/benchmarks-ann/Makefile b/benchmarks-ann/Makefile index a631478..9ae456e 100644 --- a/benchmarks-ann/Makefile +++ b/benchmarks-ann/Makefile @@ -4,9 +4,9 @@ EXT = ../dist/vec0 # --- Baseline (brute-force) configs --- BASELINES = \ - "brute-float:type=baseline,variant=float" \ - "brute-int8:type=baseline,variant=int8" \ - "brute-bit:type=baseline,variant=bit" + "brute-float:type=vec0-flat,variant=float" \ + "brute-int8:type=vec0-flat,variant=int8" \ + "brute-bit:type=vec0-flat,variant=bit" # --- IVF configs --- IVF_CONFIGS = \ @@ -43,7 +43,7 @@ ground-truth: seed # --- Quick smoke test --- bench-smoke: seed $(BENCH) --subset-size 5000 -k 10 -n 20 --dataset cohere1m -o runs \ - "brute-float:type=baseline,variant=float" \ + "brute-float:type=vec0-flat,variant=float" \ "ivf-quick:type=ivf,nlist=16,nprobe=4" \ "diskann-quick:type=diskann,R=48,L=64,quantizer=binary" diff --git a/tests/test-diskann.py b/tests/test-diskann.py index f2a56a1..d3f3e86 100644 --- a/tests/test-diskann.py +++ b/tests/test-diskann.py @@ -1246,3 +1246,46 @@ def test_diskann_delete_interleaved_with_knn(db): returned = {r["rowid"] for r in rows} assert returned.issubset(alive), \ f"Deleted rowid {to_del} found in KNN results" + + +# ====================================================================== +# Text primary key + DiskANN +# ====================================================================== + + +def test_diskann_text_pk_insert_knn_delete(db): + """DiskANN with text primary key: insert, KNN, delete, KNN again.""" + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + id text primary key, + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8) + ) + """) + + vecs = { + "alpha": [1, 0, 0, 0, 0, 0, 0, 0], + "beta": [0, 1, 0, 0, 0, 0, 0, 0], + "gamma": [0, 0, 1, 0, 0, 0, 0, 0], + "delta": [0, 0, 0, 1, 0, 0, 0, 0], + "epsilon": [0, 0, 0, 0, 1, 0, 0, 0], + } + for name, vec in vecs.items(): + db.execute("INSERT INTO t(id, emb) VALUES (?, ?)", [name, _f32(vec)]) + + # KNN should return text IDs + rows = db.execute( + "SELECT id, distance FROM t WHERE emb MATCH ? AND k=3", + [_f32([1, 0, 0, 0, 0, 0, 0, 0])], + ).fetchall() + assert len(rows) >= 1 + ids = [r["id"] for r in rows] + assert "alpha" in ids # closest to query + + # Delete and verify + db.execute("DELETE FROM t WHERE id = 'alpha'") + rows = db.execute( + "SELECT id FROM t WHERE emb MATCH ? AND k=3", + [_f32([1, 0, 0, 0, 0, 0, 0, 0])], + ).fetchall() + ids = [r["id"] for r in rows] + assert "alpha" not in ids diff --git a/tests/test-insert-delete.py b/tests/test-insert-delete.py index eb34f84..7e97ea2 100644 --- a/tests/test-insert-delete.py +++ b/tests/test-insert-delete.py @@ -483,3 +483,57 @@ def test_delete_one_chunk_of_two_shrinks_pages(tmp_path): row = db.execute("select emb from v where rowid = ?", [i]).fetchone() assert row[0] == _f32([float(i)] * dims) db.close() + + +def test_wal_concurrent_reader_during_write(tmp_path): + """In WAL mode, a reader should see a consistent snapshot while a writer inserts.""" + dims = 4 + db_path = str(tmp_path / "test.db") + + # Writer: create table, insert initial rows, enable WAL + writer = sqlite3.connect(db_path) + writer.enable_load_extension(True) + writer.load_extension("dist/vec0") + writer.execute("PRAGMA journal_mode=WAL") + writer.execute( + f"CREATE VIRTUAL TABLE v USING vec0(emb float[{dims}])" + ) + for i in range(1, 11): + writer.execute("INSERT INTO v(rowid, emb) VALUES (?, ?)", [i, _f32([float(i)] * dims)]) + writer.commit() + + # Reader: open separate connection, start read + reader = sqlite3.connect(db_path) + reader.enable_load_extension(True) + reader.load_extension("dist/vec0") + + # Reader sees 10 rows + count_before = reader.execute("SELECT count(*) FROM v").fetchone()[0] + assert count_before == 10 + + # Writer inserts more rows (not yet committed) + writer.execute("BEGIN") + for i in range(11, 21): + writer.execute("INSERT INTO v(rowid, emb) VALUES (?, ?)", [i, _f32([float(i)] * dims)]) + + # Reader still sees 10 (WAL snapshot isolation) + count_during = reader.execute("SELECT count(*) FROM v").fetchone()[0] + assert count_during == 10 + + # KNN during writer's transaction should work on reader's snapshot + rows = reader.execute( + "SELECT rowid FROM v WHERE emb MATCH ? AND k = 5", + [_f32([1.0] * dims)], + ).fetchall() + assert len(rows) == 5 + assert all(r[0] <= 10 for r in rows) # only original rows + + # Writer commits + writer.commit() + + # Reader sees new rows after re-query (new snapshot) + count_after = reader.execute("SELECT count(*) FROM v").fetchone()[0] + assert count_after == 20 + + writer.close() + reader.close() diff --git a/tests/test-rescore.py b/tests/test-rescore.py index 1dc6cd7..7c9c669 100644 --- a/tests/test-rescore.py +++ b/tests/test-rescore.py @@ -595,3 +595,40 @@ def test_corrupt_zeroblob_validity(db): ).fetchall() except sqlite3.OperationalError: pass # Error is acceptable — crash is not + + +def test_rescore_text_pk_insert_knn_delete(db): + """Rescore with text primary key: insert, KNN, delete, KNN again.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " id text primary key," + " embedding float[128] indexed by rescore(quantizer=bit)" + ")" + ) + + import random + random.seed(99) + vecs = {} + for name in ["alpha", "beta", "gamma", "delta", "epsilon"]: + v = [random.gauss(0, 1) for _ in range(128)] + vecs[name] = v + db.execute("INSERT INTO t(id, embedding) VALUES (?, ?)", [name, float_vec(v)]) + + # KNN should return text IDs + rows = db.execute( + "SELECT id, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 3", + [float_vec(vecs["alpha"])], + ).fetchall() + assert len(rows) >= 1 + ids = [r["id"] for r in rows] + assert "alpha" in ids + + # Delete and verify + db.execute("DELETE FROM t WHERE id = 'alpha'") + rows = db.execute( + "SELECT id FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 3", + [float_vec(vecs["alpha"])], + ).fetchall() + ids = [r["id"] for r in rows] + assert "alpha" not in ids + assert len(rows) >= 1 # other results still returned From 5522e86cd237a3e15276a8d1f03e34fedadd7177 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 17:49:40 -0700 Subject: [PATCH 14/24] Validate validity/rowids blob sizes in rescore KNN path The rescore KNN loop read validity and rowids blobs from the chunks iterator without checking their sizes matched chunk_size expectations. A truncated or corrupt blob could cause OOB reads in bitmap_copy or rowid array access. The flat KNN path already had these checks. Adds corruption tests: truncated rowids blob and truncated validity blob both produce errors instead of crashes. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec-rescore.c | 8 ++++++++ tests/test-rescore.py | 33 ++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/sqlite-vec-rescore.c b/sqlite-vec-rescore.c index 1cf67bf..5432612 100644 --- a/sqlite-vec-rescore.c +++ b/sqlite-vec-rescore.c @@ -426,10 +426,18 @@ static int rescore_knn(vec0_vtab *p, vec0_cursor *pCur, unsigned char *chunkValidity = (unsigned char *)sqlite3_column_blob(stmtChunks, 1); i64 *chunkRowids = (i64 *)sqlite3_column_blob(stmtChunks, 2); + int validityBytes = sqlite3_column_bytes(stmtChunks, 1); + int rowidsBytes = sqlite3_column_bytes(stmtChunks, 2); if (!chunkValidity || !chunkRowids) { rc = SQLITE_ERROR; goto cleanup; } + // Validate blob sizes match chunk_size expectations + if (validityBytes < (p->chunk_size + 7) / 8 || + rowidsBytes < p->chunk_size * (int)sizeof(i64)) { + rc = SQLITE_ERROR; + goto cleanup; + } memset(chunk_distances, 0, p->chunk_size * sizeof(f32)); memset(chunk_topk_idxs, 0, k_oversample * sizeof(i32)); diff --git a/tests/test-rescore.py b/tests/test-rescore.py index 7c9c669..aa8586e 100644 --- a/tests/test-rescore.py +++ b/tests/test-rescore.py @@ -587,14 +587,37 @@ def test_corrupt_zeroblob_validity(db): # Corrupt: replace rowids with a truncated blob (wrong size) db.execute("UPDATE t_chunks SET rowids = x'00'") - # Should not crash — may return wrong results or error - try: - rows = db.execute( + # Should error, not crash — blob size validation catches the mismatch + with pytest.raises(sqlite3.OperationalError): + db.execute( "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", [float_vec([1, 0, 0, 0, 0, 0, 0, 0])], ).fetchall() - except sqlite3.OperationalError: - pass # Error is acceptable — crash is not + + +def test_corrupt_truncated_validity_blob(db): + """KNN should error when rescore chunk validity blob is truncated.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit)" + ")" + ) + for i in range(5): + import random + random.seed(i) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec([random.gauss(0, 1) for _ in range(128)])], + ) + + # Corrupt: truncate validity blob to 1 byte (should be chunk_size/8 = 128 bytes) + db.execute("UPDATE t_chunks SET validity = x'FF'") + + with pytest.raises(sqlite3.OperationalError): + db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0] * 128)], + ).fetchall() def test_rescore_text_pk_insert_knn_delete(db): From c4c23bd8baaf70b079e3ead2675872dd452ba922 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 17:52:12 -0700 Subject: [PATCH 15/24] Reject NaN and Inf in float32 vector input NaN/Inf values in vectors break heap/sort invariants in KNN, causing wrong or unpredictable results. Now rejected at parse time in fvec_from_value() for both blob and JSON text input paths, with a clear error message identifying the offending element index. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 19 ++++++++++++++++++- tests/test-loadable.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index f239d47..7261436 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -984,8 +984,18 @@ static int fvec_from_value(sqlite3_value *value, f32 **vector, 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 = bytes / sizeof(f32); + *dimensions = n; *cleanup = sqlite3_free; return SQLITE_OK; } @@ -1053,6 +1063,13 @@ static int fvec_from_value(sqlite3_value *value, f32 **vector, } 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, (const void *)&res); offset += (endptr - ptr); diff --git a/tests/test-loadable.py b/tests/test-loadable.py index 1ac0cf3..0044144 100644 --- a/tests/test-loadable.py +++ b/tests/test-loadable.py @@ -365,6 +365,34 @@ def test_vec_distance_l1(): ) +def test_vec_reject_nan_inf(): + """NaN and Inf in float32 vectors should be rejected.""" + import struct, math + + # NaN via blob + nan_blob = struct.pack("4f", 1.0, float("nan"), 3.0, 4.0) + with pytest.raises(sqlite3.OperationalError, match="NaN"): + db.execute("SELECT vec_length(?)", [nan_blob]) + + # Inf via blob + inf_blob = struct.pack("4f", 1.0, float("inf"), 3.0, 4.0) + with pytest.raises(sqlite3.OperationalError, match="Inf"): + db.execute("SELECT vec_length(?)", [inf_blob]) + + # -Inf via blob + ninf_blob = struct.pack("4f", 1.0, float("-inf"), 3.0, 4.0) + with pytest.raises(sqlite3.OperationalError, match="Inf"): + db.execute("SELECT vec_length(?)", [ninf_blob]) + + # NaN via JSON + # Note: JSON doesn't have NaN literal, but strtod may parse "NaN" + # This tests the blob path which is the primary input method + + # Valid vectors still work + ok_blob = struct.pack("4f", 1.0, 2.0, 3.0, 4.0) + assert db.execute("SELECT vec_length(?)", [ok_blob]).fetchone()[0] == 4 + + def test_vec_distance_l2(): vec_distance_l2 = lambda *args, a="?", b="?": db.execute( f"select vec_distance_l2({a}, {b})", args From c36a995f1e7f35325db480de296cc70fae59b7cb Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 17:54:38 -0700 Subject: [PATCH 16/24] Propagate diskann_node_write error in delete repair path diskann_repair_reverse_edges() ignored the return code from diskann_node_write() when writing repaired neighbor lists after a node deletion. A failed write would leave the graph inconsistent with no error reported to the caller. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec-diskann.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlite-vec-diskann.c b/sqlite-vec-diskann.c index ab9db6a..e0af464 100644 --- a/sqlite-vec-diskann.c +++ b/sqlite-vec-diskann.c @@ -1621,13 +1621,14 @@ static int diskann_repair_reverse_edges( break; } - diskann_node_write(p, vec_col_idx, nodeRowid, - validity, vs, neighborIds, nis, qvecs, qs); + rc = diskann_node_write(p, vec_col_idx, nodeRowid, + validity, vs, neighborIds, nis, qvecs, qs); } sqlite3_free(validity); sqlite3_free(neighborIds); sqlite3_free(qvecs); + if (rc != SQLITE_OK) return rc; } return SQLITE_OK; From 01b4b2a965b7471831d390d38d475595a9acde34 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 18:10:48 -0700 Subject: [PATCH 17/24] Scrub stale reverse edges on DiskANN delete (data leak fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After deleting a node, its rowid and quantized vector remained in other nodes' neighbor blobs via unidirectional reverse edges. This is a data leak — the deleted vector's compressed representation was still readable in shadow tables. Fix: after deleting the node and repairing forward edges, scan all remaining nodes and clear any neighbor slot that references the deleted rowid. Uses a lightweight two-pass approach: first scan reads only validity + neighbor_ids to find affected nodes, then does full read/clear/write only for those nodes. Tradeoff: O(N) scan per delete adds ~1ms/row at 10k vectors, ~10ms at 100k. Recall and query latency are unaffected. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec-diskann.c | 95 +++++++++++++++++++++++++++++++++++++++++++ tests/test-diskann.py | 37 +++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/sqlite-vec-diskann.c b/sqlite-vec-diskann.c index e0af464..5bd298b 100644 --- a/sqlite-vec-diskann.c +++ b/sqlite-vec-diskann.c @@ -1638,6 +1638,95 @@ static int diskann_repair_reverse_edges( * Delete a vector from the DiskANN graph (Algorithm 3: LM-Delete). * If the vector is in the buffer (not yet flushed), just remove from buffer. */ +/** + * Scan all nodes and clear any neighbor slot referencing deleted_rowid. + * This removes stale reverse edges that the forward-edge repair misses, + * preventing data leaks (deleted rowid + quantized vector lingering in + * other nodes' blobs). + */ +static int diskann_scrub_deleted_rowid( + vec0_vtab *p, int vec_col_idx, i64 deleted_rowid) { + + struct VectorColumnDefinition *col = &p->vector_columns[vec_col_idx]; + struct Vec0DiskannConfig *cfg = &col->diskann; + int rc; + sqlite3_stmt *stmt = NULL; + + // Lightweight scan: only read validity + neighbor_ids to find matches + char *zSql = sqlite3_mprintf( + "SELECT rowid, neighbors_validity, neighbor_ids " + "FROM " VEC0_SHADOW_DISKANN_NODES_N_NAME, + p->schemaName, p->tableName, vec_col_idx); + if (!zSql) return SQLITE_NOMEM; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); + sqlite3_free(zSql); + if (rc != SQLITE_OK) return rc; + + // Collect rowids that need updating (avoid modifying while iterating) + i64 *dirty = NULL; + int nDirty = 0, capDirty = 0; + + while (sqlite3_step(stmt) == SQLITE_ROW) { + const u8 *validity = (const u8 *)sqlite3_column_blob(stmt, 1); + const u8 *ids = (const u8 *)sqlite3_column_blob(stmt, 2); + int idsBytes = sqlite3_column_bytes(stmt, 2); + if (!validity || !ids) continue; + + int nSlots = idsBytes / (int)sizeof(i64); + if (nSlots > cfg->n_neighbors) nSlots = cfg->n_neighbors; + + for (int i = 0; i < nSlots; i++) { + if (!diskann_validity_get(validity, i)) continue; + i64 nid = diskann_neighbor_id_get(ids, i); + if (nid == deleted_rowid) { + i64 nodeRowid = sqlite3_column_int64(stmt, 0); + // Add to dirty list + if (nDirty >= capDirty) { + capDirty = capDirty ? capDirty * 2 : 16; + i64 *tmp = sqlite3_realloc64(dirty, capDirty * sizeof(i64)); + if (!tmp) { sqlite3_free(dirty); sqlite3_finalize(stmt); return SQLITE_NOMEM; } + dirty = tmp; + } + dirty[nDirty++] = nodeRowid; + break; // one match per node is enough + } + } + } + sqlite3_finalize(stmt); + + // Now do full read/clear/write for each dirty node + for (int d = 0; d < nDirty; d++) { + u8 *val = NULL, *nids = NULL, *qvecs = NULL; + int vs, nis, qs; + rc = diskann_node_read(p, vec_col_idx, dirty[d], + &val, &vs, &nids, &nis, &qvecs, &qs); + if (rc != SQLITE_OK) continue; + + int modified = 0; + for (int i = 0; i < cfg->n_neighbors; i++) { + if (diskann_validity_get(val, i) && + diskann_neighbor_id_get(nids, i) == deleted_rowid) { + diskann_node_clear_neighbor(val, nids, qvecs, i, + cfg->quantizer_type, col->dimensions); + modified = 1; + } + } + + if (modified) { + rc = diskann_node_write(p, vec_col_idx, dirty[d], + val, vs, nids, nis, qvecs, qs); + } + + sqlite3_free(val); + sqlite3_free(nids); + sqlite3_free(qvecs); + if (rc != SQLITE_OK) break; + } + + sqlite3_free(dirty); + return rc; +} + static int diskann_delete(vec0_vtab *p, int vec_col_idx, i64 rowid) { struct VectorColumnDefinition *col = &p->vector_columns[vec_col_idx]; struct Vec0DiskannConfig *cfg = &col->diskann; @@ -1706,6 +1795,12 @@ static int diskann_delete(vec0_vtab *p, int vec_col_idx, i64 rowid) { rc = diskann_medoid_handle_delete(p, vec_col_idx, rowid); } + // 5. Scrub stale reverse edges — removes deleted rowid + quantized vector + // from any node that still references it (data leak prevention) + if (rc == SQLITE_OK) { + rc = diskann_scrub_deleted_rowid(p, vec_col_idx, rowid); + } + return rc; } diff --git a/tests/test-diskann.py b/tests/test-diskann.py index d3f3e86..16ab872 100644 --- a/tests/test-diskann.py +++ b/tests/test-diskann.py @@ -1289,3 +1289,40 @@ def test_diskann_text_pk_insert_knn_delete(db): ).fetchall() ids = [r["id"] for r in rows] assert "alpha" not in ids + + +def test_diskann_delete_scrubs_all_references(db): + """After DELETE, no shadow table should contain the deleted rowid or its data.""" + import struct + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8) + ) + """) + for i in range(20): + vec = struct.pack("8f", *[float(i + d) for d in range(8)]) + db.execute("INSERT INTO t(rowid, emb) VALUES (?, ?)", [i, vec]) + + target = 5 + db.execute("DELETE FROM t WHERE rowid = ?", [target]) + + # Node row itself should be gone + assert db.execute( + "SELECT count(*) FROM t_diskann_nodes00 WHERE rowid=?", [target] + ).fetchone()[0] == 0 + + # Vector should be gone + assert db.execute( + "SELECT count(*) FROM t_vectors00 WHERE rowid=?", [target] + ).fetchone()[0] == 0 + + # No other node should reference the deleted rowid in neighbor_ids + for row in db.execute("SELECT rowid, neighbor_ids FROM t_diskann_nodes00"): + node_rowid = row[0] + ids_blob = row[1] + for j in range(0, len(ids_blob), 8): + nid = struct.unpack(" Date: Tue, 31 Mar 2026 18:27:02 -0700 Subject: [PATCH 18/24] Enable auxiliary columns for rescore, IVF, and DiskANN indexes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The constructor previously rejected auxiliary columns (+col) for all non-flat index types. Analysis confirms all code paths already handle aux columns correctly — aux data lives in _auxiliary shadow table, independent of the vector index structures. Remove the three auxiliary column guards. Metadata and partition key guards remain in place (separate analysis needed). Adds 8 snapshot-based tests covering shadow table creation, insert+KNN returning aux values, aux UPDATE, aux DELETE cleanup, and DROP TABLE for both rescore and DiskANN. IVF aux verified with IVF-enabled build. Co-Authored-By: Claude Opus 4.6 (1M context) --- sqlite-vec.c | 16 - tests/__snapshots__/test-auxiliary.ambr | 371 ++++++++++++++++++++++++ tests/test-auxiliary.py | 199 ++++++++++++- tests/test-diskann.py | 13 +- tests/test-ivf-mutations.py | 14 +- tests/test-rescore-mutations.py | 21 +- 6 files changed, 597 insertions(+), 37 deletions(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index 7261436..16c3b4d 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -5149,11 +5149,6 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, } } if (hasRescore) { - if (numAuxiliaryColumns > 0) { - *pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR - "Auxiliary columns are not supported with rescore indexes"); - goto error; - } if (numMetadataColumns > 0) { *pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR "Metadata columns are not supported with rescore indexes"); @@ -5183,11 +5178,6 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, "partition key columns are not supported with IVF indexes"); goto error; } - if (numAuxiliaryColumns > 0) { - *pzErr = sqlite3_mprintf(VEC_CONSTRUCTOR_ERROR - "auxiliary 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"); @@ -5199,12 +5189,6 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, // 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 (numAuxiliaryColumns > 0) { - *pzErr = sqlite3_mprintf( - VEC_CONSTRUCTOR_ERROR - "Auxiliary columns are not supported with DiskANN-indexed vector columns"); - goto error; - } if (numMetadataColumns > 0) { *pzErr = sqlite3_mprintf( VEC_CONSTRUCTOR_ERROR diff --git a/tests/__snapshots__/test-auxiliary.ambr b/tests/__snapshots__/test-auxiliary.ambr index 66a3ef3..7313faf 100644 --- a/tests/__snapshots__/test-auxiliary.ambr +++ b/tests/__snapshots__/test-auxiliary.ambr @@ -169,6 +169,200 @@ }), }) # --- +# name: test_diskann_aux_insert_knn[diskann aux select all] + OrderedDict({ + 'sql': 'SELECT rowid, label FROM t ORDER BY rowid', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'label': 'red', + }), + OrderedDict({ + 'rowid': 2, + 'label': 'green', + }), + OrderedDict({ + 'rowid': 3, + 'label': 'blue', + }), + ]), + }) +# --- +# name: test_diskann_aux_insert_knn[diskann aux shadow contents] + dict({ + 't_auxiliary': OrderedDict({ + 'sql': 'select * from t_auxiliary', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'value00': 'red', + }), + OrderedDict({ + 'rowid': 2, + 'value00': 'green', + }), + OrderedDict({ + 'rowid': 3, + 'value00': 'blue', + }), + ]), + }), + 't_chunks': OrderedDict({ + 'sql': 'select * from t_chunks', + 'rows': list([ + ]), + }), + 't_diskann_buffer00': OrderedDict({ + 'sql': 'select * from t_diskann_buffer00', + 'rows': list([ + ]), + }), + 't_diskann_nodes00': OrderedDict({ + 'sql': 'select * from t_diskann_nodes00', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'neighbors_validity': b'\x03', + 'neighbor_ids': b'\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'neighbor_quantized_vectors': b'\x02\x04\x00\x00\x00\x00\x00\x00', + }), + OrderedDict({ + 'rowid': 2, + 'neighbors_validity': b'\x03', + 'neighbor_ids': b'\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'neighbor_quantized_vectors': b'\x01\x04\x00\x00\x00\x00\x00\x00', + }), + OrderedDict({ + 'rowid': 3, + 'neighbors_validity': b'\x03', + 'neighbor_ids': b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'neighbor_quantized_vectors': b'\x01\x02\x00\x00\x00\x00\x00\x00', + }), + ]), + }), + 't_rowids': OrderedDict({ + 'sql': 'select * from t_rowids', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'id': None, + 'chunk_id': None, + 'chunk_offset': None, + }), + OrderedDict({ + 'rowid': 2, + 'id': None, + 'chunk_id': None, + 'chunk_offset': None, + }), + OrderedDict({ + 'rowid': 3, + 'id': None, + 'chunk_id': None, + 'chunk_offset': None, + }), + ]), + }), + 't_vectors00': OrderedDict({ + 'sql': 'select * from t_vectors00', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'vector': b'\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + }), + OrderedDict({ + 'rowid': 2, + 'vector': b'\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + }), + OrderedDict({ + 'rowid': 3, + 'vector': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + }), + ]), + }), + }) +# --- +# name: test_diskann_aux_shadow_tables[diskann aux shadow tables] + OrderedDict({ + 'sql': "SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE 't_%' ORDER BY name", + 'rows': list([ + OrderedDict({ + 'name': 't_auxiliary', + 'sql': 'CREATE TABLE "t_auxiliary"( rowid integer PRIMARY KEY , value00, value01)', + }), + OrderedDict({ + 'name': 't_chunks', + 'sql': 'CREATE TABLE "t_chunks"(chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,size INTEGER NOT NULL,validity BLOB NOT NULL,rowids BLOB NOT NULL)', + }), + OrderedDict({ + 'name': 't_diskann_buffer00', + 'sql': 'CREATE TABLE "t_diskann_buffer00" (rowid INTEGER PRIMARY KEY, vector BLOB NOT NULL)', + }), + OrderedDict({ + 'name': 't_diskann_nodes00', + 'sql': 'CREATE TABLE "t_diskann_nodes00" (rowid INTEGER PRIMARY KEY, neighbors_validity BLOB NOT NULL, neighbor_ids BLOB NOT NULL, neighbor_quantized_vectors BLOB NOT NULL)', + }), + OrderedDict({ + 'name': 't_info', + 'sql': 'CREATE TABLE "t_info" (key text primary key, value any)', + }), + OrderedDict({ + 'name': 't_rowids', + 'sql': 'CREATE TABLE "t_rowids"(rowid INTEGER PRIMARY KEY AUTOINCREMENT,id,chunk_id INTEGER,chunk_offset INTEGER)', + }), + OrderedDict({ + 'name': 't_vectors00', + 'sql': 'CREATE TABLE "t_vectors00" (rowid INTEGER PRIMARY KEY, vector BLOB NOT NULL)', + }), + ]), + }) +# --- +# name: test_diskann_aux_update_and_delete[diskann aux after update+delete] + OrderedDict({ + 'sql': 'SELECT rowid, label FROM t ORDER BY rowid', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'label': 'item-1', + }), + OrderedDict({ + 'rowid': 2, + 'label': 'UPDATED', + }), + OrderedDict({ + 'rowid': 4, + 'label': 'item-4', + }), + OrderedDict({ + 'rowid': 5, + 'label': 'item-5', + }), + ]), + }) +# --- +# name: test_diskann_aux_update_and_delete[diskann aux shadow after update+delete] + OrderedDict({ + 'sql': 'SELECT rowid, value00 FROM t_auxiliary ORDER BY rowid', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'value00': 'item-1', + }), + OrderedDict({ + 'rowid': 2, + 'value00': 'UPDATED', + }), + OrderedDict({ + 'rowid': 4, + 'value00': 'item-4', + }), + OrderedDict({ + 'rowid': 5, + 'value00': 'item-5', + }), + ]), + }) +# --- # name: test_knn OrderedDict({ 'sql': 'select * from v', @@ -392,6 +586,183 @@ ]), }) # --- +# name: test_rescore_aux_delete[rescore aux after delete] + OrderedDict({ + 'sql': 'SELECT rowid, label FROM t ORDER BY rowid', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'label': 'item-1', + }), + OrderedDict({ + 'rowid': 2, + 'label': 'item-2', + }), + OrderedDict({ + 'rowid': 4, + 'label': 'item-4', + }), + OrderedDict({ + 'rowid': 5, + 'label': 'item-5', + }), + ]), + }) +# --- +# name: test_rescore_aux_delete[rescore aux shadow after delete] + OrderedDict({ + 'sql': 'SELECT rowid, value00 FROM t_auxiliary ORDER BY rowid', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'value00': 'item-1', + }), + OrderedDict({ + 'rowid': 2, + 'value00': 'item-2', + }), + OrderedDict({ + 'rowid': 4, + 'value00': 'item-4', + }), + OrderedDict({ + 'rowid': 5, + 'value00': 'item-5', + }), + ]), + }) +# --- +# name: test_rescore_aux_insert_knn[rescore aux select all] + OrderedDict({ + 'sql': 'SELECT rowid, label FROM t ORDER BY rowid', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'label': 'alpha', + }), + OrderedDict({ + 'rowid': 2, + 'label': 'beta', + }), + OrderedDict({ + 'rowid': 3, + 'label': 'gamma', + }), + ]), + }) +# --- +# name: test_rescore_aux_insert_knn[rescore aux shadow contents] + dict({ + 't_auxiliary': OrderedDict({ + 'sql': 'select * from t_auxiliary', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'value00': 'alpha', + }), + OrderedDict({ + 'rowid': 2, + 'value00': 'beta', + }), + OrderedDict({ + 'rowid': 3, + 'value00': 'gamma', + }), + ]), + }), + 't_chunks': OrderedDict({ + 'sql': 'select * from t_chunks', + 'rows': list([ + OrderedDict({ + 'chunk_id': 1, + 'size': 1024, + 'validity': b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'rowids': b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + }), + ]), + }), + 't_rescore_chunks00': OrderedDict({ + 'sql': 'select * from t_rescore_chunks00', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'vectors': b'=\xf3<\xef\xf1H\x85\xa57\x16v\xe6/\x86\x7f\xace\x96\x11|1\x18a\xd8\x15\x1c&\x02z\x9e\xeb\x12\xa4\xd7\xd2i\x89\xc4\x18A>\xa2\x9bT\xcd=\xd9i\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + }), + ]), + }), + 't_rescore_vectors00': OrderedDict({ + 'sql': 'select * from t_rescore_vectors00', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'vector': b']\x1c\x8a>\xc4\x9eX\xbf\xceY\xe3=w\x9b\xed?\nQZ?\xdc\x9f@?\x80u\xa4\xbf\x16q\xfa\xbeB\x9b\\?B\x13\x8f?\x80\xd1\xf9\xbf\x10\x0c\xf9\xbb\xf8\x1c\x1e@G\xbd\xe3>\xea\x03[?\xecc\xcf?\x12\x03\xf3\xbf\xa1\xd4\xb2\xbd\x8f\x99\xb8>\x92\xb5M?\xa1\xc9\xbd?/\x90g?\xe1u)\xbf\x94\xe8\x12\xbe\xd6\xba\x16>\'i\xc4>\xc3?\xca>\t\x0e\x9c?\xf0\xa8\xb8\xbf\x1b\x98\x18?\xa8<\xd9??\xdca?\x89\xad\xd0?\x88\xb8<\xbe<\xfa\xab\xbd#\xfc\xf8\xbf\x1e\x90k?\xa0\xec\x1f?a\x1a\xc4?\xc0yU?\xcc\x11\xec\xbe(\xad\xe5\xbd\xfbx\xd0\xbfA\xd3\x16?1\xc5\xf6\xbe\xdcn\'\xbe\x00\xe6\xa0>\t\xd4\x06\xbeD\xfb\xbe?\x1a,\x10\xc0\x80\x8a\x83<\xd2\x8f\x1a\xc0\xf0\xab0\xbf\xfaD\x0b\xbe\x18`\xce\xbf\xa43\x91>\xd0\x13\xea=\x1cpz\xbf\x9ai\x81>\x1d+\xb2\xbd\xb1:\x91\xbe\r\x9e\xf4;|"\xf2\xbfA\x0c\x16@+\x92\t@\x99\x9e\xfb?&\xb9\xa1?_v[\xbf\x98\xb7\x87?\xfe"\xc7>%]#\xbe\xa0\xf2\xd5\xbe\x9e"\x06\xbe\x1dz\xd6>\x84\xa2\x9d?\xd7\xb3\xec\xbf\xbbJ\xbd?\xbd\xebD\xbf\xdd.\xa3\xbe1\xcd\'\xbe\xa2\xf9\xd6\xbfL\xa7I>\xef\x17\x0f<(0n\xbe\xbe\xaf\xdb>\x7fo\xb5<\xcah\xdf>d\x00f\xbe\xb1\x85`\xbf\x95 9?\xd1\xeb\xcd>gk\xb8\xbe\x18\xd3\xfe\xbd1\x80\xdb?\x8b\x86\x03?\x1a\xb7\x9d>\xadM\x1f@\x04\xa0\xca>tc\xfc=\x186\x96?7.\x03\xc0V\xfa\xf8?\xf2\xf2\xa3\xbe1\xa1\xa8\xbf\x06\xb1I\xbfs\xcbT=\xda\xe5}>\xcd@\xca\xbe\x1ee\x1a\xbf\x02H\x14\xbf\x99\xef8\xbeG\xd9\x8a?&\xdc\xff>O\xf8\x9e?\xbd+4>\x9d\xa4\xab=PB\x8c>fs\xac>\x8b\xb4\\?q\xe2S\xbf^\x9a@\xbf\xe7\x7f\xc8\xbf\xbb\x9e\x9f?\xc2\xa0\x07@\xe2mT\xbf@\xf1v\x89n\xbf\xfb\xe2T\xbc\xd4\xd2\xff?,o\xaf?\x0c+\xb6\xbf#\x84|\xbf\x80\xc8\xfd?9\x97\xdb>oa\'\xc0\x8f\xa9\x00>[\xc3\x83\xc0d\xe2\xd2\xbf\xba\xeai>1\x14s>\xe3\x11\x99\xbf\xd9j\x81\xbf\xb3\x1e\xe1\xbcS;)?\x86\x987\xbf\t\xf4\xe4\xbc\xb8f\xa4\xbf\x1c\x83k\xbe|*T\xbf\x00\xd8\xa8?\xc4\x966?2\x14\x14?H\xfe>?\xbd\xbb\x7f=\xcb\x1c\x9f\xbe\xe5\xad\x90?U\x085\xbf\xde{\xb2\xbf\x1f\x03\x10\xbf\x19\xd6J>b\xb9\x97=\xc18z\xbe\xe76\xa7\xbf\xed\x80\x98\xbf\xf5\x12\xb7\xbf\x86(\x9f\xbdaY\x16?\x07j\xb1>\x9ea\x8c\xbd\x91(\xb2\xbf\xe1\xa1\x0c\xbe\xc4_\xd1>\x8c\xad\xf2\xbdc\xab\xf4\xbd\x81<\xc6\xbe}\xa7\xaa\xbfk\xe4\xcb>\xcd89>dk\x81\xbe%\xa4\xb0\xbbAU\x11\xbfG\n\x15\xc0\xb6m\xcb?\xafoq>0\x17\xa5\xbe?j\x81>\xbet\xee;\xc0\xc2\\=S\x81\x8c\xbe2T\xca> \xbe\xe2\xbf1\xd2w?\xed\xfd\x08\xc0\x01\x17\xa0\xbf\x99o\xb1\xbfRX\xb7\xbf\x06f\xca\xbeD\x9e\x92?\x86W\t?\x03G}?\xdd\xbfv\xbdd\xf0\x0c\xbe\xf8\x8a\x1c\xbf\xd8\xc9\x9e\xbfy/\x13>\x802\xdc= 5`\xbf\x00\xf3"\xbf\x99\x92\x01>7 \x06\xc0{\xd7\x8d\xbf\xa5/\x8e\xbf\\\x82u?\x17M\x1e\xbf\xcf\xbbk\xbe\xc3\x84i\xbe\xf1\xa4&\xbfD\xb4\x1a\xc0au\x06\xc0:\xbc\x04\xbf\x0cK\xb2?mdD\xbf\xfa\xa4\x9b?\x89w\xd9>\xde\xb7\x8c?!e\x1a>\xc3\x05-\xbf\x11\xce\xdf\xbe!\xf3\x10?\xab!P?\x96\xbc\x9f?]\x1c\x19?f\x97\x88\xbf\xddRM\xbf,)\x8e>7\x14$>}\x8d\x18@O%\xc0\xbf/\xa5C>`B\xe5\xbd\xb71\x1b?s\x11V?3\xa2F=\x13\xaf\x87\xbf\xe2X\x17?\xa7\xc8\x91\xbf\x19^\x83\xbf\xc6w\xa4?[H\xa1\xbf\x17M2\xbf\xfb\x7f\xd5\xbf', + }), + OrderedDict({ + 'rowid': 3, + 'vector': b'b\xd9\x8b\xbf9\x81\x96\xbe\x83h\xe9?\\\xa4\x89\xbf\x93\xff\xc1\xbf6\xfe\xa8?\xd8\x19\xc1\xbf*\xf06?\xb2\x0c\xaa?2\\P>\xd9\xf0\x81?K~\xb4\xbe\x05\x00\x85?\xcfg\x8c\xbf?\x93\xca>\xf82Q=\x00\x8e\xa5\xbf\xf3:\x15@\xbc\x9c>\xbf}\x13\x90\xbf5z\x17?w17\xbf\xaf\xde(?<\x00]?M\xff\x00?\n7\xa8\xbe\x83kU\xbf-R\x1a=\xa1\xbc\x8c\xbe\xf7.\xb0>\xf1W$?\xe3\xb1\xd8\xbeZ\x89\x19>\x08\x0b\x19\xbf8\xbfK\xbe\t\x12\xcb=P\xd5\x81\xbf8C!\xbf$\xaa\x1b\xbf1\x8f\x8e>\xbb\x1c2\xbe"\x88R\xbekR\x86?\xb3\xfa\xee\xbe\x1aAN\xbf|\xca,\xbf\x0c{z>\x97;\n=Q>4\xbey\x12\x92\xbf[\xa1]\xbe\t\x93\x9c?\xf5\xbcR?\x1cj]\xbf\xa5w\xa8\xbf\xf5\xc1\x1b\xbej\xb25>5\xf48\xbe\x87\xe4-\xbf x\x8f\xbfoC\x01\xbe\xe7:\x16\xbf\x1c\xf1W?\xf6\x1e\xc8\xbf\xe9&\xd5\xbec^\x19@\x19\n\x98?My\xd0>\xa3\xa4\r?\xfc#\xab>\xf7\x1a\x81\xbf\xbb\xe8\x98\xbf\xb0]>\xbff\x92\xc7=\xb3\x16\x86\xbf4\xdc\x9b\xbe\xe2\xd4C\xbfi\xbb/?r\x0fo\xbf\xb8\xd8g>$\x9cW?\xfd\xb0P?\x05\r\xc0\xbeC\x08\xde?Pz\xcb?\x88\xd5\xe9\xbe\xd4\x07\x0c\xbf\x16s\x7f?\xf9=K\xbd\x9378\xbfI\xd6\xbb=\xe7j\x92\xbe\xeb\xfa\x9f?\x9d\x9d\x83\xbe4\xbbK>\xcf\x82\xab\xbfv\x98\x1c?a"Z\xbf\xaf/\x12?\xfe4\xbc>\x84\xed\x91\xbd\xeb\x857\xc0\x90\x89">\x05t\x92?\x1b\x00(>F>V\xbf\x84\x12\x1e>\xcb\xae\xd8?\xc0S+?\x95Z\x1a?\xbe\x93x\xbf>\xfe\'\xbf`\xa4\x8b?\xca\x08\xba\xbe\x89\xc2\n\xbf\xec2\xb2>\x1c\xb3\x04>w\xc0\x95\xbe\x94\xf0r?\xb5\x08\xc4=\x15~\x84>:\xc78\xbfV-\xdb\xbe\xaf\xde\xb2=\xd8\xc8\xe1\xbe\x06\xf9\x14@^\x16\x92>bk\xb1\xbe', + }), + ]), + }), + 't_rowids': OrderedDict({ + 'sql': 'select * from t_rowids', + 'rows': list([ + OrderedDict({ + 'rowid': 1, + 'id': None, + 'chunk_id': 1, + 'chunk_offset': 0, + }), + OrderedDict({ + 'rowid': 2, + 'id': None, + 'chunk_id': 1, + 'chunk_offset': 1, + }), + OrderedDict({ + 'rowid': 3, + 'id': None, + 'chunk_id': 1, + 'chunk_offset': 2, + }), + ]), + }), + }) +# --- +# name: test_rescore_aux_shadow_tables[rescore aux shadow tables] + OrderedDict({ + 'sql': "SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE 't_%' ORDER BY name", + 'rows': list([ + OrderedDict({ + 'name': 't_auxiliary', + 'sql': 'CREATE TABLE "t_auxiliary"( rowid integer PRIMARY KEY , value00, value01)', + }), + OrderedDict({ + 'name': 't_chunks', + 'sql': 'CREATE TABLE "t_chunks"(chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,size INTEGER NOT NULL,validity BLOB NOT NULL,rowids BLOB NOT NULL)', + }), + OrderedDict({ + 'name': 't_info', + 'sql': 'CREATE TABLE "t_info" (key text primary key, value any)', + }), + OrderedDict({ + 'name': 't_rescore_chunks00', + 'sql': 'CREATE TABLE "t_rescore_chunks00"(rowid PRIMARY KEY, vectors BLOB NOT NULL)', + }), + OrderedDict({ + 'name': 't_rescore_vectors00', + 'sql': 'CREATE TABLE "t_rescore_vectors00"(rowid INTEGER PRIMARY KEY, vector BLOB NOT NULL)', + }), + OrderedDict({ + 'name': 't_rowids', + 'sql': 'CREATE TABLE "t_rowids"(rowid INTEGER PRIMARY KEY AUTOINCREMENT,id,chunk_id INTEGER,chunk_offset INTEGER)', + }), + ]), + }) +# --- # name: test_types OrderedDict({ 'sql': 'select * from v', diff --git a/tests/test-auxiliary.py b/tests/test-auxiliary.py index 807b2b8..dbe9654 100644 --- a/tests/test-auxiliary.py +++ b/tests/test-auxiliary.py @@ -1,5 +1,7 @@ import sqlite3 -from helpers import exec, vec0_shadow_table_contents +import struct +import pytest +from helpers import exec, vec0_shadow_table_contents, _f32 def test_constructor_limit(db, snapshot): @@ -126,3 +128,198 @@ def test_knn(db, snapshot): ) == snapshot(name="illegal KNN w/ aux") +# ====================================================================== +# Auxiliary columns with non-flat indexes +# ====================================================================== + + +def test_rescore_aux_shadow_tables(db, snapshot): + """Rescore + aux column: verify shadow tables are created correctly.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " emb float[128] indexed by rescore(quantizer=bit)," + " +label text," + " +score float" + ")" + ) + assert exec(db, "SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE 't_%' ORDER BY name") == snapshot( + name="rescore aux shadow tables" + ) + + +def test_rescore_aux_insert_knn(db, snapshot): + """Insert with aux data, KNN should return aux column values.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " emb float[128] indexed by rescore(quantizer=bit)," + " +label text" + ")" + ) + import random + random.seed(77) + data = [ + ("alpha", [random.gauss(0, 1) for _ in range(128)]), + ("beta", [random.gauss(0, 1) for _ in range(128)]), + ("gamma", [random.gauss(0, 1) for _ in range(128)]), + ] + for label, vec in data: + db.execute( + "INSERT INTO t(emb, label) VALUES (?, ?)", + [_f32(vec), label], + ) + + assert exec(db, "SELECT rowid, label FROM t ORDER BY rowid") == snapshot( + name="rescore aux select all" + ) + assert vec0_shadow_table_contents(db, "t", skip_info=True) == snapshot( + name="rescore aux shadow contents" + ) + + # KNN should include aux column, "alpha" closest to its own vector + rows = db.execute( + "SELECT label, distance FROM t WHERE emb MATCH ? ORDER BY distance LIMIT 3", + [_f32(data[0][1])], + ).fetchall() + assert len(rows) == 3 + assert rows[0][0] == "alpha" + + +def test_rescore_aux_update(db): + """UPDATE aux column on rescore table should work without affecting vectors.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " emb float[128] indexed by rescore(quantizer=bit)," + " +label text" + ")" + ) + import random + random.seed(88) + vec = [random.gauss(0, 1) for _ in range(128)] + db.execute("INSERT INTO t(rowid, emb, label) VALUES (1, ?, 'original')", [_f32(vec)]) + db.execute("UPDATE t SET label = 'updated' WHERE rowid = 1") + + assert db.execute("SELECT label FROM t WHERE rowid = 1").fetchone()[0] == "updated" + + # KNN still works with updated aux + rows = db.execute( + "SELECT rowid, label FROM t WHERE emb MATCH ? ORDER BY distance LIMIT 1", + [_f32(vec)], + ).fetchall() + assert rows[0][0] == 1 + assert rows[0][1] == "updated" + + +def test_rescore_aux_delete(db, snapshot): + """DELETE should remove aux data from shadow table.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " emb float[128] indexed by rescore(quantizer=bit)," + " +label text" + ")" + ) + import random + random.seed(99) + for i in range(5): + db.execute( + "INSERT INTO t(rowid, emb, label) VALUES (?, ?, ?)", + [i + 1, _f32([random.gauss(0, 1) for _ in range(128)]), f"item-{i+1}"], + ) + + db.execute("DELETE FROM t WHERE rowid = 3") + + assert exec(db, "SELECT rowid, label FROM t ORDER BY rowid") == snapshot( + name="rescore aux after delete" + ) + assert exec(db, "SELECT rowid, value00 FROM t_auxiliary ORDER BY rowid") == snapshot( + name="rescore aux shadow after delete" + ) + + +def test_diskann_aux_shadow_tables(db, snapshot): + """DiskANN + aux column: verify shadow tables are created correctly.""" + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8), + +label text, + +score float + ) + """) + assert exec(db, "SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE 't_%' ORDER BY name") == snapshot( + name="diskann aux shadow tables" + ) + + +def test_diskann_aux_insert_knn(db, snapshot): + """DiskANN + aux: insert, KNN, verify aux values returned.""" + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8), + +label text + ) + """) + data = [ + ("red", [1, 0, 0, 0, 0, 0, 0, 0]), + ("green", [0, 1, 0, 0, 0, 0, 0, 0]), + ("blue", [0, 0, 1, 0, 0, 0, 0, 0]), + ] + for label, vec in data: + db.execute("INSERT INTO t(emb, label) VALUES (?, ?)", [_f32(vec), label]) + + assert exec(db, "SELECT rowid, label FROM t ORDER BY rowid") == snapshot( + name="diskann aux select all" + ) + assert vec0_shadow_table_contents(db, "t", skip_info=True) == snapshot( + name="diskann aux shadow contents" + ) + + rows = db.execute( + "SELECT label, distance FROM t WHERE emb MATCH ? AND k = 3", + [_f32([1, 0, 0, 0, 0, 0, 0, 0])], + ).fetchall() + assert len(rows) >= 1 + assert rows[0][0] == "red" + + +def test_diskann_aux_update_and_delete(db, snapshot): + """DiskANN + aux: update aux column, delete row, verify cleanup.""" + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8), + +label text + ) + """) + for i in range(5): + vec = [0.0] * 8 + vec[i % 8] = 1.0 + db.execute( + "INSERT INTO t(rowid, emb, label) VALUES (?, ?, ?)", + [i + 1, _f32(vec), f"item-{i+1}"], + ) + + db.execute("UPDATE t SET label = 'UPDATED' WHERE rowid = 2") + db.execute("DELETE FROM t WHERE rowid = 3") + + assert exec(db, "SELECT rowid, label FROM t ORDER BY rowid") == snapshot( + name="diskann aux after update+delete" + ) + assert exec(db, "SELECT rowid, value00 FROM t_auxiliary ORDER BY rowid") == snapshot( + name="diskann aux shadow after update+delete" + ) + + +def test_diskann_aux_drop_cleans_all(db): + """DROP TABLE should remove aux shadow table too.""" + db.execute(""" + CREATE VIRTUAL TABLE t USING vec0( + emb float[8] INDEXED BY diskann(neighbor_quantizer=binary, n_neighbors=8), + +label text + ) + """) + db.execute("INSERT INTO t(emb, label) VALUES (?, 'test')", [_f32([1]*8)]) + db.execute("DROP TABLE t") + + tables = [r[0] for r in db.execute( + "SELECT name FROM sqlite_master WHERE name LIKE 't_%'" + ).fetchall()] + assert "t_auxiliary" not in tables + diff --git a/tests/test-diskann.py b/tests/test-diskann.py index 16ab872..4369a8b 100644 --- a/tests/test-diskann.py +++ b/tests/test-diskann.py @@ -630,16 +630,19 @@ def test_diskann_command_search_list_size_error(db): # Error cases: DiskANN + auxiliary/metadata/partition columns # ====================================================================== -def test_diskann_create_error_with_auxiliary_column(db): - """DiskANN tables should not support auxiliary columns.""" - result = exec(db, """ +def test_diskann_create_with_auxiliary_column(db): + """DiskANN tables should support auxiliary columns.""" + db.execute(""" CREATE VIRTUAL TABLE t USING vec0( emb float[64] INDEXED BY diskann(neighbor_quantizer=binary), +extra text ) """) - assert "error" in result - assert "auxiliary" in result["message"].lower() or "Auxiliary" in result["message"] + # Auxiliary shadow table should exist + tables = [r[0] for r in db.execute( + "SELECT name FROM sqlite_master WHERE name LIKE 't_%' ORDER BY 1" + ).fetchall()] + assert "t_auxiliary" in tables def test_diskann_create_error_with_metadata_column(db): diff --git a/tests/test-ivf-mutations.py b/tests/test-ivf-mutations.py index fce1832..c20dac3 100644 --- a/tests/test-ivf-mutations.py +++ b/tests/test-ivf-mutations.py @@ -203,13 +203,15 @@ def test_update_vector_via_delete_insert(db): # ============================================================================ -def test_error_ivf_with_auxiliary_column(db): - result = exec( - db, - "CREATE VIRTUAL TABLE t USING vec0(v float[4] indexed by ivf(), +extra text)", +def test_ivf_with_auxiliary_column(db): + """IVF should support auxiliary columns.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(v float[4] indexed by ivf(), +extra text)" ) - assert "error" in result - assert "auxiliary" in result.get("message", "").lower() + tables = [r[0] for r in db.execute( + "SELECT name FROM sqlite_master WHERE name LIKE 't_%' ORDER BY 1" + ).fetchall()] + assert "t_auxiliary" in tables def test_error_ivf_with_metadata_column(db): diff --git a/tests/test-rescore-mutations.py b/tests/test-rescore-mutations.py index dbb802a..6015471 100644 --- a/tests/test-rescore-mutations.py +++ b/tests/test-rescore-mutations.py @@ -32,15 +32,18 @@ def unpack_float_vec(blob): # ============================================================================ -def test_create_error_with_aux_column(db): - """Rescore should reject auxiliary columns.""" - with pytest.raises(sqlite3.OperationalError, match="Auxiliary columns"): - db.execute( - "CREATE VIRTUAL TABLE t USING vec0(" - " embedding float[8] indexed by rescore(quantizer=bit)," - " +extra text" - ")" - ) +def test_create_with_aux_column(db): + """Rescore should support auxiliary columns.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit)," + " +extra text" + ")" + ) + tables = [r[0] for r in db.execute( + "SELECT name FROM sqlite_master WHERE name LIKE 't_%' ORDER BY 1" + ).fetchall()] + assert "t_auxiliary" in tables def test_create_error_with_metadata_column(db): From 6e2c4c6bab0edb2217120d96eb3050a4aa56a6ef Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Tue, 31 Mar 2026 22:39:18 -0700 Subject: [PATCH 19/24] Add FTS5-style command column and runtime oversample for rescore Replace the old INSERT INTO t(rowid) VALUES('command') hack with a proper hidden command column named after the table (FTS5 pattern): INSERT INTO t(t) VALUES ('oversample=16') The command column is the first hidden column (before distance and k) to reserve ability for future table-valued function argument use. Schema: CREATE TABLE x(rowid, , "" hidden, distance hidden, k hidden) For backwards compat, pre-v0.1.10 tables (detected via _info shadow table version) skip the command column to avoid name conflicts with user columns that may share the table's name. Verified with legacy fixture DB generated by sqlite-vec v0.1.6. Changes: - Add hidden command column to sqlite3_declare_vtab for new tables - Version-gate via _info shadow table for existing tables - Validate at CREATE time that no column name matches table name - Add rescore_handle_command() with oversample=N support - rescore_knn() prefers runtime oversample_search over CREATE default - Remove old rowid-based command dispatch - Migrate all DiskANN/IVF/fuzz tests and benchmarks to new syntax - Add legacy DB fixture (v0.1.6) and 9 backwards-compat tests Co-Authored-By: Claude Opus 4.6 (1M context) --- benchmarks-ann/bench-delete/bench_delete.py | 2 +- benchmarks-ann/bench.py | 10 +- sqlite-vec-rescore.c | 25 +++- sqlite-vec.c | 141 ++++++++++++++++---- tests/fixtures/legacy-v0.1.6.db | Bin 0 -> 106496 bytes tests/fuzz/diskann-command-inject.c | 8 +- tests/fuzz/ivf-cell-overflow.c | 12 +- tests/fuzz/ivf-kmeans.c | 8 +- tests/fuzz/ivf-knn-deep.c | 6 +- tests/fuzz/ivf-operations.c | 8 +- tests/fuzz/ivf-quantize.c | 4 +- tests/fuzz/ivf-rescore.c | 6 +- tests/fuzz/ivf-shadow-corrupt.c | 10 +- tests/generate_legacy_db.py | 81 +++++++++++ tests/test-diskann.py | 10 +- tests/test-general.py | 12 ++ tests/test-ivf-mutations.py | 26 ++-- tests/test-ivf-quantization.py | 14 +- tests/test-ivf.py | 26 ++-- tests/test-legacy-compat.py | 138 +++++++++++++++++++ tests/test-rescore.py | 70 ++++++++++ 21 files changed, 512 insertions(+), 105 deletions(-) create mode 100644 tests/fixtures/legacy-v0.1.6.db create mode 100644 tests/generate_legacy_db.py create mode 100644 tests/test-legacy-compat.py diff --git a/benchmarks-ann/bench-delete/bench_delete.py b/benchmarks-ann/bench-delete/bench_delete.py index 802f0a4..0ebd2ec 100644 --- a/benchmarks-ann/bench-delete/bench_delete.py +++ b/benchmarks-ann/bench-delete/bench_delete.py @@ -159,7 +159,7 @@ INDEX_REGISTRY = { def _ivf_train(conn): """Trigger built-in k-means training for IVF.""" t0 = now_ns() - conn.execute("INSERT INTO vec_items(id) VALUES ('compute-centroids')") + conn.execute("INSERT INTO vec_items(vec_items) VALUES ('compute-centroids')") conn.commit() return ns_to_s(now_ns() - t0) diff --git a/benchmarks-ann/bench.py b/benchmarks-ann/bench.py index a4cbbe4..966c458 100644 --- a/benchmarks-ann/bench.py +++ b/benchmarks-ann/bench.py @@ -456,7 +456,7 @@ def _ivf_create_table_sql(params): def _ivf_post_insert_hook(conn, params): print(" Training k-means centroids (built-in)...", flush=True) t0 = time.perf_counter() - conn.execute("INSERT INTO vec_items(id) VALUES ('compute-centroids')") + conn.execute("INSERT INTO vec_items(vec_items) VALUES ('compute-centroids')") conn.commit() elapsed = time.perf_counter() - t0 print(f" Training done in {elapsed:.1f}s", flush=True) @@ -514,7 +514,7 @@ def _ivf_faiss_kmeans_hook(conn, params): for cid, blob in centroids: conn.execute( - "INSERT INTO vec_items(id, embedding) VALUES (?, ?)", + "INSERT INTO vec_items(vec_items, embedding) VALUES (?, ?)", (f"set-centroid:{cid}", blob), ) conn.commit() @@ -540,7 +540,7 @@ def _ivf_pre_query_hook(conn, params): nprobe = params.get("nprobe") if nprobe: conn.execute( - "INSERT INTO vec_items(id) VALUES (?)", + "INSERT INTO vec_items(vec_items) VALUES (?)", (f"nprobe={nprobe}",), ) conn.commit() @@ -572,7 +572,7 @@ INDEX_REGISTRY["ivf"] = { "insert_sql": None, "post_insert_hook": _ivf_post_insert_hook, "pre_query_hook": _ivf_pre_query_hook, - "train_sql": lambda _: "INSERT INTO vec_items(id) VALUES ('compute-centroids')", + "train_sql": lambda _: "INSERT INTO vec_items(vec_items) VALUES ('compute-centroids')", "run_query": None, "query_sql": None, "describe": _ivf_describe, @@ -616,7 +616,7 @@ def _diskann_pre_query_hook(conn, params): L_search = params.get("L_search", 0) if L_search: conn.execute( - "INSERT INTO vec_items(id) VALUES (?)", + "INSERT INTO vec_items(vec_items) VALUES (?)", (f"search_list_size_search={L_search}",), ) conn.commit() diff --git a/sqlite-vec-rescore.c b/sqlite-vec-rescore.c index 5432612..6a47214 100644 --- a/sqlite-vec-rescore.c +++ b/sqlite-vec-rescore.c @@ -351,7 +351,9 @@ static int rescore_knn(vec0_vtab *p, vec0_cursor *pCur, (void)pCur; (void)aMetadataIn; int rc = SQLITE_OK; - int oversample = vector_column->rescore.oversample; + int oversample = vector_column->rescore.oversample_search > 0 + ? vector_column->rescore.oversample_search + : vector_column->rescore.oversample; i64 k_oversample = k * oversample; if (k_oversample > 4096) k_oversample = 4096; @@ -640,6 +642,27 @@ cleanup: return rc; } +/** + * Handle FTS5-style command dispatch for rescore parameters. + * Returns SQLITE_OK if handled, SQLITE_EMPTY if not a rescore command. + */ +static int rescore_handle_command(vec0_vtab *p, const char *command) { + if (strncmp(command, "oversample=", 11) == 0) { + int val = atoi(command + 11); + if (val < 1) { + vtab_set_error(&p->base, "oversample must be >= 1"); + return SQLITE_ERROR; + } + for (int i = 0; i < p->numVectorColumns; i++) { + if (p->vector_columns[i].index_type == VEC0_INDEX_TYPE_RESCORE) { + p->vector_columns[i].rescore.oversample_search = val; + } + } + return SQLITE_OK; + } + return SQLITE_EMPTY; +} + #ifdef SQLITE_VEC_TEST void _test_rescore_quantize_float_to_bit(const float *src, uint8_t *dst, size_t dim) { rescore_quantize_float_to_bit(src, dst, dim); diff --git a/sqlite-vec.c b/sqlite-vec.c index 16c3b4d..40fe0bf 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -2588,7 +2588,8 @@ enum Vec0RescoreQuantizerType { struct Vec0RescoreConfig { enum Vec0RescoreQuantizerType quantizer_type; - int oversample; + int oversample; // CREATE-time default + int oversample_search; // runtime override (0 = use default) }; #endif @@ -3399,8 +3400,9 @@ static sqlite3_module vec_eachModule = { #define VEC0_COLUMN_ID 0 #define VEC0_COLUMN_USERN_START 1 -#define VEC0_COLUMN_OFFSET_DISTANCE 1 -#define VEC0_COLUMN_OFFSET_K 2 +#define VEC0_COLUMN_OFFSET_COMMAND 1 +#define VEC0_COLUMN_OFFSET_DISTANCE 2 +#define VEC0_COLUMN_OFFSET_K 3 #define VEC0_SHADOW_INFO_NAME "\"%w\".\"%w_info\"" @@ -3498,6 +3500,10 @@ struct vec0_vtab { // 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; @@ -3777,20 +3783,19 @@ int vec0_num_defined_user_columns(vec0_vtab *p) { * @param p vec0 table * @return int */ -int vec0_column_distance_idx(vec0_vtab *p) { - return VEC0_COLUMN_USERN_START + (vec0_num_defined_user_columns(p) - 1) + - VEC0_COLUMN_OFFSET_DISTANCE; +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); } -/** - * @brief Returns the index of the k hidden column for the given vec0 table. - * - * @param p vec0 table - * @return int k column index - */ int vec0_column_k_idx(vec0_vtab *p) { - return VEC0_COLUMN_USERN_START + (vec0_num_defined_user_columns(p) - 1) + - VEC0_COLUMN_OFFSET_K; + int base = VEC0_COLUMN_USERN_START + vec0_num_defined_user_columns(p); + return base + (p->hasCommandColumn ? 2 : 1); } /** @@ -5205,6 +5210,74 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, } } + // 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) { @@ -5246,7 +5319,11 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, } } - sqlite3_str_appendall(createStr, " distance hidden, k hidden) "); + 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 "); } @@ -10161,25 +10238,31 @@ static int vec0Update(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, } // INSERT operation else if (argc > 1 && sqlite3_value_type(argv[0]) == SQLITE_NULL) { -#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE || SQLITE_VEC_ENABLE_DISKANN - // Check for command inserts: INSERT INTO t(rowid) VALUES ('command-string') - // The id column holds the command string. - sqlite3_value *idVal = argv[2 + VEC0_COLUMN_ID]; - if (sqlite3_value_type(idVal) == SQLITE_TEXT) { - const char *cmd = (const char *)sqlite3_value_text(idVal); - vec0_vtab *p = (vec0_vtab *)pVTab; - int cmdRc = SQLITE_EMPTY; + vec0_vtab *p = (vec0_vtab *)pVTab; + // FTS5-style command dispatch via hidden column named after table + if (p->hasCommandColumn) { + sqlite3_value *cmdVal = argv[2 + vec0_column_command_idx(p)]; + if (sqlite3_value_type(cmdVal) == SQLITE_TEXT) { + const char *cmd = (const char *)sqlite3_value_text(cmdVal); + int cmdRc = SQLITE_EMPTY; +#if SQLITE_VEC_ENABLE_RESCORE + cmdRc = rescore_handle_command(p, cmd); +#endif #if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE - cmdRc = ivf_handle_command(p, cmd, argc, argv); + if (cmdRc == SQLITE_EMPTY) + cmdRc = ivf_handle_command(p, cmd, argc, argv); #endif #if SQLITE_VEC_ENABLE_DISKANN - if (cmdRc == SQLITE_EMPTY) - cmdRc = diskann_handle_command(p, cmd); + if (cmdRc == SQLITE_EMPTY) + cmdRc = diskann_handle_command(p, cmd); #endif - if (cmdRc != SQLITE_EMPTY) return cmdRc; // handled (or error) - // SQLITE_EMPTY means not a recognized command — fall through to normal insert + if (cmdRc == SQLITE_EMPTY) { + vtab_set_error(pVTab, "unknown vec0 command: '%s'", cmd); + return SQLITE_ERROR; + } + return cmdRc; + } } -#endif return vec0Update_Insert(pVTab, argc, argv, pRowid); } // UPDATE operation diff --git a/tests/fixtures/legacy-v0.1.6.db b/tests/fixtures/legacy-v0.1.6.db new file mode 100644 index 0000000000000000000000000000000000000000..58bd89d250a0907420336b40abbf14615b1dc244 GIT binary patch literal 106496 zcmeI*Piz$D9mesQo!xhL*6Vp~46F?p#^#?FjIkjOq^cCJ=_)KSHZ=x`MwM$|Y2ARG z*oMR$(p{gb9IA*@MJ|XOQsva5Mv71lz^Ou2R3SyGI3W&A#UY9~HB}@!^Uk}d`?9!I zm14wxUoCp(H}81neLvo{+1c6k(I1aYFO>WwTRhwO1SSI6E-+L#T6z1|cjwRg1i{lf=@+1207OeM=9W9Q& z<_CAE{+_Trb9Q!W;fPa79eUopV=v#EteyGt8&~f^ z|M~9zMoMI7i%(-&{wR|J%lD@$yF@a(Z=YRRtT+AWyKn3wS}B&D?EmdiawH+LJ9pYA z)M)i%f4=ng`O?g(l2ImVMrd^Q%uJmt^#)&=s}$o(U}>P9K=k-B1frGkn*Ddz-R2oT zFyDzqKbhCmn@aeQF$Ni~iY+^haEt%qp<8g4`;lDds88H6!vi_NFSk_q3+}X?%;s{b zN}|p@YImjwU;B8k-Q=kc{pR3EarvPozmi|bPlE*-0tg_000IagfB*srAbN6+mij~B*|7RN>>kK`u~928l{8FY=F!^P3D@odZ)Oqm_|LqUh_e z^u4TJ)R!Mw@@x5}{5)8oA%Fk^2q1s}0tg_000IagfWS%!v_IuEK)g`>j=*R z#9E!4ao!+&{onRltl&mN009ILKmY**5I_I{1Q2K-ftzVNWyO{aB7J3A z^&RFhefa7~s_#Ni==DNpQm49pK3H~j9lppNwx%eojLz`MZ`iyjEUB~lT)0)&t8}*M zT&;7B&b2zTI@@)2=&Zhre!Z?Y=+I2))440V3Ft( z7dmWbpI!Ure*Hl$d)i~pKG*1qz9<-WI_mKpr^^oa7JWKJIy?ok$D6a{xALKUAn(h& z@{YVMZ^`TOn*2mwm6zou`Mz9|7v-G%r93HrBFE(+IV^u5_sAWxTdtF=xZByF`~tU-&9FiA^duij68eMW@OQVuQ-{V!g_BVx7tk(V?jWr7R@R%BBQcNG^tFBv`SaF zDpMk*GAWWOg%BzeBB3%a;wl~CsEj3IVY&%Bj8t^}zqGvSwGRz+Ph_eHAbKjJ=C!_w0QUBGb?-}*qjrtp-{zq6h zg>Q|p<#$$aqalC*0tg_000IagfB*srAb`LNDBw75_z4MVYnPr}pU&Mn`*jZJ+@q5~ z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~f#*%2`qvPu;~K6mL>V{g zlu>7ly2Yqh8+E%;Z!l`#sJ9q(k5Ts;b-z)+WYqhO`iDk6WYj-0>VrmIt3`zX0tg_0 z00IagfB*srAb`MgDp36^h|lTp7#0EuAbg0^`2I1@fw%1|>HyQ#6Ab