diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index dc9897b6..70a6ca2c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -28,6 +28,22 @@ Very much a WIP. #### `xyz_vector_chunksNN` +- `rowid INTEGER` +- `vectors BLOB` + +#### `xyz_rescore_chunksNN` + +The vectors of a column indexed by `rescore()`, quantized, in the layout of +`xyz_vector_chunksNN`. + +- `rowid INTEGER` +- `vectors BLOB` + +#### `xyz_rescore_vectorsNN` + +The `float32` vectors of a column indexed by `rescore()`, one row per row of +the table. + - `rowid INTEGER` - `vector BLOB` @@ -52,7 +68,10 @@ The supported way to tell which tables belong to a `vec0` table is by name. A `vec0` table `xyz` owns these tables and no others (`NN` is two digits): - `xyz_info`, `xyz_chunks`, `xyz_rowids`: always -- `xyz_vector_chunksNN`: one per vector column +- `xyz_vector_chunksNN`: one per vector column, except those indexed by + `rescore()` +- `xyz_rescore_chunksNN`, `xyz_rescore_vectorsNN`: one each per vector column + indexed by `rescore()` - `xyz_auxiliary`: only if there are auxiliary columns - `xyz_metadatachunksNN`: one per metadata column - `xyz_metadatatextNN`: one per text metadata column @@ -69,6 +88,8 @@ join sqlite_master as s and substr(s.name, 1, length(v.name) + 1) = v.name || '_' and (substr(s.name, length(v.name) + 2) in ('rowids', 'chunks', 'info', 'auxiliary') or substr(s.name, length(v.name) + 2) glob 'vector_chunks[0-9][0-9]' + or substr(s.name, length(v.name) + 2) glob 'rescore_chunks[0-9][0-9]' + or substr(s.name, length(v.name) + 2) glob 'rescore_vectors[0-9][0-9]' or substr(s.name, length(v.name) + 2) glob 'metadatachunks[0-9][0-9]' or substr(s.name, length(v.name) + 2) glob 'metadatatext[0-9][0-9]') where v.type = 'table' and v.rootpage = 0 @@ -84,24 +105,25 @@ relied on: - Up to SQLite 3.53, so does a connection that parsed the schema before loading the extension. - Before SQLite 3.37 there is no `PRAGMA table_list` and no prefix marking (see - below), so the vector chunk tables are never shadow tables. + below), so the tables whose suffix has an underscore (`vector_chunksNN`, + `rescore_chunksNN`, `rescore_vectorsNN`) are never shadow tables. - SQLite finds the virtual table that owns a newly created or newly parsed table by splitting its name at the **last** underscore ([documented](https://www.sqlite.org/vtab.html#the_xshadowname_method)). For `xyz_vector_chunks00` it looks for a virtual table called `xyz_vector`, so `xShadowName` is never asked about it on that path. -- `xShadowName` does report `vector_chunksNN`, and SQLite also marks shadow +- `xShadowName` does report those suffixes, and SQLite also marks shadow tables by name prefix when it parses the virtual table's own schema entry. That only sees tables parsed earlier, which in practice means after a `VACUUM` (it rewrites `sqlite_master` with virtual tables last). A `vec0` table created after that `VACUUM` is not covered until the next one. -- SQLite 3.54 additionally marks by prefix after every `xConnect`. There the - vector chunk tables become `shadow` once the `vec0` table has been used in - the connection, except in the connection that created it. `PRAGMA - table_list` connects every virtual table first, so it always reports them as - `shadow`, but under `SQLITE_DBCONFIG_DEFENSIVE` a direct write to a chunk - table is still accepted until the `vec0` table is first touched. This prefix - matching is not part of the documented contract. +- SQLite 3.54 additionally marks by prefix after every `xConnect`. There those + tables become `shadow` once the `vec0` table has been used in the + connection, except in the connection that created it. `PRAGMA table_list` + connects every virtual table first, so it always reports them as `shadow`, + but under `SQLITE_DBCONFIG_DEFENSIVE` a direct write to one of them is still + accepted until the `vec0` table is first touched. This prefix matching is not + part of the documented contract. Rules that follow from this: diff --git a/CHANGELOG.md b/CHANGELOG.md index 326649bf..b391647c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added + +- A vector column definition accepts `indexed by flat()`, as in `embedding float[768] indexed by flat()`. `flat()` is the exhaustive search that a column without the clause gets. Ported from [asg017@0de765f](https://github.com/asg017/sqlite-vec/commit/0de765f) ([asg017#273](https://github.com/asg017/sqlite-vec/pull/273)). +- A `float` vector column can be declared `indexed by rescore(quantizer=bit)` or `indexed by rescore(quantizer=int8)`, with an optional `oversample=N` from 1 to 128 (8 by default). It also keeps its vectors quantized, in the new `_rescore_chunksNN` shadow table, and its `float` vectors by rowid in `_rescore_vectorsNN`. A KNN query scans the quantized vectors for the `k * oversample` nearest rows, then ranks those by the exact distances of their `float` vectors, to which distance constraints apply. The `oversample=N` command, as in `insert into t(t) values ('oversample=16')`, sets the oversample of a connection's queries. Rowid filters, MMR, auxiliary columns, `UPDATE`, `optimize`, and `ALTER TABLE ... RENAME` work on rescore columns; a table with one cannot have metadata or partition key columns. Ported from [asg017#276](https://github.com/asg017/sqlite-vec/pull/276) ([asg017@ba0db0b](https://github.com/asg017/sqlite-vec/commit/ba0db0b)) with its fixes [asg017@5e4c557](https://github.com/asg017/sqlite-vec/commit/5e4c557), [@82f4eb0](https://github.com/asg017/sqlite-vec/commit/82f4eb0), [@5522e86](https://github.com/asg017/sqlite-vec/commit/5522e86), [@b7fc459](https://github.com/asg017/sqlite-vec/commit/b7fc459), [@6e2c4c6](https://github.com/asg017/sqlite-vec/commit/6e2c4c6), the int8 quantizer of [@575371d](https://github.com/asg017/sqlite-vec/commit/575371d), and the distance constraints of the unmerged [asg017#309](https://github.com/asg017/sqlite-vec/pull/309). Unlike upstream, a point query for a rowid that a rescore table does not have returns no row instead of failing with `Could not fetch vector data`. + ### Changed - Reading a vector column without a KNN query, as in `select embedding from v`, reads each chunk's vectors through one blob handle instead of opening the chunk's blob again for every row: selecting every vector of a 10,000-row `float[768]` table took 25 ms instead of 2,080 ms, and `select vec_distance_cosine(embedding, ?) from v` 41 ms instead of 2,093 ms. @@ -10,6 +15,7 @@ - A KNN query picks each chunk's k nearest rows with the heap of upstream's `SQLITE_VEC_EXPERIMENTAL_MIN_IDX` build option ([asg017/sqlite-vec@0de765f](https://github.com/asg017/sqlite-vec/commit/0de765f4570c1d65a0699e8e38d6c5562da9e23e)) instead of one pass over the chunk for every row it keeps, ordered so that it returns the same rows in the same order as before: on a 10,000-row `float[768]` table, `k = 1000` and `k = 4096` take 9.3 ms instead of 17.8 and 18.0 ms, `k = 100` 8.8 ms instead of 9.2, and `k = 1` 8.7 ms either way. A NaN distance ranks after every number instead of where its position put it. A static blob can produce one, and so can a `vec0` table written before 2.1.0, which started rejecting NaN elements. On such a table a NaN row used to push nearer rows out of the result or behind farther ones; queries now return the nearest rows in order, which can leave out a NaN row that a query with `k` smaller than the row count used to return. - A `DELETE` that removes the last row of a chunk now deletes the chunk's rows from the `_chunks`, `_vector_chunksNN`, and `_metadatachunksNN` shadow tables, so their pages are free for reuse without `optimize`; the emptied chunk used to stay until `optimize` removed it. Ported from [asg017@cb147c8](https://github.com/asg017/sqlite-vec/commit/cb147c8) via [vlasky@3a64182](https://github.com/vlasky/sqlite-vec/commit/3a64182) ([asg017#268](https://github.com/asg017/sqlite-vec/pull/268)). - A `DELETE` of a row whose bit in its chunk's validity bitmap is clear now fails with `vec0 deletion error: validity bit is not set`; it used to delete the row when a later row in the same byte of the bitmap was set. Only a damaged `_chunks` table has such a row. +- A command that a `vec0` table does not know, as in `insert into t(t) values ('compact')`, now fails with `unknown vec0 command: 'compact'` instead of `SQL logic error`, as upstream [asg017@6e2c4c6](https://github.com/asg017/sqlite-vec/commit/6e2c4c6) does. ### Fixed diff --git a/README.md b/README.md index ddc5076c..c1c16dba 100644 --- a/README.md +++ b/README.md @@ -295,8 +295,9 @@ VACUUM; `VACUUM` should not corrupt vec tables; a checkpoint first is recommended when using WAL so the rewrite starts from a clean state. -`optimize` runs through a hidden column named after the table, so a `vec0` -table cannot have another column with its name. `CREATE` rejects tables such as +`optimize`, like the `oversample=N` command of a rescore index, runs through a +hidden column named after the table, so a `vec0` table cannot have another +column with its name. `CREATE` rejects tables such as `CREATE VIRTUAL TABLE emb USING vec0(emb float[4])`, and tables named `distance`, `k`, `mmr_lambda`, or (without a primary key column) `rowid`, which are names of columns `vec0` always declares. Older releases allowed these @@ -308,6 +309,73 @@ ALTER TABLE emb RENAME TO emb_vectors; INSERT INTO emb_vectors(emb_vectors) VALUES('optimize'); ``` +### Rescore Index + +A `float` vector column declared `indexed by rescore(quantizer=bit)` or +`indexed by rescore(quantizer=int8)` also keeps each vector quantized, to 1 bit +or 8 bits per dimension. A KNN query scans the quantized vectors for the +`k * oversample` nearest rows, then ranks those candidates by the distances of +their `float` vectors. The distances it returns are exact, and its rows are +those of an exact search whenever the nearest rows are among the candidates: + +```sql +create virtual table vec_rescore using vec0( + sample_embedding float[8] indexed by rescore(quantizer=bit) +); +insert into vec_rescore(rowid, sample_embedding) + select rowid, sample_embedding from vec_examples; + +select rowid, distance +from vec_rescore +where sample_embedding match '[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]' + and k = 3; +/* +┌───────┬──────────────────┐ +│ rowid │ distance │ +├───────┼──────────────────┤ +│ 5 │ 1.16368770599365 │ +│ 13 │ 1.75137972831726 │ +│ 11 │ 1.83941268920898 │ +└───────┴──────────────────┘ +*/ +``` + +`oversample` is 8 unless the column sets it, as in +`indexed by rescore(quantizer=bit, oversample=16)`, from 1 to 128. The +`oversample=N` command sets it for every rescore column, for the queries of the +connection that runs it. With an oversample of 1, the query above keeps only the +3 rows nearest by bit distance, and row 11 is not among them: + +```sql +insert into vec_rescore(vec_rescore) values ('oversample=1'); + +select rowid, distance +from vec_rescore +where sample_embedding match '[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]' + and k = 3; +/* +┌───────┬──────────────────┐ +│ rowid │ distance │ +├───────┼──────────────────┤ +│ 5 │ 1.16368770599365 │ +│ 13 │ 1.75137972831726 │ +│ 16 │ 2.36842274665833 │ +└───────┴──────────────────┘ +*/ +``` + +- `quantizer=bit` sets one bit for each value >= 0, and needs a number of + dimensions divisible by 8. `quantizer=int8` maps [-1, 1] onto the 256 values + of an int8, so it suits vectors whose values lie in that range, such as + normalized embeddings. +- Distance constraints apply to the exact distances of the candidates, so a + query with `distance > x`, as in cursor-based pagination, returns fewer than + `k` rows when most of the candidates are nearer than `x`. +- A table with a rescore column cannot have metadata or partition key columns. + Auxiliary columns work. +- `indexed by flat()` declares the exhaustive search that a column without the + clause gets. + ## Sponsors > [!NOTE] diff --git a/sqlite-vec.c b/sqlite-vec.c index e7b25513..72923b95 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -73,6 +73,10 @@ typedef size_t usize; #define countof(x) (sizeof(x) / sizeof((x)[0])) #define min(a, b) (((a) <= (b)) ? (a) : (b)) +#ifndef SQLITE_VEC_ENABLE_RESCORE +#define SQLITE_VEC_ENABLE_RESCORE 1 +#endif + // VEC0_UNPREDICTABLE(cond) tells clang that cond is hard to predict, so it // emits a conditional move instead of a branch. Other compilers get cond as is. #if defined(__has_builtin) @@ -2287,6 +2291,9 @@ enum Vec0TokenType { TOKEN_TYPE_RBRACKET, TOKEN_TYPE_PLUS, TOKEN_TYPE_EQ, + TOKEN_TYPE_LPAREN, + TOKEN_TYPE_RPAREN, + TOKEN_TYPE_COMMA, }; struct Vec0Token { enum Vec0TokenType token_type; @@ -2337,6 +2344,24 @@ int vec0_token_next(char *start, char *end, struct Vec0Token *out) { out->end = ptr; out->token_type = TOKEN_TYPE_EQ; return VEC0_TOKEN_RESULT_SOME; + } else if (curr == '(') { + ptr++; + out->start = ptr; + out->end = ptr; + out->token_type = TOKEN_TYPE_LPAREN; + return VEC0_TOKEN_RESULT_SOME; + } else if (curr == ')') { + ptr++; + out->start = ptr; + out->end = ptr; + out->token_type = TOKEN_TYPE_RPAREN; + return VEC0_TOKEN_RESULT_SOME; + } else if (curr == ',') { + ptr++; + out->start = ptr; + out->end = ptr; + out->token_type = TOKEN_TYPE_COMMA; + return VEC0_TOKEN_RESULT_SOME; } else if (is_alpha(curr)) { char *start = ptr; while (ptr < end && (is_alpha(*ptr) || is_digit(*ptr) || *ptr == '_')) { @@ -2772,12 +2797,46 @@ enum Vec0DistanceMetrics { VEC0_DISTANCE_METRIC_L1 = 3, }; +// The index a vector column's `indexed by` clause names. A column without one +// is indexed by flat(), which scans every vector of the column. +// https://github.com/asg017/sqlite-vec/commit/0de765f +enum Vec0IndexType { + VEC0_INDEX_TYPE_FLAT = 1, +#if SQLITE_VEC_ENABLE_RESCORE + VEC0_INDEX_TYPE_RESCORE = 2, +#endif +}; + +#if SQLITE_VEC_ENABLE_RESCORE +// `indexed by rescore(quantizer=bit|int8, oversample=N)` keeps a quantized copy +// of each float32 vector in chunks for the KNN scan, which finds k * oversample +// candidates, and the vectors themselves by rowid to rank the candidates by +// their exact distances. +// https://github.com/asg017/sqlite-vec/commit/ba0db0b +enum Vec0RescoreQuantizerType { + VEC0_RESCORE_QUANTIZER_BIT = 1, + VEC0_RESCORE_QUANTIZER_INT8 = 2, +}; + +struct Vec0RescoreConfig { + enum Vec0RescoreQuantizerType quantizer_type; + // the oversample option of CREATE + int oversample; + // set by the 'oversample=N' command; 0 means the CREATE value + int oversample_search; +}; +#endif + struct VectorColumnDefinition { char *name; int name_length; size_t dimensions; enum VectorElementType element_type; enum Vec0DistanceMetrics distance_metric; + enum Vec0IndexType index_type; +#if SQLITE_VEC_ENABLE_RESCORE + struct Vec0RescoreConfig rescore; +#endif }; struct Vec0PartitionColumnDefinition { @@ -2814,6 +2873,80 @@ size_t vector_column_byte_size(struct VectorColumnDefinition column) { return vector_byte_size(column.element_type, column.dimensions); } +#if SQLITE_VEC_ENABLE_RESCORE +/** + * @brief Parses the options of `indexed by rescore(...)`, from after its '(' + * through its ')': `quantizer=bit` or `quantizer=int8`, which is required, and + * `oversample=N` with N from 1 to 128, 8 when omitted, separated by commas. + * + * @return SQLITE_OK, or SQLITE_ERROR for any other text. + */ +static int vec0_parse_rescore_options(struct Vec0Scanner *scanner, + struct Vec0RescoreConfig *outConfig) { + struct Vec0Token token; + int hasQuantizer = 0; + outConfig->oversample = 8; + outConfig->oversample_search = 0; + + int rc = vec0_scanner_next(scanner, &token); + if (rc == VEC0_TOKEN_RESULT_SOME && token.token_type == TOKEN_TYPE_RPAREN) { + return SQLITE_ERROR; + } + while (1) { + if (rc != VEC0_TOKEN_RESULT_SOME || + token.token_type != TOKEN_TYPE_IDENTIFIER) { + return SQLITE_ERROR; + } + struct Vec0Token key = token; + rc = vec0_scanner_next(scanner, &token); + if (rc != VEC0_TOKEN_RESULT_SOME || token.token_type != TOKEN_TYPE_EQ) { + return SQLITE_ERROR; + } + rc = vec0_scanner_next(scanner, &token); + if (rc != VEC0_TOKEN_RESULT_SOME) { + return SQLITE_ERROR; + } + if (vec0_token_is_keyword(&key, "quantizer", true)) { + if (token.token_type != TOKEN_TYPE_IDENTIFIER) { + return SQLITE_ERROR; + } + if (vec0_token_is_keyword(&token, "bit", true)) { + outConfig->quantizer_type = VEC0_RESCORE_QUANTIZER_BIT; + } else if (vec0_token_is_keyword(&token, "int8", true)) { + outConfig->quantizer_type = VEC0_RESCORE_QUANTIZER_INT8; + } else { + return SQLITE_ERROR; + } + hasQuantizer = 1; + } else if (vec0_token_is_keyword(&key, "oversample", true)) { + if (token.token_type != TOKEN_TYPE_DIGIT) { + return SQLITE_ERROR; + } + errno = 0; + char *endptr; + long oversample = strtol(token.start, &endptr, 10); + if (errno == ERANGE || endptr != token.end || oversample < 1 || + oversample > 128) { + return SQLITE_ERROR; + } + outConfig->oversample = (int)oversample; + } else { + return SQLITE_ERROR; + } + + rc = vec0_scanner_next(scanner, &token); + if (rc == VEC0_TOKEN_RESULT_SOME && token.token_type == TOKEN_TYPE_RPAREN) { + break; + } + if (rc != VEC0_TOKEN_RESULT_SOME || token.token_type != TOKEN_TYPE_COMMA) { + return SQLITE_ERROR; + } + rc = vec0_scanner_next(scanner, &token); + } + return hasQuantizer ? SQLITE_OK : SQLITE_ERROR; +} +#endif + /** * @brief Parse an vec0 vtab argv[i] column definition and see if * it's a vector column defintion, ex `contents_embedding float[768]`. @@ -2842,6 +2975,11 @@ int vec0_parse_vector_column(const char *source, int source_length, int nameLength; enum VectorElementType elementType; enum Vec0DistanceMetrics distanceMetric = VEC0_DISTANCE_METRIC_L2; + enum Vec0IndexType indexType = VEC0_INDEX_TYPE_FLAT; +#if SQLITE_VEC_ENABLE_RESCORE + struct Vec0RescoreConfig rescoreConfig; + memset(&rescoreConfig, 0, sizeof(rescoreConfig)); +#endif int dimensions; vec0_scanner_init(&scanner, source, source_length); @@ -2951,6 +3089,57 @@ int vec0_parse_vector_column(const char *source, int source_length, return SQLITE_ERROR; } } + // `indexed by flat()` or `indexed by rescore(...)`. No table predates the + // clause, so its keywords match exactly whether or not the parse is strict. + else if (vec0_token_is_keyword(&token, "indexed", true)) { + rc = vec0_scanner_next(&scanner, &token); + if (rc != VEC0_TOKEN_RESULT_SOME || + token.token_type != TOKEN_TYPE_IDENTIFIER || + !vec0_token_is_keyword(&token, "by", true)) { + return SQLITE_ERROR; + } + rc = vec0_scanner_next(&scanner, &token); + if (rc != VEC0_TOKEN_RESULT_SOME || + token.token_type != TOKEN_TYPE_IDENTIFIER) { + return SQLITE_ERROR; + } + if (vec0_token_is_keyword(&token, "flat", true)) { + indexType = VEC0_INDEX_TYPE_FLAT; + rc = vec0_scanner_next(&scanner, &token); + if (rc != VEC0_TOKEN_RESULT_SOME || + token.token_type != TOKEN_TYPE_LPAREN) { + return SQLITE_ERROR; + } + rc = vec0_scanner_next(&scanner, &token); + if (rc != VEC0_TOKEN_RESULT_SOME || + token.token_type != TOKEN_TYPE_RPAREN) { + return SQLITE_ERROR; + } + } +#if SQLITE_VEC_ENABLE_RESCORE + else if (vec0_token_is_keyword(&token, "rescore", true)) { + indexType = VEC0_INDEX_TYPE_RESCORE; + if (elementType != SQLITE_VEC_ELEMENT_TYPE_FLOAT32) { + return SQLITE_ERROR; + } + rc = vec0_scanner_next(&scanner, &token); + if (rc != VEC0_TOKEN_RESULT_SOME || + token.token_type != TOKEN_TYPE_LPAREN) { + return SQLITE_ERROR; + } + if (vec0_parse_rescore_options(&scanner, &rescoreConfig) != SQLITE_OK) { + return SQLITE_ERROR; + } + if (rescoreConfig.quantizer_type == VEC0_RESCORE_QUANTIZER_BIT && + (dimensions % CHAR_BIT) != 0) { + return SQLITE_ERROR; + } + } +#endif + else { + return SQLITE_ERROR; + } + } // unknown key else { return SQLITE_ERROR; @@ -2965,6 +3154,10 @@ int vec0_parse_vector_column(const char *source, int source_length, outColumn->distance_metric = distanceMetric; outColumn->element_type = elementType; outColumn->dimensions = dimensions; + outColumn->index_type = indexType; +#if SQLITE_VEC_ENABLE_RESCORE + outColumn->rescore = rescoreConfig; +#endif return SQLITE_OK; } @@ -4120,6 +4313,18 @@ struct vec0_vtab { // The first numMetadataColumns entries must be freed with sqlite3_free() char *shadowMetadataChunksNames[VEC0_MAX_METADATA_COLUMNS]; +#if SQLITE_VEC_ENABLE_RESCORE + // Name of the rescore chunk shadow table of each vector column indexed by + // rescore(), ie `_rescore_chunks00`, holding its quantized vectors in the + // _vector_chunks layout. NULL for other columns; freed with sqlite3_free(). + char *shadowRescoreChunksNames[VEC0_MAX_VECTOR_COLUMNS]; + + // Name of the rescore vector shadow table of each vector column indexed by + // rescore(), ie `_rescore_vectors00`, holding its float32 vectors by rowid. + // NULL for other columns; freed with sqlite3_free(). + char *shadowRescoreVectorsNames[VEC0_MAX_VECTOR_COLUMNS]; +#endif + struct VectorColumnDefinition vector_columns[VEC0_MAX_VECTOR_COLUMNS]; struct Vec0PartitionColumnDefinition paritition_columns[VEC0_MAX_PARTITION_COLUMNS]; @@ -4227,6 +4432,12 @@ void vec0_free(vec0_vtab *p) { for (int i = 0; i < p->numVectorColumns; i++) { sqlite3_free(p->shadowVectorChunksNames[i]); p->shadowVectorChunksNames[i] = NULL; +#if SQLITE_VEC_ENABLE_RESCORE + sqlite3_free(p->shadowRescoreChunksNames[i]); + p->shadowRescoreChunksNames[i] = NULL; + sqlite3_free(p->shadowRescoreVectorsNames[i]); + p->shadowRescoreVectorsNames[i] = NULL; +#endif sqlite3_free(p->vector_columns[i].name); p->vector_columns[i].name = NULL; } @@ -4253,6 +4464,10 @@ int vec0_num_defined_user_columns(vec0_vtab *p) { p->numMetadataColumns; } +#if SQLITE_VEC_ENABLE_RESCORE +static int rescore_new_chunk(vec0_vtab *p, i64 chunk_rowid); +#endif + /** * @brief Returns the index of the distance hidden column for the given vec0 * table. @@ -4585,9 +4800,23 @@ int vec0_get_vector_data(vec0_vtab *pVtab, i64 rowid, int vector_column_idx, goto cleanup; } - rc = sqlite3_blob_open(p->db, p->schemaName, - p->shadowVectorChunksNames[vector_column_idx], - "vectors", chunk_id, 0, &vectorBlob); + size = vector_column_byte_size(pVtab->vector_columns[vector_column_idx]); +#if SQLITE_VEC_ENABLE_RESCORE + // a rescore() column keeps each float32 vector in a row of its own + if (p->vector_columns[vector_column_idx].index_type == + VEC0_INDEX_TYPE_RESCORE) { + rc = sqlite3_blob_open(p->db, p->schemaName, + p->shadowRescoreVectorsNames[vector_column_idx], + "vector", rowid, 0, &vectorBlob); + blobOffset = 0; + } else +#endif + { + rc = sqlite3_blob_open(p->db, p->schemaName, + p->shadowVectorChunksNames[vector_column_idx], + "vectors", chunk_id, 0, &vectorBlob); + blobOffset = chunk_offset * size; + } if (rc != SQLITE_OK) { vtab_set_error(&pVtab->base, @@ -4597,9 +4826,6 @@ int vec0_get_vector_data(vec0_vtab *pVtab, i64 rowid, int vector_column_idx, goto cleanup; } - size = vector_column_byte_size(pVtab->vector_columns[vector_column_idx]); - blobOffset = chunk_offset * size; - buf = sqlite3_malloc(size); if (!buf) { rc = SQLITE_NOMEM; @@ -5107,7 +5333,8 @@ int vec0_rowids_update_position(vec0_vtab *p, i64 rowid, i64 chunk_rowid, * chunks. * * Inserts a new row into the _chunks table, with blank data, and uses that new - * rowid to insert new blank rows into _vector_chunksXX tables. + * rowid to insert new blank rows into _vector_chunksXX tables, or into the + * _rescore_chunksXX tables of columns indexed by rescore(). * * @param p: vec0 table to add new chunk * @param paritionKeyValues: Array of partition key valeus for the new chunk, if @@ -5190,6 +5417,11 @@ int vec0_new_chunk(vec0_vtab *p, sqlite3_value **partitionKeyValues, continue; } int vector_column_idx = p->user_column_idxs[i]; + // a column indexed by anything but flat() keeps its vectors elsewhere + if (p->vector_columns[vector_column_idx].index_type != + VEC0_INDEX_TYPE_FLAT) { + continue; + } i64 vectorsSize = p->chunk_size * vector_column_byte_size( p->vector_columns[vector_column_idx]); @@ -5225,6 +5457,13 @@ int vec0_new_chunk(vec0_vtab *p, sqlite3_value **partitionKeyValues, } } +#if SQLITE_VEC_ENABLE_RESCORE + rc = rescore_new_chunk(p, rowid); + if (rc != SQLITE_OK) { + return rc; + } +#endif + // Step 3: Create new metadata chunks for each metadata column for (int i = 0; i < vec0_num_defined_user_columns(p); i++) { if (p->user_column_kinds[i] != SQLITE_VEC0_USER_COLUMN_KIND_METADATA) { @@ -5621,6 +5860,26 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, goto error; } +#if SQLITE_VEC_ENABLE_RESCORE + for (int i = 0; i < numVectorColumns; i++) { + if (pNew->vector_columns[i].index_type != VEC0_INDEX_TYPE_RESCORE) { + continue; + } + if (numMetadataColumns > 0) { + *pzErr = sqlite3_mprintf( + VEC_CONSTRUCTOR_ERROR + "Metadata columns are not supported with rescore indexes"); + goto error; + } + if (numPartitionColumns > 0) { + *pzErr = sqlite3_mprintf( + VEC_CONSTRUCTOR_ERROR + "Partition key columns are not supported with rescore indexes"); + goto error; + } + } +#endif + const char *schemaName = argv[1]; const char *tableName = argv[2]; @@ -5753,6 +6012,20 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, if (!pNew->shadowVectorChunksNames[i]) { goto error; } +#if SQLITE_VEC_ENABLE_RESCORE + if (pNew->vector_columns[i].index_type == VEC0_INDEX_TYPE_RESCORE) { + pNew->shadowRescoreChunksNames[i] = + sqlite3_mprintf("%s_rescore_chunks%02d", tableName, i); + if (!pNew->shadowRescoreChunksNames[i]) { + goto error; + } + pNew->shadowRescoreVectorsNames[i] = + sqlite3_mprintf("%s_rescore_vectors%02d", tableName, i); + if (!pNew->shadowRescoreVectorsNames[i]) { + goto error; + } + } +#endif } for (int i = 0; i < pNew->numMetadataColumns; i++) { pNew->shadowMetadataChunksNames[i] = @@ -5878,6 +6151,10 @@ 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++) { + // a column indexed by anything but flat() keeps its vectors elsewhere + if (pNew->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) { + continue; + } char *zSql = sqlite3_mprintf(VEC0_SHADOW_VECTOR_N_CREATE, pNew->schemaName, pNew->tableName, i); if (!zSql) { @@ -5896,6 +6173,37 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_finalize(stmt); } +#if SQLITE_VEC_ENABLE_RESCORE + for (int i = 0; i < pNew->numVectorColumns; i++) { + if (pNew->vector_columns[i].index_type != VEC0_INDEX_TYPE_RESCORE) { + continue; + } + const char *zCreate[] = { + "CREATE TABLE \"%w\".\"%w_rescore_chunks%02d\"" + "(rowid INTEGER PRIMARY KEY, vectors BLOB NOT NULL)", + "CREATE TABLE \"%w\".\"%w_rescore_vectors%02d\"" + "(rowid INTEGER PRIMARY KEY, vector BLOB NOT NULL)", + }; + const char *zShadow[] = {"_rescore_chunks", "_rescore_vectors"}; + for (size_t j = 0; j < countof(zCreate); j++) { + char *zSql = + sqlite3_mprintf(zCreate[j], pNew->schemaName, pNew->tableName, i); + if (!zSql) { + goto error; + } + rc = sqlite3_prepare_v2(db, zSql, -1, &stmt, 0); + sqlite3_free((void *)zSql); + if ((rc != SQLITE_OK) || (sqlite3_step(stmt) != SQLITE_DONE)) { + sqlite3_finalize(stmt); + *pzErr = sqlite3_mprintf("Could not create '%s%02d' shadow table: %s", + zShadow[j], i, sqlite3_errmsg(db)); + goto error; + } + sqlite3_finalize(stmt); + } + } +#endif + for (int i = 0; i < pNew->numMetadataColumns; i++) { char *zSql = sqlite3_mprintf("CREATE TABLE " VEC0_SHADOW_METADATA_N_NAME @@ -6038,15 +6346,29 @@ static int vec0Destroy(sqlite3_vtab *pVtab) { sqlite3_finalize(stmt); for (int i = 0; i < p->numVectorColumns; i++) { - zSql = sqlite3_mprintf("DROP TABLE \"%w\".\"%w\"", p->schemaName, - p->shadowVectorChunksNames[i]); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, 0); - sqlite3_free((void *)zSql); - if ((rc != SQLITE_OK) || (sqlite3_step(stmt) != SQLITE_DONE)) { - rc = SQLITE_ERROR; - goto done; + const char *shadowNames[] = { + p->vector_columns[i].index_type == VEC0_INDEX_TYPE_FLAT + ? p->shadowVectorChunksNames[i] + : NULL, +#if SQLITE_VEC_ENABLE_RESCORE + p->shadowRescoreChunksNames[i], + p->shadowRescoreVectorsNames[i], +#endif + }; + for (size_t j = 0; j < countof(shadowNames); j++) { + if (!shadowNames[j]) { + continue; + } + zSql = sqlite3_mprintf("DROP TABLE \"%w\".\"%w\"", p->schemaName, + shadowNames[j]); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, 0); + sqlite3_free((void *)zSql); + if ((rc != SQLITE_OK) || (sqlite3_step(stmt) != SQLITE_DONE)) { + rc = SQLITE_ERROR; + goto done; + } + sqlite3_finalize(stmt); } - sqlite3_finalize(stmt); } if (p->numAuxiliaryColumns > 0) { @@ -8924,67 +9246,711 @@ static int vec0_knn_rowids_append(vec0_vtab *p, sqlite3_value *value, return array_append(rowids, &rowid); } -int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum, - const char *idxStr, int argc, sqlite3_value **argv) { - assert(argc == (int)((strlen(idxStr) - 1) / 4)); - int rc; - struct vec0_query_knn_data *knn_data; - - int vectorColumnIdx = idxNum; - struct VectorColumnDefinition *vector_column = - &p->vector_columns[vectorColumnIdx]; +#if SQLITE_VEC_ENABLE_RESCORE +// A vector column indexed by rescore() keeps two shadow tables: +// _rescore_chunksNN holds its vectors quantized, in the layout of +// _vector_chunksNN, for the KNN to scan, and _rescore_vectorsNN holds its +// float32 vectors by rowid, for the KNN to rank the candidates the scan finds. +// https://github.com/asg017/sqlite-vec/blob/04d28bd/sqlite-vec-rescore.c - struct Array *arrayRowidsIn = NULL; - sqlite3_stmt *stmtChunks = NULL; - void *queryVector; - size_t dimensions; - enum VectorElementType elementType; - vector_cleanup queryVectorCleanup = vector_cleanup_noop; - char *pzError; - knn_data = sqlite3_malloc(sizeof(*knn_data)); - if (!knn_data) { - return SQLITE_NOMEM; +size_t rescore_quantized_byte_size(struct VectorColumnDefinition *col) { + switch (col->rescore.quantizer_type) { + case VEC0_RESCORE_QUANTIZER_BIT: + return col->dimensions / CHAR_BIT; + case VEC0_RESCORE_QUANTIZER_INT8: + return col->dimensions; + default: + return 0; } - memset(knn_data, 0, sizeof(*knn_data)); - // array of `struct Vec0MetadataIn`, IF there are any `xxx in (...)` metadata - // constraints - struct Array *aMetadataIn = NULL; +} - int query_idx = -1; - int k_idx = -1; - int rowid_in_idx = -1; - int rowid_eq_idx = -1; - int mmr_lambda_idx = -1; - for (int i = 0; i < argc; i++) { - if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_MATCH) { - query_idx = i; +// One bit per dimension, set when the value is >= 0. +void rescore_quantize_float_to_bit(const float *src, uint8_t *dst, + size_t dimensions) { + memset(dst, 0, dimensions / CHAR_BIT); + for (size_t i = 0; i < dimensions; i++) { + if (src[i] >= 0.0f) { + dst[i / CHAR_BIT] |= (1 << (i % CHAR_BIT)); } - if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_K) { - k_idx = i; + } +} + +// Maps [-1, 1] onto [-128, 127] in steps of 2/255, truncating toward zero, and +// clamps values outside it, NaN to 127. +void rescore_quantize_float_to_int8(const float *src, int8_t *dst, + size_t dimensions) { + float step = 2.0f / 255.0f; + for (size_t i = 0; i < dimensions; i++) { + float v = (src[i] - (-1.0f)) / step - 128.0f; + if (!(v <= 127.0f)) + v = 127.0f; + if (!(v >= -128.0f)) + v = -128.0f; + dst[i] = (int8_t)v; + } +} + +/** + * Inserts the row of chunk chunk_rowid, its quantized vectors zeroed, into the + * _rescore_chunksNN table of each column indexed by rescore(). + */ +static int rescore_new_chunk(vec0_vtab *p, i64 chunk_rowid) { + for (int i = 0; i < p->numVectorColumns; i++) { + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_RESCORE) { + continue; } - if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_ROWID_IN) { - rowid_in_idx = i; + size_t quantized_size = rescore_quantized_byte_size(&p->vector_columns[i]); + i64 blob_size = (i64)p->chunk_size * (i64)quantized_size; + + char *zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(rowid, vectors) VALUES (?, ?)", + p->schemaName, p->shadowRescoreChunksNames[i]); + if (!zSql) { + return SQLITE_NOMEM; } - if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_ROWID_EQ) { - rowid_eq_idx = i; + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); + sqlite3_free(zSql); + if (rc != SQLITE_OK) { + sqlite3_finalize(stmt); + return rc; } - if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_MMR_LAMBDA) { - mmr_lambda_idx = i; + sqlite3_bind_int64(stmt, 1, chunk_rowid); + sqlite3_bind_zeroblob64(stmt, 2, blob_size); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (rc != SQLITE_DONE) { + return vec0_error_rc(rc); } } - assert(query_idx >= 0); - assert(k_idx >= 0); + return SQLITE_OK; +} - // make sure the query vector matches the vector column (type dimensions etc.) - rc = vector_from_value(argv[query_idx], &queryVector, &dimensions, - &elementType, &queryVectorCleanup, &pzError); +/** + * Writes the quantized form of vector, a float32 vector of the column i + * indexed by rescore(), to the slot chunk_offset of chunk chunk_id in the + * column's _rescore_chunksNN table. + */ +static int rescore_write_quantized(vec0_vtab *p, int i, i64 chunk_id, + i64 chunk_offset, const void *vector) { + struct VectorColumnDefinition *col = &p->vector_columns[i]; + size_t qsize = rescore_quantized_byte_size(col); + void *qbuf = sqlite3_malloc(qsize); + if (!qbuf) { + return SQLITE_NOMEM; + } + switch (col->rescore.quantizer_type) { + case VEC0_RESCORE_QUANTIZER_BIT: + rescore_quantize_float_to_bit((const float *)vector, (uint8_t *)qbuf, + col->dimensions); + break; + case VEC0_RESCORE_QUANTIZER_INT8: + rescore_quantize_float_to_int8((const float *)vector, (int8_t *)qbuf, + col->dimensions); + break; + } + sqlite3_blob *blob = NULL; + int rc = + sqlite3_blob_open(p->db, p->schemaName, p->shadowRescoreChunksNames[i], + "vectors", chunk_id, 1, &blob); if (rc != SQLITE_OK) { - vtab_set_error(&p->base, - "Query vector on the \"%.*s\" column is invalid: %z", - vector_column->name_length, vector_column->name, pzError); - rc = SQLITE_ERROR; - goto cleanup; + sqlite3_free(qbuf); + return vec0_error_rc(rc); + } + rc = sqlite3_blob_write(blob, qbuf, qsize, chunk_offset * qsize); + sqlite3_free(qbuf); + int brc = sqlite3_blob_close(blob); + if (rc != SQLITE_OK) { + return rc; + } + return brc; +} + +/** + * Writes the vectors of a new row to the columns indexed by rescore(): each + * quantized to the row's slot in _rescore_chunksNN, and as is to a new row of + * _rescore_vectorsNN. + */ +static int rescore_on_insert(vec0_vtab *p, i64 chunk_rowid, i64 chunk_offset, + i64 rowid, void *vectorDatas[]) { + for (int i = 0; i < p->numVectorColumns; i++) { + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_RESCORE) { + continue; + } + int rc = rescore_write_quantized(p, i, chunk_rowid, chunk_offset, + vectorDatas[i]); + if (rc != SQLITE_OK) { + return rc; + } + + char *zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(rowid, vector) VALUES (?, ?)", p->schemaName, + p->shadowRescoreVectorsNames[i]); + if (!zSql) { + return SQLITE_NOMEM; + } + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); + sqlite3_free(zSql); + if (rc != SQLITE_OK) { + sqlite3_finalize(stmt); + return rc; + } + sqlite3_bind_int64(stmt, 1, rowid); + sqlite3_bind_blob(stmt, 2, vectorDatas[i], + vector_column_byte_size(p->vector_columns[i]), + SQLITE_TRANSIENT); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (rc != SQLITE_DONE) { + return vec0_error_rc(rc); + } + } + return SQLITE_OK; +} + +/** + * Zeroes a deleted row's quantized vectors in _rescore_chunksNN, and deletes + * its row of _rescore_vectorsNN, for each column indexed by rescore(). + */ +static int rescore_on_delete(vec0_vtab *p, i64 chunk_id, i64 chunk_offset, + i64 rowid) { + for (int i = 0; i < p->numVectorColumns; i++) { + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_RESCORE) { + continue; + } + size_t qsize = rescore_quantized_byte_size(&p->vector_columns[i]); + void *zeroBuf = sqlite3_malloc(qsize); + if (!zeroBuf) { + return SQLITE_NOMEM; + } + memset(zeroBuf, 0, qsize); + + sqlite3_blob *blob = NULL; + int rc = + sqlite3_blob_open(p->db, p->schemaName, p->shadowRescoreChunksNames[i], + "vectors", chunk_id, 1, &blob); + if (rc != SQLITE_OK) { + sqlite3_free(zeroBuf); + return vec0_error_rc(rc); + } + rc = sqlite3_blob_write(blob, zeroBuf, qsize, chunk_offset * qsize); + sqlite3_free(zeroBuf); + int brc = sqlite3_blob_close(blob); + if (rc != SQLITE_OK) { + return rc; + } + if (brc != SQLITE_OK) { + return brc; + } + + char *zSql = + sqlite3_mprintf("DELETE FROM \"%w\".\"%w\" WHERE rowid = ?", + p->schemaName, p->shadowRescoreVectorsNames[i]); + if (!zSql) { + return SQLITE_NOMEM; + } + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); + sqlite3_free(zSql); + if (rc != SQLITE_OK) { + return rc; + } + sqlite3_bind_int64(stmt, 1, rowid); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (rc != SQLITE_DONE) { + return vec0_error_rc(rc); + } + } + return SQLITE_OK; +} + +/** + * Deletes chunk chunk_id from the _rescore_chunksNN table of each column + * indexed by rescore(). rescore_on_delete() already deleted the chunk's rows of + * _rescore_vectorsNN. + */ +static int rescore_delete_chunk(vec0_vtab *p, i64 chunk_id) { + for (int i = 0; i < p->numVectorColumns; i++) { + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_RESCORE) { + continue; + } + char *zSql = sqlite3_mprintf("DELETE FROM \"%w\".\"%w\" WHERE rowid = ?", + p->schemaName, p->shadowRescoreChunksNames[i]); + if (!zSql) { + return SQLITE_NOMEM; + } + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); + sqlite3_free(zSql); + if (rc != SQLITE_OK) { + return rc; + } + sqlite3_bind_int64(stmt, 1, chunk_id); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (rc != SQLITE_DONE) { + return vec0_error_rc(rc); + } + } + return SQLITE_OK; +} + +/** + * The KNN of a column indexed by rescore(). It scans the quantized vectors of + * the chunks stmtChunks returns for the k * oversample rows nearest the + * quantized query vector, ranks those candidates by the distance of their + * float32 vectors to the query vector, and returns the k nearest that meet the + * query's distance constraints. Takes and returns what + * vec0Filter_knn_chunks_iter() does. + * + * Distance constraints apply to the float32 distances of the candidates, as in + * https://github.com/asg017/sqlite-vec/pull/309 + */ +static int rescore_knn(vec0_vtab *p, sqlite3_stmt *stmtChunks, + struct VectorColumnDefinition *vector_column, + int vectorColumnIdx, struct Array *arrayRowidsIn, + const char *idxStr, int argc, sqlite3_value **argv, + void *queryVector, i64 k, i64 **out_topk_rowids, + f32 **out_topk_distances, i64 *out_used) { + int rc = SQLITE_OK; + 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; + } + + size_t qdim = vector_column->dimensions; + size_t qsize = rescore_quantized_byte_size(vector_column); + size_t fsize = vector_column_byte_size(*vector_column); + + sqlite3_blob *blobQ = NULL; + sqlite3_blob *blobFloat = NULL; + i64 *cand_rowids = NULL; + f32 *cand_distances = NULL; + i64 *tmp_rowids = NULL; + f32 *tmp_distances = NULL; + f32 *chunk_distances = NULL; + i32 *chunk_topk_idxs = NULL; + u8 *b = NULL; + u8 *bmRowids = NULL; + void *baseVectors = NULL; + f32 *float_distances = NULL; + void *fBuf = NULL; + i64 *out_rowids = NULL; + f32 *out_distances = NULL; + + void *quantizedQuery = sqlite3_malloc(qsize); + if (!quantizedQuery) { + return SQLITE_NOMEM; + } + switch (vector_column->rescore.quantizer_type) { + case VEC0_RESCORE_QUANTIZER_BIT: + rescore_quantize_float_to_bit((const float *)queryVector, + (uint8_t *)quantizedQuery, qdim); + break; + case VEC0_RESCORE_QUANTIZER_INT8: + rescore_quantize_float_to_int8((const float *)queryVector, + (int8_t *)quantizedQuery, qdim); + break; + } + + // Phase 1: scan the quantized chunks for the k * oversample nearest rows + cand_rowids = sqlite3_malloc(k_oversample * sizeof(i64)); + cand_distances = sqlite3_malloc(k_oversample * sizeof(f32)); + tmp_rowids = sqlite3_malloc(k_oversample * sizeof(i64)); + tmp_distances = sqlite3_malloc(k_oversample * sizeof(f32)); + chunk_distances = sqlite3_malloc(p->chunk_size * sizeof(f32)); + chunk_topk_idxs = sqlite3_malloc(k_oversample * sizeof(i32)); + b = bitmap_new(p->chunk_size); + baseVectors = sqlite3_malloc((i64)p->chunk_size * (i64)qsize); + if (!cand_rowids || !cand_distances || !tmp_rowids || !tmp_distances || + !chunk_distances || !chunk_topk_idxs || !b || !baseVectors) { + rc = SQLITE_NOMEM; + goto cleanup; + } + memset(cand_rowids, 0, k_oversample * sizeof(i64)); + memset(cand_distances, 0, k_oversample * sizeof(f32)); + + if (arrayRowidsIn) { + bmRowids = bitmap_new(p->chunk_size); + if (!bmRowids) { + rc = SQLITE_NOMEM; + goto cleanup; + } + } + + i64 cand_used = 0; + while (1) { + rc = sqlite3_step(stmtChunks); + if (rc == SQLITE_DONE) { + break; + } + if (rc != SQLITE_ROW) { + vtab_set_error(&p->base, "chunks iter error"); + rc = vec0_error_rc(rc); + goto cleanup; + } + + i64 chunk_id = sqlite3_column_int64(stmtChunks, 0); + unsigned char *chunkValidity = + (unsigned char *)sqlite3_column_blob(stmtChunks, 1); + i64 validitySize = sqlite3_column_bytes(stmtChunks, 1); + if (validitySize != p->chunk_size / CHAR_BIT) { + vtab_set_error( + &p->base, + "chunk validity size doesn't match - expected %lld, found %lld", + p->chunk_size / CHAR_BIT, validitySize); + rc = SQLITE_ERROR; + goto cleanup; + } + i64 *chunkRowids = (i64 *)sqlite3_column_blob(stmtChunks, 2); + i64 rowidsSize = sqlite3_column_bytes(stmtChunks, 2); + if (rowidsSize != (i64)(p->chunk_size * sizeof(i64))) { + vtab_set_error(&p->base, + "chunk rowids size doesn't match - expected %lld, found " + "%lld", + p->chunk_size * sizeof(i64), rowidsSize); + rc = SQLITE_ERROR; + goto cleanup; + } + + memset(chunk_distances, 0, p->chunk_size * sizeof(f32)); + memset(chunk_topk_idxs, 0, k_oversample * sizeof(i32)); + bitmap_copy(b, chunkValidity, p->chunk_size); + + if (arrayRowidsIn) { + bitmap_clear(bmRowids, p->chunk_size); + for (int j = 0; j < p->chunk_size; j++) { + if (!bitmap_get(chunkValidity, j)) { + continue; + } + i64 rid = chunkRowids[j]; + void *found = bsearch(&rid, arrayRowidsIn->z, arrayRowidsIn->length, + sizeof(i64), _cmp); + bitmap_set(bmRowids, j, found ? 1 : 0); + } + bitmap_and_inplace(b, bmRowids, p->chunk_size); + } + + // A chunk whose rowid filter keeps no row offers no candidate, so its + // vectors are never read. + if (!bitmap_any(b, p->chunk_size)) { + continue; + } + + rc = sqlite3_blob_open(p->db, p->schemaName, + p->shadowRescoreChunksNames[vectorColumnIdx], + "vectors", chunk_id, 0, &blobQ); + if (rc != SQLITE_OK) { + vtab_set_error(&p->base, + "could not open rescore vectors blob for chunk %lld", + chunk_id); + rc = vec0_error_rc(rc); + goto cleanup; + } + i64 currentBaseVectorsSize = sqlite3_blob_bytes(blobQ); + i64 expectedBaseVectorsSize = (i64)p->chunk_size * (i64)qsize; + if (currentBaseVectorsSize != expectedBaseVectorsSize) { + vtab_set_error( + &p->base, + "rescore vectors blob size doesn't match - expected %lld, found %lld", + expectedBaseVectorsSize, currentBaseVectorsSize); + rc = SQLITE_ERROR; + goto cleanup; + } + rc = sqlite3_blob_read(blobQ, baseVectors, currentBaseVectorsSize, 0); + // blobQ is opened read-only, so closing it never fails + sqlite3_blob_close(blobQ); + blobQ = NULL; + if (rc != SQLITE_OK) { + vtab_set_error(&p->base, "rescore vectors blob read error for %lld", + chunk_id); + rc = SQLITE_ERROR; + goto cleanup; + } + + for (int j = 0; j < p->chunk_size; j++) { + if (!bitmap_get(b, j)) { + continue; + } + 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)); + dist = distance_hamming(base_j, (u8 *)quantizedQuery, &qdim); + break; + } + case VEC0_RESCORE_QUANTIZER_INT8: { + const i8 *base_j = ((i8 *)baseVectors) + (j * qdim); + switch (vector_column->distance_metric) { + case VEC0_DISTANCE_METRIC_L2: + dist = distance_l2_sqr_int8(base_j, (i8 *)quantizedQuery, &qdim); + break; + case VEC0_DISTANCE_METRIC_COSINE: + dist = distance_cosine_int8(base_j, (i8 *)quantizedQuery, &qdim); + break; + case VEC0_DISTANCE_METRIC_L1: + dist = (f32)distance_l1_int8(base_j, (i8 *)quantizedQuery, &qdim); + break; + } + break; + } + } + chunk_distances[j] = dist; + } + + int used1; + min_idx(chunk_distances, p->chunk_size, b, chunk_topk_idxs, + min(k_oversample, p->chunk_size), &used1); + + i64 merged_used; + merge_sorted_lists(cand_distances, cand_rowids, cand_used, chunk_distances, + chunkRowids, chunk_topk_idxs, + min(min(k_oversample, p->chunk_size), used1), + tmp_distances, tmp_rowids, k_oversample, &merged_used); + + for (i64 j = 0; j < merged_used; j++) { + cand_rowids[j] = tmp_rowids[j]; + cand_distances[j] = tmp_distances[j]; + } + cand_used = merged_used; + } + rc = SQLITE_OK; + + // Phase 2: rank the candidates by the distances of their float32 vectors, + // which blob_reopen() reads from _rescore_vectorsNN by rowid directly + if (cand_used > 0) { + float_distances = sqlite3_malloc(cand_used * sizeof(f32)); + fBuf = sqlite3_malloc(fsize); + if (!float_distances || !fBuf) { + rc = SQLITE_NOMEM; + goto cleanup; + } + for (i64 j = 0; j < cand_used; j++) { + if (j == 0) { + rc = sqlite3_blob_open(p->db, p->schemaName, + p->shadowRescoreVectorsNames[vectorColumnIdx], + "vector", cand_rowids[0], 0, &blobFloat); + } else { + rc = sqlite3_blob_reopen(blobFloat, cand_rowids[j]); + } + if (rc != SQLITE_OK) { + vtab_set_error(&p->base, + "could not read the rescore vector of row %lld", + cand_rowids[j]); + rc = vec0_error_rc(rc); + goto cleanup; + } + rc = sqlite3_blob_read(blobFloat, fBuf, fsize, 0); + if (rc != SQLITE_OK) { + vtab_set_error(&p->base, + "could not read the rescore vector of row %lld", + cand_rowids[j]); + rc = SQLITE_ERROR; + goto cleanup; + } + float_distances[j] = + vec0_compute_distance(vector_column, fBuf, queryVector); + } + + for (int c = 0; c < argc; c++) { + int idx = 1 + (c * 4); + if (idxStr[idx + 0] != VEC0_IDXSTR_KIND_KNN_DISTANCE_CONSTRAINT) { + continue; + } + vec0_distance_constraint_operator op = idxStr[idx + 1]; + int type = sqlite3_value_type(argv[c]); + if (type != SQLITE_INTEGER && type != SQLITE_FLOAT) { + // No distance compares with NULL, and every distance is less than any + // TEXT or BLOB. + int keep = type != SQLITE_NULL && (op == VEC0_DISTANCE_CONSTRAINT_LT || + op == VEC0_DISTANCE_CONSTRAINT_LE); + if (!keep) { + cand_used = 0; + } + continue; + } + f32 target = (f32)sqlite3_value_double(argv[c]); + i64 kept = 0; + for (i64 j = 0; j < cand_used; j++) { + int pass; + switch (op) { + case VEC0_DISTANCE_CONSTRAINT_GE: + pass = float_distances[j] >= target; + break; + case VEC0_DISTANCE_CONSTRAINT_GT: + pass = float_distances[j] > target; + break; + case VEC0_DISTANCE_CONSTRAINT_LE: + pass = float_distances[j] <= target; + break; + case VEC0_DISTANCE_CONSTRAINT_LT: + pass = float_distances[j] < target; + break; + default: + pass = 1; + break; + } + if (pass) { + cand_rowids[kept] = cand_rowids[j]; + float_distances[kept] = float_distances[j]; + kept++; + } + } + cand_used = kept; + } + + // sort by float32 distance + for (i64 a = 0; a + 1 < cand_used; a++) { + i64 minIdx = a; + for (i64 c = a + 1; c < cand_used; c++) { + if (float_distances[c] < float_distances[minIdx]) { + minIdx = c; + } + } + if (minIdx != a) { + f32 td = float_distances[a]; + float_distances[a] = float_distances[minIdx]; + float_distances[minIdx] = td; + i64 tr = cand_rowids[a]; + cand_rowids[a] = cand_rowids[minIdx]; + cand_rowids[minIdx] = tr; + } + } + } + + i64 result_k = min(k, cand_used); + if (result_k > 0) { + out_rowids = sqlite3_malloc(result_k * sizeof(i64)); + out_distances = sqlite3_malloc(result_k * sizeof(f32)); + if (!out_rowids || !out_distances) { + sqlite3_free(out_rowids); + sqlite3_free(out_distances); + rc = SQLITE_NOMEM; + goto cleanup; + } + for (i64 j = 0; j < result_k; j++) { + out_rowids[j] = cand_rowids[j]; + out_distances[j] = float_distances[j]; + } + } + *out_topk_rowids = out_rowids; + *out_topk_distances = out_distances; + *out_used = result_k; + +cleanup: + // both blobs are opened read-only, so closing them never fails + sqlite3_blob_close(blobQ); + sqlite3_blob_close(blobFloat); + sqlite3_free(quantizedQuery); + sqlite3_free(cand_rowids); + sqlite3_free(cand_distances); + sqlite3_free(tmp_rowids); + sqlite3_free(tmp_distances); + sqlite3_free(chunk_distances); + sqlite3_free(chunk_topk_idxs); + sqlite3_free(b); + sqlite3_free(bmRowids); + sqlite3_free(baseVectors); + sqlite3_free(float_distances); + sqlite3_free(fBuf); + return rc; +} + +/** + * The command `oversample=N` sets the oversample of every column indexed by + * rescore() for the KNN queries of this connection, in place of the one its + * CREATE gave. Returns SQLITE_EMPTY for any other command. + */ +static int rescore_handle_command(vec0_vtab *p, const char *command) { + static const char zKey[] = "oversample="; + if (strncmp(command, zKey, sizeof(zKey) - 1) != 0) { + return SQLITE_EMPTY; + } + const char *zValue = command + sizeof(zKey) - 1; + char *zEnd; + errno = 0; + long oversample = strtol(zValue, &zEnd, 10); + if (!(*zValue >= '0' && *zValue <= '9') || *zEnd != '\0' || errno == ERANGE || + oversample < 1 || oversample > INT_MAX) { + 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 = (int)oversample; + } + } + return SQLITE_OK; +} +#endif + +int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum, + const char *idxStr, int argc, sqlite3_value **argv) { + assert(argc == (int)((strlen(idxStr) - 1) / 4)); + int rc; + struct vec0_query_knn_data *knn_data; + + int vectorColumnIdx = idxNum; + struct VectorColumnDefinition *vector_column = + &p->vector_columns[vectorColumnIdx]; + + struct Array *arrayRowidsIn = NULL; + sqlite3_stmt *stmtChunks = NULL; + void *queryVector; + size_t dimensions; + enum VectorElementType elementType; + vector_cleanup queryVectorCleanup = vector_cleanup_noop; + char *pzError; + knn_data = sqlite3_malloc(sizeof(*knn_data)); + if (!knn_data) { + return SQLITE_NOMEM; + } + memset(knn_data, 0, sizeof(*knn_data)); + // array of `struct Vec0MetadataIn`, IF there are any `xxx in (...)` metadata + // constraints + struct Array *aMetadataIn = NULL; + + int query_idx = -1; + int k_idx = -1; + int rowid_in_idx = -1; + int rowid_eq_idx = -1; + int mmr_lambda_idx = -1; + for (int i = 0; i < argc; i++) { + if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_MATCH) { + query_idx = i; + } + if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_K) { + k_idx = i; + } + if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_ROWID_IN) { + rowid_in_idx = i; + } + if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_ROWID_EQ) { + rowid_eq_idx = i; + } + if (idxStr[1 + (i * 4)] == VEC0_IDXSTR_KIND_KNN_MMR_LAMBDA) { + mmr_lambda_idx = i; + } + } + assert(query_idx >= 0); + assert(k_idx >= 0); + + // make sure the query vector matches the vector column (type dimensions etc.) + rc = vector_from_value(argv[query_idx], &queryVector, &dimensions, + &elementType, &queryVectorCleanup, &pzError); + + if (rc != SQLITE_OK) { + vtab_set_error(&p->base, + "Query vector on the \"%.*s\" column is invalid: %z", + vector_column->name_length, vector_column->name, pzError); + rc = SQLITE_ERROR; + goto cleanup; } if (elementType != vector_column->element_type) { vtab_set_error( @@ -9256,10 +10222,19 @@ int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum, i64 *topk_rowids = NULL; f32 *topk_distances = NULL; i64 k_used = 0; - rc = vec0Filter_knn_chunks_iter(p, stmtChunks, vector_column, vectorColumnIdx, - arrayRowidsIn, aMetadataIn, idxStr, argc, - argv, queryVector, k, &topk_rowids, - &topk_distances, &k_used); +#if SQLITE_VEC_ENABLE_RESCORE + if (vector_column->index_type == VEC0_INDEX_TYPE_RESCORE) { + rc = rescore_knn(p, stmtChunks, vector_column, vectorColumnIdx, + arrayRowidsIn, idxStr, argc, argv, queryVector, k, + &topk_rowids, &topk_distances, &k_used); + } else +#endif + { + rc = vec0Filter_knn_chunks_iter(p, stmtChunks, vector_column, + vectorColumnIdx, arrayRowidsIn, aMetadataIn, + idxStr, argc, argv, queryVector, k, + &topk_rowids, &topk_distances, &k_used); + } if (rc != SQLITE_OK) { goto cleanup; } @@ -9652,8 +10627,13 @@ static int vec0Column_fullscan(vec0_vtab *pVtab, vec0_cursor *pCur, void *v; int sz; int vector_idx = vec0_column_idx_to_vector_idx(pVtab, i); - int rc = vec0_fullscan_read_vector(pVtab, pCur->fullscan_data, vector_idx, - &v, &sz); + // Only a flat column keeps its vectors in _vector_chunks; the other index + // types keep them in tables of their own, read per row. + int rc = + pVtab->vector_columns[vector_idx].index_type == VEC0_INDEX_TYPE_FLAT + ? vec0_fullscan_read_vector(pVtab, pCur->fullscan_data, vector_idx, + &v, &sz) + : vec0_get_vector_data(pVtab, rowid, vector_idx, &v, &sz); if (rc != SQLITE_OK) { return rc; } @@ -10206,6 +11186,10 @@ 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++) { + // a column indexed by anything but flat() keeps its vectors elsewhere + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) { + continue; + } sqlite3_blob *blobVectors; rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowVectorChunksNames[i], "vectors", chunk_rowid, 1, &blobVectors); @@ -10745,6 +11729,13 @@ int vec0Update_Insert(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, goto cleanup; } +#if SQLITE_VEC_ENABLE_RESCORE + rc = rescore_on_insert(p, chunk_rowid, chunk_offset, rowid, vectorDatas); + if (rc != SQLITE_OK) { + goto cleanup; + } +#endif + if (p->numAuxiliaryColumns > 0) { sqlite3_stmt *stmt; sqlite3_str *s = sqlite3_str_new(NULL); @@ -10982,6 +11973,10 @@ int vec0Update_Delete_ClearRowid(vec0_vtab *p, i64 chunk_id, i64 chunk_offset) { int vec0Update_Delete_ClearVectors(vec0_vtab *p, i64 chunk_id, i64 chunk_offset) { for (int i = 0; i < p->numVectorColumns; i++) { + // a column indexed by anything but flat() keeps its vectors elsewhere + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) { + continue; + } int rc; sqlite3_blob *blobVectors = NULL; @@ -11146,8 +12141,8 @@ int vec0Update_Delete_ClearMetadata(vec0_vtab *p, int metadata_idx, i64 rowid, } // Once a DELETE has cleared the last validity bit of its chunk, delete the -// chunk's rows from _chunks, _vector_chunksNN and _metadatachunksNN, so their -// pages are free without an optimize. +// chunk's rows from _chunks, _vector_chunksNN, _rescore_chunksNN and +// _metadatachunksNN, so their pages are free without an optimize. // https://github.com/asg017/sqlite-vec/commit/cb147c8 int vec0Update_Delete_DeleteChunk(vec0_vtab *p, i64 chunk_id) { int rc; @@ -11173,6 +12168,10 @@ int vec0Update_Delete_DeleteChunk(vec0_vtab *p, i64 chunk_id) { } for (int i = 0; i < p->numVectorColumns; i++) { + // a column indexed by anything but flat() keeps its vectors elsewhere + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_FLAT) { + continue; + } zSql = sqlite3_mprintf("DELETE FROM " VEC0_SHADOW_VECTOR_N_NAME " WHERE rowid = ?", p->schemaName, p->tableName, i); @@ -11192,6 +12191,13 @@ int vec0Update_Delete_DeleteChunk(vec0_vtab *p, i64 chunk_id) { } } +#if SQLITE_VEC_ENABLE_RESCORE + rc = rescore_delete_chunk(p, chunk_id); + if (rc != SQLITE_OK) { + return rc; + } +#endif + for (int i = 0; i < p->numMetadataColumns; i++) { zSql = sqlite3_mprintf("DELETE FROM " VEC0_SHADOW_METADATA_N_NAME " WHERE rowid = ?", @@ -11263,6 +12269,15 @@ int vec0Update_Delete(sqlite3_vtab *pVTab, sqlite3_value *idValue) { return rc; } +#if SQLITE_VEC_ENABLE_RESCORE + // 4b. zero out the quantized vectors in rescore chunks, and delete the row's + // rescore vectors + rc = rescore_on_delete(p, chunk_id, chunk_offset, rowid); + if (rc != SQLITE_OK) { + return rc; + } +#endif + // 5. delete from _rowids table rc = vec0Update_Delete_DeleteRowids(p, rowid); if (rc != SQLITE_OK) { @@ -11319,7 +12334,8 @@ int vec0Update_UpdateAuxColumn(vec0_vtab *p, int auxiliary_column_idx, } int vec0Update_UpdateVectorColumn(vec0_vtab *p, i64 chunk_id, i64 chunk_offset, - int i, sqlite3_value *valueVector) { + int i, sqlite3_value *valueVector, + i64 rowid) { int rc; sqlite3_blob *blobVectors = NULL; @@ -11364,6 +12380,40 @@ int vec0Update_UpdateVectorColumn(vec0_vtab *p, i64 chunk_id, i64 chunk_offset, goto cleanup; } +#if SQLITE_VEC_ENABLE_RESCORE + // a rescore() column keeps the vector quantized in the row's slot of + // _rescore_chunksNN, and as is in its row of _rescore_vectorsNN + if (p->vector_columns[i].index_type == VEC0_INDEX_TYPE_RESCORE) { + rc = rescore_write_quantized(p, i, chunk_id, chunk_offset, vector); + if (rc != SQLITE_OK) { + goto cleanup; + } + char *zSql = + sqlite3_mprintf("UPDATE \"%w\".\"%w\" SET vector = ? WHERE rowid = ?", + p->schemaName, p->shadowRescoreVectorsNames[i]); + if (!zSql) { + rc = SQLITE_NOMEM; + goto cleanup; + } + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); + sqlite3_free(zSql); + if (rc != SQLITE_OK) { + goto cleanup; + } + sqlite3_bind_blob(stmt, 1, vector, + vector_column_byte_size(p->vector_columns[i]), + SQLITE_TRANSIENT); + sqlite3_bind_int64(stmt, 2, rowid); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + rc = rc == SQLITE_DONE ? SQLITE_OK : vec0_error_rc(rc); + goto cleanup; + } +#else + UNUSED_PARAMETER(rowid); +#endif + rc = sqlite3_blob_open(p->db, p->schemaName, p->shadowVectorChunksNames[i], "vectors", chunk_id, 1, &blobVectors); if (rc != SQLITE_OK) { @@ -11515,7 +12565,7 @@ int vec0Update_Update(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv) { } rc = vec0Update_UpdateVectorColumn(p, chunk_id, chunk_offset, vector_idx, - valueVector); + valueVector, rowid); if (rc != SQLITE_OK) { return vec0_error_rc(rc); } @@ -11810,6 +12860,20 @@ int vec0Update_SpecialInsert_Optimize(vec0_vtab *p) { if (rc != SQLITE_OK) { goto cleanup; } +#if SQLITE_VEC_ENABLE_RESCORE + // the row keeps its rowid, so its rows of _rescore_vectorsNN stay, and + // only its quantized vectors move to the new slot + for (int i = 0; i < p->numVectorColumns; i++) { + if (p->vector_columns[i].index_type != VEC0_INDEX_TYPE_RESCORE) { + continue; + } + rc = rescore_write_quantized(p, i, new_chunk_id, new_chunk_offset, + vectorDatas[i]); + if (rc != SQLITE_OK) { + goto cleanup; + } + } +#endif sqlite3_free((void *)bufferChunksValidity); bufferChunksValidity = NULL; if (sqlite3_blob_close(blobChunksValidity) != SQLITE_OK) { @@ -11875,9 +12939,17 @@ int vec0Update_SpecialInsert_Optimize(vec0_vtab *p) { // 4) clean up old vector chunks for (int i = 0; i < p->numVectorColumns; i++) { - zSql = sqlite3_mprintf("DELETE FROM " VEC0_SHADOW_VECTOR_N_NAME - " WHERE rowid <= ?", - p->schemaName, p->tableName, i); +#if SQLITE_VEC_ENABLE_RESCORE + if (p->vector_columns[i].index_type == VEC0_INDEX_TYPE_RESCORE) { + zSql = sqlite3_mprintf("DELETE FROM \"%w\".\"%w\" WHERE rowid <= ?", + p->schemaName, p->shadowRescoreChunksNames[i]); + } else +#endif + { + zSql = sqlite3_mprintf("DELETE FROM " VEC0_SHADOW_VECTOR_N_NAME + " WHERE rowid <= ?", + p->schemaName, p->tableName, i); + } rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, 0); sqlite3_free((void *)zSql); if (rc != SQLITE_OK) { @@ -11941,6 +13013,13 @@ int vec0Update_SpecialInsert(sqlite3_vtab *pVTab, sqlite3_value *pVal) { if (n_bytes == 8 && sqlite3_strnicmp(cmd, "optimize", 8) == 0) { return vec0Update_SpecialInsert_Optimize(p); } +#if SQLITE_VEC_ENABLE_RESCORE + int rc = rescore_handle_command(p, cmd); + if (rc != SQLITE_EMPTY) { + return rc; + } +#endif + vtab_set_error(pVTab, "unknown vec0 command: '%s'", cmd); return SQLITE_ERROR; } @@ -12025,13 +13104,22 @@ static int vec0ShadowName(const char *zName) { return 1; } - // "vector_chunksNN", one per vector column - static const char zVectorChunks[] = "vector_chunks"; - size_t n = sizeof(zVectorChunks) - 1; - if (sqlite3_strnicmp(zName, zVectorChunks, n) == 0 && zName[n] >= '0' && - zName[n] <= '9' && zName[n + 1] >= '0' && zName[n + 1] <= '9' && - zName[n + 2] == '\0') { - return 1; + // "vector_chunksNN", "rescore_chunksNN" and "rescore_vectorsNN", per vector + // column + static const char *azPerColumn[] = { + "vector_chunks", +#if SQLITE_VEC_ENABLE_RESCORE + "rescore_chunks", + "rescore_vectors", +#endif + }; + for (size_t i = 0; i < countof(azPerColumn); i++) { + size_t n = strlen(azPerColumn[i]); + if (sqlite3_strnicmp(zName, azPerColumn[i], n) == 0 && zName[n] >= '0' && + zName[n] <= '9' && zName[n + 1] >= '0' && zName[n + 1] <= '9' && + zName[n + 2] == '\0') { + return 1; + } } return 0; } @@ -12119,23 +13207,39 @@ static int vec0Rename(sqlite3_vtab *pVTab, const char *zName) { sqlite3_finalize(stmt); for (int i = 0; i < p->numVectorColumns; i++) { - char *newShadowVectorChunksName = - sqlite3_mprintf("%s_vector_chunks%02d", zName, i); - if (!newShadowVectorChunksName) { - return SQLITE_NOMEM; - } - zSql = sqlite3_mprintf("ALTER TABLE \"%w\".\"%w\" RENAME TO \"%w\"", - p->schemaName, p->shadowVectorChunksNames[i], - newShadowVectorChunksName); - sqlite3_free(newShadowVectorChunksName); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, 0); - sqlite3_free((void *)zSql); - if ((rc != SQLITE_OK) || (sqlite3_step(stmt) != SQLITE_DONE)) { - rc = SQLITE_ERROR; - vtab_set_error(pVTab, "could not rename vector_chunks shadow table"); - goto done; + struct { + const char *zOld; + const char *zSuffix; + } aShadow[] = { + {p->vector_columns[i].index_type == VEC0_INDEX_TYPE_FLAT + ? p->shadowVectorChunksNames[i] + : NULL, + "vector_chunks"}, +#if SQLITE_VEC_ENABLE_RESCORE + {p->shadowRescoreChunksNames[i], "rescore_chunks"}, + {p->shadowRescoreVectorsNames[i], "rescore_vectors"}, +#endif + }; + for (size_t j = 0; j < countof(aShadow); j++) { + if (!aShadow[j].zOld) { + continue; + } + zSql = sqlite3_mprintf( + "ALTER TABLE \"%w\".\"%w\" RENAME TO \"%w_%s%02d\"", p->schemaName, + aShadow[j].zOld, zName, aShadow[j].zSuffix, i); + if (!zSql) { + return SQLITE_NOMEM; + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, 0); + sqlite3_free((void *)zSql); + if ((rc != SQLITE_OK) || (sqlite3_step(stmt) != SQLITE_DONE)) { + rc = SQLITE_ERROR; + vtab_set_error(pVTab, "could not rename %s shadow table", + aShadow[j].zSuffix); + goto done; + } + sqlite3_finalize(stmt); } - sqlite3_finalize(stmt); } if (p->numAuxiliaryColumns > 0) { @@ -12912,8 +14016,15 @@ static sqlite3_module vec_static_blob_entriesModule = { #define SQLITE_VEC_DEBUG_BUILD_NEON "" #endif +#if SQLITE_VEC_ENABLE_RESCORE +#define SQLITE_VEC_DEBUG_BUILD_RESCORE "rescore" +#else +#define SQLITE_VEC_DEBUG_BUILD_RESCORE "" +#endif + #define SQLITE_VEC_DEBUG_BUILD \ - SQLITE_VEC_DEBUG_BUILD_AVX " " SQLITE_VEC_DEBUG_BUILD_NEON + SQLITE_VEC_DEBUG_BUILD_AVX " " SQLITE_VEC_DEBUG_BUILD_NEON \ + " " SQLITE_VEC_DEBUG_BUILD_RESCORE #define SQLITE_VEC_DEBUG_STRING \ "Version: " SQLITE_VEC_VERSION "\n" \ diff --git a/tests/__snapshots__/test-auxiliary.ambr b/tests/__snapshots__/test-auxiliary.ambr index 96fb1876..7669ff25 100644 --- a/tests/__snapshots__/test-auxiliary.ambr +++ b/tests/__snapshots__/test-auxiliary.ambr @@ -471,6 +471,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 INTEGER 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/fuzz/Makefile b/tests/fuzz/Makefile index 23c72491..b4937fdf 100644 --- a/tests/fuzz/Makefile +++ b/tests/fuzz/Makefile @@ -70,10 +70,37 @@ $(TARGET_DIR)/vec_mismatch: vec-mismatch.c $(FUZZ_SRCS) | $(TARGET_DIR) $(TARGET_DIR)/vec0_delete_completeness: vec0-delete-completeness.c $(FUZZ_SRCS) | $(TARGET_DIR) $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ +$(TARGET_DIR)/rescore_operations: rescore-operations.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + +$(TARGET_DIR)/rescore_create: rescore-create.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + +$(TARGET_DIR)/rescore_quantize: rescore-quantize.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + +$(TARGET_DIR)/rescore_shadow_corrupt: rescore-shadow-corrupt.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + +$(TARGET_DIR)/rescore_knn_deep: rescore-knn-deep.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + +$(TARGET_DIR)/rescore_quantize_edge: rescore-quantize-edge.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + +$(TARGET_DIR)/rescore_interleave: rescore-interleave.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + +$(TARGET_DIR)/rescore_optimize: rescore-optimize.c $(FUZZ_SRCS) | $(TARGET_DIR) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@ + FUZZ_TARGETS = vec0_create exec json numpy \ shadow_corrupt vec0_operations scalar_functions \ vec0_create_full metadata_columns vec_each vec_mismatch \ - vec0_delete_completeness + vec0_delete_completeness \ + rescore_operations rescore_create rescore_quantize \ + rescore_shadow_corrupt rescore_knn_deep \ + rescore_quantize_edge rescore_interleave rescore_optimize all: $(addprefix $(TARGET_DIR)/,$(FUZZ_TARGETS)) diff --git a/tests/fuzz/rescore-create.c b/tests/fuzz/rescore-create.c new file mode 100644 index 00000000..3e69d6db --- /dev/null +++ b/tests/fuzz/rescore-create.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + int rc = SQLITE_OK; + sqlite3 *db; + sqlite3_stmt *stmt; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + rc = sqlite3_vec_init(db, NULL, NULL); + assert(rc == SQLITE_OK); + + sqlite3_str *s = sqlite3_str_new(NULL); + assert(s); + sqlite3_str_appendall(s, "CREATE VIRTUAL TABLE v USING vec0(emb float[128] indexed by rescore("); + sqlite3_str_appendf(s, "%.*s", (int)size, data); + sqlite3_str_appendall(s, "))"); + const char *zSql = sqlite3_str_finish(s); + assert(zSql); + + rc = sqlite3_prepare_v2(db, zSql, -1, &stmt, NULL); + sqlite3_free((void *)zSql); + if (rc == SQLITE_OK) { + sqlite3_step(stmt); + } + sqlite3_finalize(stmt); + sqlite3_close(db); + return 0; +} diff --git a/tests/fuzz/rescore-create.dict b/tests/fuzz/rescore-create.dict new file mode 100644 index 00000000..a8adf71f --- /dev/null +++ b/tests/fuzz/rescore-create.dict @@ -0,0 +1,20 @@ +"rescore" +"quantizer" +"bit" +"int8" +"oversample" +"indexed" +"by" +"float" +"(" +")" +"," +"=" +"[" +"]" +"1" +"8" +"16" +"128" +"256" +"1024" diff --git a/tests/fuzz/rescore-interleave.c b/tests/fuzz/rescore-interleave.c new file mode 100644 index 00000000..74e8b8d7 --- /dev/null +++ b/tests/fuzz/rescore-interleave.c @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include + +/** + * Fuzz target: interleaved insert/update/delete/KNN operations on rescore + * tables with BOTH quantizer types, exercising the int8 quantizer path + * and the update code path that the existing rescore-operations.c misses. + * + * Key differences from rescore-operations.c: + * - Tests BOTH bit and int8 quantizers (the existing target only tests bit) + * - Fuzz-controlled query vectors (not fixed [1,0,0,...]) + * - Exercises the UPDATE path (line 9080+ in sqlite-vec.c) + * - Tests with 16 dimensions (more realistic, exercises more of the + * quantization loop) + * - Interleaves KNN between mutations to stress the blob_reopen path + * when _rescore_vectors rows have been deleted/modified + */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 8) return 0; + + int rc; + sqlite3 *db; + sqlite3_stmt *stmtInsert = NULL; + sqlite3_stmt *stmtUpdate = NULL; + sqlite3_stmt *stmtDelete = NULL; + sqlite3_stmt *stmtKnn = NULL; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + rc = sqlite3_vec_init(db, NULL, NULL); + assert(rc == SQLITE_OK); + + /* Use first byte to pick quantizer */ + int use_int8 = data[0] & 1; + data++; size--; + + const char *create_sql = use_int8 + ? "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=int8))" + : "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=bit))"; + + rc = sqlite3_exec(db, create_sql, NULL, NULL, NULL); + if (rc != SQLITE_OK) { sqlite3_close(db); return 0; } + + sqlite3_prepare_v2(db, + "INSERT INTO v(rowid, emb) VALUES (?, ?)", -1, &stmtInsert, NULL); + sqlite3_prepare_v2(db, + "UPDATE v SET emb = ? WHERE rowid = ?", -1, &stmtUpdate, NULL); + sqlite3_prepare_v2(db, + "DELETE FROM v WHERE rowid = ?", -1, &stmtDelete, NULL); + sqlite3_prepare_v2(db, + "SELECT rowid, distance FROM v WHERE emb MATCH ? " + "ORDER BY distance LIMIT 5", -1, &stmtKnn, NULL); + + if (!stmtInsert || !stmtUpdate || !stmtDelete || !stmtKnn) + goto cleanup; + + size_t i = 0; + while (i + 2 <= size) { + uint8_t op = data[i++] % 5; /* 5 operations now */ + uint8_t rowid_byte = data[i++]; + int64_t rowid = (int64_t)(rowid_byte % 24) + 1; + + switch (op) { + case 0: { + /* INSERT: consume bytes for 16 floats */ + float vec[16] = {0}; + for (int j = 0; j < 16 && i < size; j++, i++) { + vec[j] = (float)((int8_t)data[i]) / 8.0f; + } + sqlite3_reset(stmtInsert); + sqlite3_bind_int64(stmtInsert, 1, rowid); + sqlite3_bind_blob(stmtInsert, 2, vec, sizeof(vec), SQLITE_TRANSIENT); + sqlite3_step(stmtInsert); + break; + } + case 1: { + /* DELETE */ + sqlite3_reset(stmtDelete); + sqlite3_bind_int64(stmtDelete, 1, rowid); + sqlite3_step(stmtDelete); + break; + } + case 2: { + /* KNN with fuzz-controlled query vector */ + float qvec[16] = {0}; + for (int j = 0; j < 16 && i < size; j++, i++) { + qvec[j] = (float)((int8_t)data[i]) / 4.0f; + } + sqlite3_reset(stmtKnn); + sqlite3_bind_blob(stmtKnn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(stmtKnn) == SQLITE_ROW) { + (void)sqlite3_column_int64(stmtKnn, 0); + (void)sqlite3_column_double(stmtKnn, 1); + } + break; + } + case 3: { + /* UPDATE: modify an existing vector (exercises rescore update path) */ + float vec[16] = {0}; + for (int j = 0; j < 16 && i < size; j++, i++) { + vec[j] = (float)((int8_t)data[i]) / 6.0f; + } + sqlite3_reset(stmtUpdate); + sqlite3_bind_blob(stmtUpdate, 1, vec, sizeof(vec), SQLITE_TRANSIENT); + sqlite3_bind_int64(stmtUpdate, 2, rowid); + sqlite3_step(stmtUpdate); + break; + } + case 4: { + /* INSERT then immediately UPDATE same row (stresses blob lifecycle) */ + float vec1[16] = {0}; + float vec2[16] = {0}; + for (int j = 0; j < 16 && i < size; j++, i++) { + vec1[j] = (float)((int8_t)data[i]) / 10.0f; + vec2[j] = -vec1[j]; /* opposite direction */ + } + /* Insert */ + sqlite3_reset(stmtInsert); + sqlite3_bind_int64(stmtInsert, 1, rowid); + sqlite3_bind_blob(stmtInsert, 2, vec1, sizeof(vec1), SQLITE_TRANSIENT); + if (sqlite3_step(stmtInsert) == SQLITE_DONE) { + /* Only update if insert succeeded (rowid might already exist) */ + sqlite3_reset(stmtUpdate); + sqlite3_bind_blob(stmtUpdate, 1, vec2, sizeof(vec2), SQLITE_TRANSIENT); + sqlite3_bind_int64(stmtUpdate, 2, rowid); + sqlite3_step(stmtUpdate); + } + break; + } + } + } + + /* Final consistency check: full scan must not crash */ + sqlite3_exec(db, "SELECT * FROM v", NULL, NULL, NULL); + +cleanup: + sqlite3_finalize(stmtInsert); + sqlite3_finalize(stmtUpdate); + sqlite3_finalize(stmtDelete); + sqlite3_finalize(stmtKnn); + sqlite3_close(db); + return 0; +} diff --git a/tests/fuzz/rescore-knn-deep.c b/tests/fuzz/rescore-knn-deep.c new file mode 100644 index 00000000..8ff3c378 --- /dev/null +++ b/tests/fuzz/rescore-knn-deep.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include + +/** + * Fuzz target: deep exercise of rescore KNN with fuzz-controlled query vectors + * and both quantizer types (bit + int8), multiple distance metrics. + * + * The existing rescore-operations.c only tests bit quantizer with a fixed + * query vector. This target: + * - Tests both bit and int8 quantizers + * - Uses fuzz-controlled query vectors (hits NaN/Inf/denormal paths) + * - Tests all distance metrics with int8 (L2, cosine, L1) + * - Exercises large LIMIT values (oversample multiplication) + * - Tests KNN with rowid IN constraints + * - Exercises the insert->query->update->query->delete->query cycle + */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 20) return 0; + + int rc; + sqlite3 *db; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + rc = sqlite3_vec_init(db, NULL, NULL); + assert(rc == SQLITE_OK); + + /* Use first 4 bytes for configuration */ + uint8_t config = data[0]; + uint8_t num_inserts = (data[1] % 20) + 3; /* 3..22 inserts */ + uint8_t limit_val = (data[2] % 50) + 1; /* 1..50 for LIMIT */ + uint8_t metric_choice = data[3] % 3; + data += 4; + size -= 4; + + int use_int8 = config & 1; + const char *metric_str; + switch (metric_choice) { + case 0: metric_str = ""; break; /* default L2 */ + case 1: metric_str = " distance_metric=cosine"; break; + case 2: metric_str = " distance_metric=l1"; break; + default: metric_str = ""; break; + } + + /* Build CREATE TABLE statement */ + char create_sql[256]; + if (use_int8) { + snprintf(create_sql, sizeof(create_sql), + "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=int8)%s)", metric_str); + } else { + /* bit quantizer ignores distance_metric for the coarse pass (always hamming), + but the float rescore phase uses the specified metric */ + snprintf(create_sql, sizeof(create_sql), + "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=bit)%s)", metric_str); + } + + rc = sqlite3_exec(db, create_sql, NULL, NULL, NULL); + if (rc != SQLITE_OK) { sqlite3_close(db); return 0; } + + /* Insert vectors using fuzz data */ + { + sqlite3_stmt *ins = NULL; + sqlite3_prepare_v2(db, + "INSERT INTO v(rowid, emb) VALUES (?, ?)", -1, &ins, NULL); + if (!ins) { sqlite3_close(db); return 0; } + + size_t cursor = 0; + for (int i = 0; i < num_inserts && cursor + 1 < size; i++) { + float vec[16]; + for (int j = 0; j < 16; j++) { + if (cursor < size) { + /* Map fuzz byte to float -- includes potential for + interesting float values via reinterpretation */ + int8_t sb = (int8_t)data[cursor++]; + vec[j] = (float)sb / 5.0f; + } else { + vec[j] = 0.0f; + } + } + sqlite3_reset(ins); + sqlite3_bind_int64(ins, 1, (sqlite3_int64)(i + 1)); + sqlite3_bind_blob(ins, 2, vec, sizeof(vec), SQLITE_TRANSIENT); + sqlite3_step(ins); + } + sqlite3_finalize(ins); + } + + /* Build a fuzz-controlled query vector from remaining data */ + float qvec[16] = {0}; + { + size_t cursor = 0; + for (int j = 0; j < 16 && cursor < size; j++) { + int8_t sb = (int8_t)data[cursor++]; + qvec[j] = (float)sb / 3.0f; + } + } + + /* KNN query with fuzz-controlled vector and LIMIT */ + { + char knn_sql[256]; + snprintf(knn_sql, sizeof(knn_sql), + "SELECT rowid, distance FROM v WHERE emb MATCH ? " + "ORDER BY distance LIMIT %d", (int)limit_val); + + sqlite3_stmt *knn = NULL; + sqlite3_prepare_v2(db, knn_sql, -1, &knn, NULL); + if (knn) { + sqlite3_bind_blob(knn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(knn) == SQLITE_ROW) { + /* Read results to ensure distance computation didn't produce garbage + that crashes the cursor iteration */ + (void)sqlite3_column_int64(knn, 0); + (void)sqlite3_column_double(knn, 1); + } + sqlite3_finalize(knn); + } + } + + /* Update some vectors, then query again */ + { + float uvec[16]; + for (int j = 0; j < 16; j++) uvec[j] = qvec[15 - j]; /* reverse of query */ + sqlite3_stmt *upd = NULL; + sqlite3_prepare_v2(db, + "UPDATE v SET emb = ? WHERE rowid = 1", -1, &upd, NULL); + if (upd) { + sqlite3_bind_blob(upd, 1, uvec, sizeof(uvec), SQLITE_STATIC); + sqlite3_step(upd); + sqlite3_finalize(upd); + } + } + + /* Second KNN after update */ + { + sqlite3_stmt *knn = NULL; + sqlite3_prepare_v2(db, + "SELECT rowid, distance FROM v WHERE emb MATCH ? " + "ORDER BY distance LIMIT 10", -1, &knn, NULL); + if (knn) { + sqlite3_bind_blob(knn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(knn) == SQLITE_ROW) {} + sqlite3_finalize(knn); + } + } + + /* Delete half the rows, then KNN again */ + for (int i = 1; i <= num_inserts; i += 2) { + char del_sql[64]; + snprintf(del_sql, sizeof(del_sql), + "DELETE FROM v WHERE rowid = %d", i); + sqlite3_exec(db, del_sql, NULL, NULL, NULL); + } + + /* Third KNN after deletes -- exercises distance computation over + zeroed-out slots in the quantized chunk */ + { + sqlite3_stmt *knn = NULL; + sqlite3_prepare_v2(db, + "SELECT rowid, distance FROM v WHERE emb MATCH ? " + "ORDER BY distance LIMIT 5", -1, &knn, NULL); + if (knn) { + sqlite3_bind_blob(knn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(knn) == SQLITE_ROW) {} + sqlite3_finalize(knn); + } + } + + sqlite3_close(db); + return 0; +} diff --git a/tests/fuzz/rescore-operations.c b/tests/fuzz/rescore-operations.c new file mode 100644 index 00000000..4bb7ff18 --- /dev/null +++ b/tests/fuzz/rescore-operations.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 6) return 0; + + int rc; + sqlite3 *db; + sqlite3_stmt *stmtInsert = NULL; + sqlite3_stmt *stmtDelete = NULL; + sqlite3_stmt *stmtKnn = NULL; + sqlite3_stmt *stmtScan = NULL; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + rc = sqlite3_vec_init(db, NULL, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, + "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[8] indexed by rescore(quantizer=bit))", + NULL, NULL, NULL); + if (rc != SQLITE_OK) { sqlite3_close(db); return 0; } + + sqlite3_prepare_v2(db, + "INSERT INTO v(rowid, emb) VALUES (?, ?)", -1, &stmtInsert, NULL); + sqlite3_prepare_v2(db, + "DELETE FROM v WHERE rowid = ?", -1, &stmtDelete, NULL); + sqlite3_prepare_v2(db, + "SELECT rowid, distance FROM v WHERE emb MATCH ? ORDER BY distance LIMIT 3", + -1, &stmtKnn, NULL); + sqlite3_prepare_v2(db, + "SELECT rowid FROM v", -1, &stmtScan, NULL); + + if (!stmtInsert || !stmtDelete || !stmtKnn || !stmtScan) goto cleanup; + + size_t i = 0; + while (i + 2 <= size) { + uint8_t op = data[i++] % 4; + uint8_t rowid_byte = data[i++]; + int64_t rowid = (int64_t)(rowid_byte % 32) + 1; + + switch (op) { + case 0: { + // INSERT: consume 32 bytes for 8 floats, or use what's left + float vec[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + for (int j = 0; j < 8 && i < size; j++, i++) { + vec[j] = (float)((int8_t)data[i]) / 10.0f; + } + sqlite3_reset(stmtInsert); + sqlite3_bind_int64(stmtInsert, 1, rowid); + sqlite3_bind_blob(stmtInsert, 2, vec, sizeof(vec), SQLITE_TRANSIENT); + sqlite3_step(stmtInsert); + break; + } + case 1: { + // DELETE + sqlite3_reset(stmtDelete); + sqlite3_bind_int64(stmtDelete, 1, rowid); + sqlite3_step(stmtDelete); + break; + } + case 2: { + // KNN query with a fixed query vector + float qvec[8] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + sqlite3_reset(stmtKnn); + sqlite3_bind_blob(stmtKnn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(stmtKnn) == SQLITE_ROW) {} + break; + } + case 3: { + // Full scan + sqlite3_reset(stmtScan); + while (sqlite3_step(stmtScan) == SQLITE_ROW) {} + break; + } + } + } + + // Final operations -- must not crash regardless of prior state + sqlite3_exec(db, "SELECT * FROM v", NULL, NULL, NULL); + +cleanup: + sqlite3_finalize(stmtInsert); + sqlite3_finalize(stmtDelete); + sqlite3_finalize(stmtKnn); + sqlite3_finalize(stmtScan); + sqlite3_close(db); + return 0; +} diff --git a/tests/fuzz/rescore-optimize.c b/tests/fuzz/rescore-optimize.c new file mode 100644 index 00000000..14b6e16d --- /dev/null +++ b/tests/fuzz/rescore-optimize.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include + +static int64_t count_rows(sqlite3 *db, const char *zSql) { + sqlite3_stmt *stmt = NULL; + int64_t n = -1; + if (sqlite3_prepare_v2(db, zSql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { + n = sqlite3_column_int64(stmt, 0); + } + sqlite3_finalize(stmt); + return n; +} + +/** + * Fuzz target: optimize on rescore tables, interleaved with inserts, updates, + * deletes and KNN queries, with both quantizers and a chunk size of 8 so rows + * move across chunks. optimize moves each row's quantized vectors to its new + * slot and leaves its float32 vector where it is, so after every optimize the + * table and its _rescore_vectors00 must hold the same rows. + */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 8) return 0; + + int rc; + sqlite3 *db; + sqlite3_stmt *stmtInsert = NULL; + sqlite3_stmt *stmtUpdate = NULL; + sqlite3_stmt *stmtDelete = NULL; + sqlite3_stmt *stmtKnn = NULL; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + rc = sqlite3_vec_init(db, NULL, NULL); + assert(rc == SQLITE_OK); + + int use_int8 = data[0] & 1; + data++; size--; + + const char *create_sql = use_int8 + ? "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=int8), chunk_size=8)" + : "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=bit), chunk_size=8)"; + + rc = sqlite3_exec(db, create_sql, NULL, NULL, NULL); + if (rc != SQLITE_OK) { sqlite3_close(db); return 0; } + + sqlite3_prepare_v2(db, + "INSERT INTO v(rowid, emb) VALUES (?, ?)", -1, &stmtInsert, NULL); + sqlite3_prepare_v2(db, + "UPDATE v SET emb = ? WHERE rowid = ?", -1, &stmtUpdate, NULL); + sqlite3_prepare_v2(db, + "DELETE FROM v WHERE rowid = ?", -1, &stmtDelete, NULL); + sqlite3_prepare_v2(db, + "SELECT rowid, distance FROM v WHERE emb MATCH ? AND k = 5", -1, + &stmtKnn, NULL); + + if (!stmtInsert || !stmtUpdate || !stmtDelete || !stmtKnn) + goto cleanup; + + size_t i = 0; + while (i + 2 <= size) { + uint8_t op = data[i++] % 5; + uint8_t rowid_byte = data[i++]; + int64_t rowid = (int64_t)(rowid_byte % 40) + 1; + + switch (op) { + case 0: { + float vec[16] = {0}; + for (int j = 0; j < 16 && i < size; j++, i++) { + vec[j] = (float)((int8_t)data[i]) / 64.0f; + } + sqlite3_reset(stmtInsert); + sqlite3_bind_int64(stmtInsert, 1, rowid); + sqlite3_bind_blob(stmtInsert, 2, vec, sizeof(vec), SQLITE_TRANSIENT); + sqlite3_step(stmtInsert); + break; + } + case 1: { + sqlite3_reset(stmtDelete); + sqlite3_bind_int64(stmtDelete, 1, rowid); + sqlite3_step(stmtDelete); + break; + } + case 2: { + float qvec[16] = {0}; + for (int j = 0; j < 16 && i < size; j++, i++) { + qvec[j] = (float)((int8_t)data[i]) / 64.0f; + } + sqlite3_reset(stmtKnn); + sqlite3_bind_blob(stmtKnn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(stmtKnn) == SQLITE_ROW) { + (void)sqlite3_column_int64(stmtKnn, 0); + (void)sqlite3_column_double(stmtKnn, 1); + } + break; + } + case 3: { + float vec[16] = {0}; + for (int j = 0; j < 16 && i < size; j++, i++) { + vec[j] = (float)((int8_t)data[i]) / 32.0f; + } + sqlite3_reset(stmtUpdate); + sqlite3_bind_blob(stmtUpdate, 1, vec, sizeof(vec), SQLITE_TRANSIENT); + sqlite3_bind_int64(stmtUpdate, 2, rowid); + sqlite3_step(stmtUpdate); + break; + } + case 4: { + int64_t before = count_rows(db, "SELECT count(*) FROM v"); + rc = sqlite3_exec(db, "INSERT INTO v(v) VALUES ('optimize')", NULL, + NULL, NULL); + assert(rc == SQLITE_OK); + assert(count_rows(db, "SELECT count(*) FROM v") == before); + assert(count_rows(db, "SELECT count(*) FROM v_rescore_vectors00") == + before); + break; + } + } + } + + sqlite3_exec(db, "SELECT * FROM v", NULL, NULL, NULL); + +cleanup: + sqlite3_finalize(stmtInsert); + sqlite3_finalize(stmtUpdate); + sqlite3_finalize(stmtDelete); + sqlite3_finalize(stmtKnn); + sqlite3_close(db); + return 0; +} diff --git a/tests/fuzz/rescore-quantize-edge.c b/tests/fuzz/rescore-quantize-edge.c new file mode 100644 index 00000000..9eb34cde --- /dev/null +++ b/tests/fuzz/rescore-quantize-edge.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include "../sqlite-vec-internal.h" +#include + +static size_t quantized_byte_size(enum Vec0RescoreQuantizerType quantizer, + size_t dimensions) { + struct VectorColumnDefinition col; + memset(&col, 0, sizeof(col)); + col.dimensions = dimensions; + col.rescore.quantizer_type = quantizer; + return rescore_quantized_byte_size(&col); +} + +/** + * Fuzz target: edge cases in rescore quantization functions. + * + * The existing rescore-quantize.c only tests dimensions that are multiples of 8 + * and never passes special float values. This target: + * + * - Tests rescore_quantized_byte_size with arbitrary dimension values + * (including 0, 1, 7, MAX values -- looking for integer division issues) + * - Passes raw float reinterpretation of fuzz bytes (NaN, Inf, denormals, + * negative zero) + * - Tests the int8 quantizer with all-identical values, which must quantize + * to all-identical values + * - Tests the int8 quantizer with values far outside [-1, 1], which clamp + * - Tests bit quantizer with exact float threshold (0.0f boundary) + */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 8) return 0; + + uint8_t mode = data[0] % 5; + data++; size--; + + switch (mode) { + case 0: { + /* Test rescore_quantized_byte_size with fuzz-controlled dimensions. + This function does dimensions / CHAR_BIT for bit, dimensions for int8. + We're checking it doesn't do anything weird with edge values. */ + if (size < sizeof(size_t)) return 0; + size_t dim; + memcpy(&dim, data, sizeof(dim)); + + /* These should never crash, just return values */ + size_t bit_size = quantized_byte_size(VEC0_RESCORE_QUANTIZER_BIT, dim); + size_t int8_size = quantized_byte_size(VEC0_RESCORE_QUANTIZER_INT8, dim); + + /* Verify basic invariants */ + (void)bit_size; + (void)int8_size; + break; + } + + case 1: { + /* Bit quantize with raw reinterpreted floats (NaN, Inf, denormal). + The key check: src[i] >= 0.0f -- NaN comparison is always false, + so NaN should produce 0-bits. But denormals and -0.0f are tricky. */ + size_t num_floats = size / sizeof(float); + if (num_floats == 0) return 0; + /* Round to multiple of 8 for bit quantizer */ + size_t dim = (num_floats / 8) * 8; + if (dim == 0) return 0; + + /* The fuzz bytes start one byte past the mode byte, so they are copied + out to be read as floats at float alignment. */ + float *src = (float *)malloc(dim * sizeof(float)); + size_t bit_bytes = dim / 8; + uint8_t *dst = (uint8_t *)malloc(bit_bytes); + if (!src || !dst) { free(src); free(dst); return 0; } + memcpy(src, data, dim * sizeof(float)); + + rescore_quantize_float_to_bit(src, dst, dim); + + /* Verify: for each bit, if src >= 0 then bit should be set */ + for (size_t i = 0; i < dim; i++) { + int bit_set = (dst[i / 8] >> (i % 8)) & 1; + if (src[i] >= 0.0f) { + assert(bit_set == 1); + } else if (src[i] < 0.0f) { + /* Definitely negative -- bit must be 0 */ + assert(bit_set == 0); + } + /* NaN: comparison is false, so bit_set should be 0 */ + } + + free(src); + free(dst); + break; + } + + case 2: { + /* Int8 quantize with raw reinterpreted floats. + The dangerous paths: + - NaN, which clamps to 127 + - Inf and values far outside [-1, 1], which clamp to -128 or 127 + - denormals near the clamping boundaries */ + size_t num_floats = size / sizeof(float); + if (num_floats == 0) return 0; + + /* Copied out to float alignment, as in mode 1 */ + float *src = (float *)malloc(num_floats * sizeof(float)); + int8_t *dst = (int8_t *)malloc(num_floats); + if (!src || !dst) { free(src); free(dst); return 0; } + memcpy(src, data, num_floats * sizeof(float)); + + rescore_quantize_float_to_int8(src, dst, num_floats); + + /* Output must always be in [-128, 127] (trivially true for int8_t, + but check the actual clamping logic worked) */ + for (size_t i = 0; i < num_floats; i++) { + assert(dst[i] >= -128 && dst[i] <= 127); + } + + free(src); + free(dst); + break; + } + + case 3: { + /* Int8 quantize stress: all-same values quantize to all-same values */ + size_t dim = (size < 64) ? size : 64; + if (dim == 0) return 0; + + float *src = (float *)malloc(dim * sizeof(float)); + int8_t *dst = (int8_t *)malloc(dim); + if (!src || !dst) { free(src); free(dst); return 0; } + + /* Fill with a single value derived from fuzz data */ + float val; + memcpy(&val, data, sizeof(float) < size ? sizeof(float) : size); + for (size_t i = 0; i < dim; i++) src[i] = val; + + rescore_quantize_float_to_int8(src, dst, dim); + + /* Each value quantizes on its own, so every output is the same */ + for (size_t i = 0; i < dim; i++) { + assert(dst[i] == dst[0]); + } + + free(src); + free(dst); + break; + } + + case 4: { + /* Int8 quantize with extreme values: one huge positive, one huge + negative, which clamp to [-128, 127]. */ + if (size < 2 * sizeof(float)) return 0; + + float extreme[2]; + memcpy(extreme, data, 2 * sizeof(float)); + + /* Only test if both are finite (NaN/Inf tested in case 2) */ + if (!isfinite(extreme[0]) || !isfinite(extreme[1])) return 0; + + /* Build a vector with these two extreme values plus some fuzz */ + size_t dim = 16; + float src[16]; + src[0] = extreme[0]; + src[1] = extreme[1]; + for (size_t i = 2; i < dim; i++) { + if (2 * sizeof(float) + (i - 2) < size) { + src[i] = (float)((int8_t)data[2 * sizeof(float) + (i - 2)]) * 1000.0f; + } else { + src[i] = 0.0f; + } + } + + int8_t dst[16]; + rescore_quantize_float_to_int8(src, dst, dim); + + for (size_t i = 0; i < dim; i++) { + assert(dst[i] >= -128 && dst[i] <= 127); + } + break; + } + } + + return 0; +} diff --git a/tests/fuzz/rescore-quantize.c b/tests/fuzz/rescore-quantize.c new file mode 100644 index 00000000..f68c9bd2 --- /dev/null +++ b/tests/fuzz/rescore-quantize.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include "../sqlite-vec-internal.h" +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + /* Need at least 4 bytes for one float */ + if (size < 4) return 0; + + /* Use the input as an array of floats. Dimensions must be a multiple of 8 + * for the bit quantizer. */ + size_t num_floats = size / sizeof(float); + if (num_floats == 0) return 0; + + /* Round down to multiple of 8 for bit quantizer compatibility */ + size_t dim = (num_floats / 8) * 8; + if (dim == 0) dim = 8; + if (dim > num_floats) return 0; + + const float *src = (const float *)data; + + /* Allocate output buffers */ + size_t bit_bytes = dim / 8; + uint8_t *bit_dst = (uint8_t *)malloc(bit_bytes); + int8_t *int8_dst = (int8_t *)malloc(dim); + if (!bit_dst || !int8_dst) { + free(bit_dst); + free(int8_dst); + return 0; + } + + /* Test bit quantization */ + rescore_quantize_float_to_bit(src, bit_dst, dim); + + /* Test int8 quantization */ + rescore_quantize_float_to_int8(src, int8_dst, dim); + + /* Verify int8 output is in range */ + for (size_t i = 0; i < dim; i++) { + assert(int8_dst[i] >= -128 && int8_dst[i] <= 127); + } + + free(bit_dst); + free(int8_dst); + return 0; +} diff --git a/tests/fuzz/rescore-shadow-corrupt.c b/tests/fuzz/rescore-shadow-corrupt.c new file mode 100644 index 00000000..edd87ef0 --- /dev/null +++ b/tests/fuzz/rescore-shadow-corrupt.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include +#include "sqlite-vec.h" +#include "sqlite3.h" +#include + +/** + * Fuzz target: corrupt rescore shadow tables then exercise KNN/read/write. + * + * This targets the dangerous code paths in rescore_knn (Phase 1 + 2): + * - sqlite3_blob_read into baseVectors with potentially wrong-sized blobs + * - distance computation on corrupted/partial quantized data + * - blob_reopen on _rescore_vectors with missing/corrupted rows + * - insert/delete after corruption (blob_write to wrong offsets) + * + * The existing shadow-corrupt.c only tests vec0 without rescore. + */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 4) return 0; + + int rc; + sqlite3 *db; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + rc = sqlite3_vec_init(db, NULL, NULL); + assert(rc == SQLITE_OK); + + /* Pick quantizer type from first byte */ + int use_int8 = data[0] & 1; + int target = (data[1] % 8); + const uint8_t *payload = data + 2; + int payload_size = (int)(size - 2); + + const char *create_sql = use_int8 + ? "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=int8))" + : "CREATE VIRTUAL TABLE v USING vec0(" + "emb float[16] indexed by rescore(quantizer=bit))"; + + rc = sqlite3_exec(db, create_sql, NULL, NULL, NULL); + if (rc != SQLITE_OK) { sqlite3_close(db); return 0; } + + /* Insert several vectors so there's a full chunk to corrupt */ + { + sqlite3_stmt *ins = NULL; + sqlite3_prepare_v2(db, + "INSERT INTO v(rowid, emb) VALUES (?, ?)", -1, &ins, NULL); + if (!ins) { sqlite3_close(db); return 0; } + + for (int i = 1; i <= 8; i++) { + float vec[16]; + for (int j = 0; j < 16; j++) vec[j] = (float)(i * 10 + j) / 100.0f; + sqlite3_reset(ins); + sqlite3_bind_int64(ins, 1, i); + sqlite3_bind_blob(ins, 2, vec, sizeof(vec), SQLITE_TRANSIENT); + sqlite3_step(ins); + } + sqlite3_finalize(ins); + } + + /* Now corrupt rescore shadow tables based on fuzz input */ + sqlite3_stmt *stmt = NULL; + + switch (target) { + case 0: { + /* Corrupt _rescore_chunks00 vectors blob with fuzz data */ + rc = sqlite3_prepare_v2(db, + "UPDATE v_rescore_chunks00 SET vectors = ? WHERE rowid = 1", + -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_blob(stmt, 1, payload, payload_size, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + stmt = NULL; + } + break; + } + case 1: { + /* Corrupt _rescore_vectors00 vector blob for a specific row */ + rc = sqlite3_prepare_v2(db, + "UPDATE v_rescore_vectors00 SET vector = ? WHERE rowid = 3", + -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_blob(stmt, 1, payload, payload_size, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + stmt = NULL; + } + break; + } + case 2: { + /* Truncate the quantized chunk blob to wrong size */ + rc = sqlite3_prepare_v2(db, + "UPDATE v_rescore_chunks00 SET vectors = X'DEADBEEF' WHERE rowid = 1", + -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_step(stmt); + sqlite3_finalize(stmt); + stmt = NULL; + } + break; + } + case 3: { + /* Delete rows from _rescore_vectors (orphan the float vectors) */ + sqlite3_exec(db, + "DELETE FROM v_rescore_vectors00 WHERE rowid IN (2, 4, 6)", + NULL, NULL, NULL); + break; + } + case 4: { + /* Delete the chunk row entirely from _rescore_chunks */ + sqlite3_exec(db, + "DELETE FROM v_rescore_chunks00 WHERE rowid = 1", + NULL, NULL, NULL); + break; + } + case 5: { + /* Set vectors to NULL in _rescore_chunks */ + sqlite3_exec(db, + "UPDATE v_rescore_chunks00 SET vectors = NULL WHERE rowid = 1", + NULL, NULL, NULL); + break; + } + case 6: { + /* Set vector to NULL in _rescore_vectors */ + sqlite3_exec(db, + "UPDATE v_rescore_vectors00 SET vector = NULL WHERE rowid = 3", + NULL, NULL, NULL); + break; + } + case 7: { + /* Corrupt BOTH tables with fuzz data */ + int half = payload_size / 2; + rc = sqlite3_prepare_v2(db, + "UPDATE v_rescore_chunks00 SET vectors = ? WHERE rowid = 1", + -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_blob(stmt, 1, payload, half, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + stmt = NULL; + } + rc = sqlite3_prepare_v2(db, + "UPDATE v_rescore_vectors00 SET vector = ? WHERE rowid = 1", + -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_blob(stmt, 1, payload + half, + payload_size - half, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + stmt = NULL; + } + break; + } + } + + /* Exercise ALL read/write paths -- NONE should crash */ + + /* KNN query (triggers rescore_knn Phase 1 + Phase 2) */ + { + float qvec[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; + sqlite3_stmt *knn = NULL; + sqlite3_prepare_v2(db, + "SELECT rowid, distance FROM v WHERE emb MATCH ? " + "ORDER BY distance LIMIT 5", -1, &knn, NULL); + if (knn) { + sqlite3_bind_blob(knn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(knn) == SQLITE_ROW) {} + sqlite3_finalize(knn); + } + } + + /* Full scan (triggers reading from _rescore_vectors) */ + sqlite3_exec(db, "SELECT * FROM v", NULL, NULL, NULL); + + /* Point lookups */ + sqlite3_exec(db, "SELECT * FROM v WHERE rowid = 1", NULL, NULL, NULL); + sqlite3_exec(db, "SELECT * FROM v WHERE rowid = 3", NULL, NULL, NULL); + + /* Insert after corruption */ + { + float vec[16] = {0}; + sqlite3_stmt *ins = NULL; + sqlite3_prepare_v2(db, + "INSERT INTO v(rowid, emb) VALUES (99, ?)", -1, &ins, NULL); + if (ins) { + sqlite3_bind_blob(ins, 1, vec, sizeof(vec), SQLITE_STATIC); + sqlite3_step(ins); + sqlite3_finalize(ins); + } + } + + /* Delete after corruption */ + sqlite3_exec(db, "DELETE FROM v WHERE rowid = 5", NULL, NULL, NULL); + + /* Update after corruption */ + { + float vec[16] = {1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1}; + sqlite3_stmt *upd = NULL; + sqlite3_prepare_v2(db, + "UPDATE v SET emb = ? WHERE rowid = 1", -1, &upd, NULL); + if (upd) { + sqlite3_bind_blob(upd, 1, vec, sizeof(vec), SQLITE_STATIC); + sqlite3_step(upd); + sqlite3_finalize(upd); + } + } + + /* KNN again after modifications to corrupted state */ + { + float qvec[16] = {0,0,0,0, 0,0,0,0, 1,1,1,1, 1,1,1,1}; + sqlite3_stmt *knn = NULL; + sqlite3_prepare_v2(db, + "SELECT rowid, distance FROM v WHERE emb MATCH ? " + "ORDER BY distance LIMIT 3", -1, &knn, NULL); + if (knn) { + sqlite3_bind_blob(knn, 1, qvec, sizeof(qvec), SQLITE_STATIC); + while (sqlite3_step(knn) == SQLITE_ROW) {} + sqlite3_finalize(knn); + } + } + + sqlite3_exec(db, "DROP TABLE v", NULL, NULL, NULL); + sqlite3_close(db); + return 0; +} diff --git a/tests/sqlite-vec-internal.h b/tests/sqlite-vec-internal.h index 20a788c5..8546dd69 100644 --- a/tests/sqlite-vec-internal.h +++ b/tests/sqlite-vec-internal.h @@ -24,6 +24,9 @@ enum Vec0TokenType { TOKEN_TYPE_RBRACKET = 3, TOKEN_TYPE_PLUS = 4, TOKEN_TYPE_EQ = 5, + TOKEN_TYPE_LPAREN = 6, + TOKEN_TYPE_RPAREN = 7, + TOKEN_TYPE_COMMA = 8, }; #define VEC0_TOKEN_RESULT_EOF 1 @@ -60,12 +63,40 @@ enum Vec0DistanceMetrics { VEC0_DISTANCE_METRIC_L1 = 3, }; +#ifndef SQLITE_VEC_ENABLE_RESCORE +#define SQLITE_VEC_ENABLE_RESCORE 1 +#endif + +enum Vec0IndexType { + VEC0_INDEX_TYPE_FLAT = 1, +#if SQLITE_VEC_ENABLE_RESCORE + VEC0_INDEX_TYPE_RESCORE = 2, +#endif +}; + +#if SQLITE_VEC_ENABLE_RESCORE +enum Vec0RescoreQuantizerType { + VEC0_RESCORE_QUANTIZER_BIT = 1, + VEC0_RESCORE_QUANTIZER_INT8 = 2, +}; + +struct Vec0RescoreConfig { + enum Vec0RescoreQuantizerType quantizer_type; + int oversample; + int oversample_search; +}; +#endif + struct VectorColumnDefinition { char *name; int name_length; size_t dimensions; enum VectorElementType element_type; enum Vec0DistanceMetrics distance_metric; + enum Vec0IndexType index_type; +#if SQLITE_VEC_ENABLE_RESCORE + struct Vec0RescoreConfig rescore; +#endif }; int vec0_parse_vector_column(const char *source, int source_length, @@ -77,4 +108,12 @@ int vec0_parse_partition_key_definition(const char *source, int source_length, int *out_column_name_length, int *out_column_type, bool isCreate); +#if SQLITE_VEC_ENABLE_RESCORE +size_t rescore_quantized_byte_size(struct VectorColumnDefinition *col); +void rescore_quantize_float_to_bit(const float *src, uint8_t *dst, + size_t dimensions); +void rescore_quantize_float_to_int8(const float *src, int8_t *dst, + size_t dimensions); +#endif + #endif /* SQLITE_VEC_INTERNAL_H */ diff --git a/tests/test-auxiliary.py b/tests/test-auxiliary.py index af51e64e..23597ffb 100644 --- a/tests/test-auxiliary.py +++ b/tests/test-auxiliary.py @@ -1,7 +1,13 @@ +import random import sqlite3 +import struct from collections import OrderedDict +def _f32(values): + return struct.pack("%sf" % len(values), *values) + + def test_constructor_limit(db, snapshot): assert ( exec( @@ -161,6 +167,113 @@ def test_vacuum(db, snapshot): assert cur_page_count < prev_page_count +# ====================================================================== +# 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" + ")" + ) + 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") == 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" + ")" + ) + 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" + ")" + ) + 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 exec(db, sql, parameters=[]): try: rows = db.execute(sql, parameters).fetchall() diff --git a/tests/test-error-paths.py b/tests/test-error-paths.py index bbf0eb20..281011cb 100644 --- a/tests/test-error-paths.py +++ b/tests/test-error-paths.py @@ -417,6 +417,17 @@ def interrupt_template(): "INSERT INTO vt(id, vector) VALUES (?, ?)", [(f"id{i}", f"[{i}, {i % 3}]") for i in range(1, 9)], ) + # vr is indexed by rescore(). Its first chunk has a deleted row for optimize + # to skip, and rowid 9 is the only row of its second chunk. + db.execute( + "CREATE VIRTUAL TABLE vr USING vec0(vector float[2] indexed by " + "rescore(quantizer=int8), +a text, chunk_size=8)" + ) + db.executemany( + "INSERT INTO vr(rowid, vector, a) VALUES (?, ?, ?)", + [(i, f"[{i / 10}, {i % 3 / 10}]", f"aux {i}") for i in range(1, 10)], + ) + db.execute("DELETE FROM vr WHERE rowid = 2") db.commit() return db.serialize() @@ -504,6 +515,27 @@ def handler(): pytest.param("DELETE FROM vt WHERE id = 'id4'", id="delete-text-pk"), pytest.param("DELETE FROM vp WHERE rowid = 4", id="delete-emptying-chunk"), pytest.param("INSERT INTO vp(vp) VALUES ('optimize')", id="optimize"), + pytest.param( + "SELECT rowid, distance, a FROM vr WHERE vector MATCH '[0.3, 0.4]' " + "AND k = 3 AND distance < 10", + id="knn-rescore", + ), + pytest.param("SELECT rowid, vector, a FROM vr", id="fullscan-rescore"), + pytest.param( + "SELECT rowid, vector, a FROM vr WHERE rowid = 4", id="point-rescore" + ), + pytest.param( + "INSERT INTO vr(rowid, vector, a) VALUES (100, '[0.1, 0.1]', 'x')", + id="insert-rescore", + ), + pytest.param( + "UPDATE vr SET vector = '[0.9, 0.9]' WHERE rowid = 4", id="update-rescore" + ), + pytest.param("DELETE FROM vr WHERE rowid = 4", id="delete-rescore"), + pytest.param( + "DELETE FROM vr WHERE rowid = 9", id="delete-emptying-rescore-chunk" + ), + pytest.param("INSERT INTO vr(vr) VALUES ('optimize')", id="optimize-rescore"), ], ) def test_interrupt_is_reported_as_interrupt(interrupt_template, sql, mode): diff --git a/tests/test-fullscan.py b/tests/test-fullscan.py index d87194c0..1445abb4 100644 --- a/tests/test-fullscan.py +++ b/tests/test-fullscan.py @@ -8,6 +8,27 @@ def _f32(values): return struct.pack(f"{len(values)}f", *values) +def test_fullscan_reads_indexed_columns(db): + # A rescore column keeps its vectors outside _vector_chunks, so a scan reads + # them row by row, beside a flat column read by chunk. + db.execute( + "create virtual table v using vec0(" + "flat float[8], " + "r float[8] indexed by rescore(quantizer=bit), " + "chunk_size=8)" + ) + for i in range(1, 21): + db.execute( + "insert into v(rowid, flat, r) values (?, ?, ?)", + [i, _f32([i] * 8), _f32([-i] * 8)], + ) + rows = db.execute("select rowid, flat, r from v").fetchall() + assert len(rows) == 20 + for rowid, flat, r in rows: + assert flat == _f32([rowid] * 8) + assert r == _f32([-rowid] * 8) + + def test_fullscan_reads_every_vector_across_chunks(db): # Two vector columns, so each keeps its own handle, over chunks with gaps. db.execute( @@ -54,10 +75,10 @@ def test_fullscan_reads_a_chunk_rewritten_during_the_scan(db): assert rows[rowid] == (rowid, 0) -def _scan_around(db, write): +def _scan_around(db, column, write): # Fetches one row of a full scan, runs `write` on the same connection, then # fetches the rest. - db.execute("create virtual table v using vec0(a float[2], chunk_size=8)") + db.execute(f"create virtual table v using vec0({column}, chunk_size=8)") for i in range(1, 17): db.execute("insert into v(rowid, a) values (?, ?)", [i, _f32([i, 0])]) db.execute("delete from v where rowid between 2 and 7") @@ -67,18 +88,26 @@ def _scan_around(db, write): return {r[0]: struct.unpack("2f", r[1]) for r in head + scan.fetchall()} -def test_fullscan_errors_on_a_row_deleted_during_the_scan(db): +# A flat column is read by chunk, a rescore column row by row. +SCANNED_COLUMNS = ["a float[2]", "a float[2] indexed by rescore(quantizer=int8)"] + + +@pytest.mark.parametrize("column", SCANNED_COLUMNS) +def test_fullscan_errors_on_a_row_deleted_during_the_scan(db, column): def write(): db.execute("delete from v where rowid = 12") db.execute("insert into v(rowid, a) values (99, ?)", [_f32([99, 99])]) # Row 99 takes row 12's slot; the scan must not return its vector as row 12. with pytest.raises(sqlite3.OperationalError, match="rowid 12"): - _scan_around(db, write) + _scan_around(db, column, write) -def test_fullscan_reads_rows_moved_by_optimize_during_the_scan(db): - rows = _scan_around(db, lambda: db.execute("insert into v(v) values ('optimize')")) +@pytest.mark.parametrize("column", SCANNED_COLUMNS) +def test_fullscan_reads_rows_moved_by_optimize_during_the_scan(db, column): + rows = _scan_around( + db, column, lambda: db.execute("insert into v(v) values ('optimize')") + ) assert rows == {i: (i, 0) for i in [1, *range(8, 17)]} diff --git a/tests/test-loadable.py b/tests/test-loadable.py index 1e351bea..ab1e2075 100644 --- a/tests/test-loadable.py +++ b/tests/test-loadable.py @@ -2984,6 +2984,58 @@ def test_vec0_constructor(): db.execute("create virtual table v using vec0(4)") +def test_vec0_indexed_by_flat(): + db.execute("drop table if exists t_ibf") + db.execute("drop table if exists t_ibf2") + db.execute("drop table if exists t_ibf3") + db.execute("drop table if exists t_ibf4") + + # indexed by flat() should succeed and behave identically to no index clause + db.execute("create virtual table t_ibf using vec0(emb float[4] indexed by flat())") + db.execute( + "insert into t_ibf(rowid, emb) values (1, X'00000000000000000000000000000000')" + ) + rows = db.execute( + "select rowid from t_ibf where emb match X'00000000000000000000000000000000' and k = 1" + ).fetchall() + assert len(rows) == 1 + assert rows[0][0] == 1 + db.execute("drop table t_ibf") + + # indexed by flat() with distance_metric + db.execute( + "create virtual table t_ibf2 using vec0(emb float[4] distance_metric=cosine indexed by flat())" + ) + db.execute("drop table t_ibf2") + + # indexed by flat() on int8 + db.execute("create virtual table t_ibf3 using vec0(emb int8[4] indexed by flat())") + db.execute("drop table t_ibf3") + + # indexed by flat() on bit + db.execute("create virtual table t_ibf4 using vec0(emb bit[8] indexed by flat())") + db.execute("drop table t_ibf4") + + # Error: unknown index type + with _raises( + "vec0 constructor error: could not parse vector column 'emb float[4] indexed by unknown()'", + sqlite3.DatabaseError, + ): + db.execute( + "create virtual table v using vec0(emb float[4] indexed by unknown())" + ) + + # Error: indexed by (missing type) + with _raises( + "vec0 constructor error: could not parse vector column 'emb float[4] indexed by'", + sqlite3.DatabaseError, + ): + db.execute("create virtual table v using vec0(emb float[4] indexed by)") + + if db.in_transaction: + db.rollback() + + def test_vec0_create_errors(): # EVIDENCE-OF: V17740_01811 vec0 create _chunks error handling db.set_authorizer(authorizer_deny_on(sqlite3.SQLITE_CREATE_TABLE, "t1_chunks")) diff --git a/tests/test-rename.py b/tests/test-rename.py index ab6732e3..bb64181e 100644 --- a/tests/test-rename.py +++ b/tests/test-rename.py @@ -1,7 +1,7 @@ """ALTER TABLE ... RENAME on vec0 tables. Ported from asg017/sqlite-vec@c560710 and @8105eee, without the tests for -upstream's ANN indexes, which this fork does not have. +upstream's DiskANN and IVF indexes, which this fork does not have. """ import sqlite3 @@ -174,6 +174,39 @@ def test_rename_with_metadata(db): assert _shadow_tables(db, "v") == [] +def test_rename_rescore(db): + """Rename should work on rescore-indexed tables (no _vector_chunks shadow).""" + db.execute(""" + CREATE VIRTUAL TABLE v USING vec0( + a float[8] indexed by rescore(quantizer=bit) + ) + """) + db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1] * 8)]) + db.execute("insert into v(rowid, a) values (2, ?)", [_f32([0.9] * 8)]) + + # Rescore columns use _rescore_chunks / _rescore_vectors instead of + # _vector_chunks; the rename must skip the missing _vector_chunks ALTER + # and rename both rescore shadow tables. + before = _shadow_tables(db, "v") + assert "v_rescore_chunks00" in before + assert "v_rescore_vectors00" in before + assert "v_vector_chunks00" not in before + + db.execute("ALTER TABLE v RENAME TO v2") + + rows = db.execute( + "select rowid from v2 where a match ? and k=10", + [_f32([0.1] * 8)], + ).fetchall() + assert rows[0][0] == 1 + + after = _shadow_tables(db, "v2") + assert "v2_rescore_chunks00" in after + assert "v2_rescore_vectors00" in after + assert "v2_vector_chunks00" not in after + assert _shadow_tables(db, "v") == [] + + def test_rename_drop_after(db): """DROP TABLE should work on a renamed table.""" db.execute("create virtual table v using vec0(a float[2], chunk_size=8)") diff --git a/tests/test-rescore-mutations.py b/tests/test-rescore-mutations.py new file mode 100644 index 00000000..349a9b21 --- /dev/null +++ b/tests/test-rescore-mutations.py @@ -0,0 +1,563 @@ +"""Mutation and edge-case tests for the rescore index feature.""" + +import struct +import sqlite3 +import pytest +import math +import random + + +def float_vec(values): + """Pack a list of floats into a blob for sqlite-vec.""" + return struct.pack(f"{len(values)}f", *values) + + +def unpack_float_vec(blob): + """Unpack a float vector blob.""" + n = len(blob) // 4 + return list(struct.unpack(f"{n}f", blob)) + + +# ============================================================================ +# Error cases: rescore + aux/metadata/partition +# ============================================================================ + + +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): + """Rescore should reject metadata columns.""" + with pytest.raises(sqlite3.OperationalError, match="Metadata columns"): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)," + " genre text" + ")" + ) + + +def test_create_error_with_partition_key(db): + """Rescore should reject partition key columns.""" + with pytest.raises(sqlite3.OperationalError, match="Partition key"): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)," + " user_id integer partition key" + ")" + ) + + +# ============================================================================ +# Insert / batch / delete / update mutations +# ============================================================================ + + +def test_insert_single_verify_knn(db): + """Insert a single row and verify KNN returns it.""" + 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] * 8)]) + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0] * 8)], + ).fetchall() + assert len(rows) == 1 + assert rows[0]["rowid"] == 1 + assert rows[0]["distance"] < 0.01 + + +def test_insert_large_batch(db): + """Insert 200+ rows (multiple chunks with default chunk_size=1024) and verify count and KNN.""" + dim = 16 + n = 200 + random.seed(99) + db.execute( + f"CREATE VIRTUAL TABLE t USING vec0(" + f" embedding float[{dim}] indexed by rescore(quantizer=int8)" + f")" + ) + for i in range(n): + v = [random.gauss(0, 1) for _ in range(dim)] + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec(v)], + ) + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == n + + # KNN should return results + query = float_vec([random.gauss(0, 1) for _ in range(dim)]) + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [query], + ).fetchall() + assert len(rows) == 10 + + +def test_delete_all_rows(db): + """Delete every row, verify count=0, KNN returns empty.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + for i in range(20): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec([float(i)] * 8)], + ) + assert db.execute("SELECT count(*) as cnt FROM t").fetchone()["cnt"] == 20 + + for i in range(20): + db.execute("DELETE FROM t WHERE rowid = ?", [i + 1]) + + assert db.execute("SELECT count(*) as cnt FROM t").fetchone()["cnt"] == 0 + + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 5", + [float_vec([0.0] * 8)], + ).fetchall() + assert len(rows) == 0 + + +def test_delete_then_reinsert_same_rowid(db): + """Delete rowid=1, re-insert rowid=1 with different vector, verify KNN uses new vector.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + # Insert rowid=1 near origin, rowid=2 far from origin + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([0.1] * 8)], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([100.0] * 8)], + ) + + # KNN to [0]*8 -> rowid 1 is closer + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([0.0] * 8)], + ).fetchall() + assert rows[0]["rowid"] == 1 + + # Delete rowid=1, re-insert with vector far from origin + db.execute("DELETE FROM t WHERE rowid = 1") + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([200.0] * 8)], + ) + + # Now KNN to [0]*8 -> rowid 2 should be closer + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([0.0] * 8)], + ).fetchall() + assert rows[0]["rowid"] == 2 + + +def test_update_vector(db): + """UPDATE the vector column and verify KNN reflects new value.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([0.0] * 8)], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([10.0] * 8)], + ) + + # Update rowid=1 to be far away + db.execute( + "UPDATE t SET embedding = ? WHERE rowid = 1", + [float_vec([100.0] * 8)], + ) + + # Now KNN to [0]*8 -> rowid 2 should be closest + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([0.0] * 8)], + ).fetchall() + assert rows[0]["rowid"] == 2 + + +def test_knn_after_delete_all_but_one(db): + """Insert 50 rows, delete 49, KNN should only return the survivor.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + for i in range(50): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec([float(i)] * 8)], + ) + # Delete all except rowid=25 + for i in range(50): + if i + 1 != 25: + db.execute("DELETE FROM t WHERE rowid = ?", [i + 1]) + + assert db.execute("SELECT count(*) as cnt FROM t").fetchone()["cnt"] == 1 + + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [float_vec([0.0] * 8)], + ).fetchall() + assert len(rows) == 1 + assert rows[0]["rowid"] == 25 + + +# ============================================================================ +# Edge cases +# ============================================================================ + + +def test_single_row_knn(db): + """Table with exactly 1 row. LIMIT 1 returns it; LIMIT 5 returns 1.""" + 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] * 8)]) + + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0] * 8)], + ).fetchall() + assert len(rows) == 1 + + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 5", + [float_vec([1.0] * 8)], + ).fetchall() + assert len(rows) == 1 + + +def test_knn_with_all_identical_vectors(db): + """All vectors are the same. All distances should be equal.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + vec = [3.0, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0, 6.0] + for i in range(10): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec(vec)], + ) + + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [float_vec(vec)], + ).fetchall() + assert len(rows) == 10 + # All distances should be ~0 (exact match) + for r in rows: + assert r["distance"] < 0.01 + + +def test_zero_vector_insert(db): + """Insert the zero vector [0,0,...,0]. Should not crash quantization.""" + 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([0.0] * 8)], + ) + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == 1 + + # Also test int8 quantizer with zero vector + db.execute( + "CREATE VIRTUAL TABLE t2 USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + db.execute( + "INSERT INTO t2(rowid, embedding) VALUES (1, ?)", + [float_vec([0.0] * 8)], + ) + row = db.execute("SELECT count(*) as cnt FROM t2").fetchone() + assert row["cnt"] == 1 + + +def test_very_large_values(db): + """Insert vectors with very large float values. Quantization should not crash.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([1e30] * 8)], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([1e30, -1e30, 1e30, -1e30, 1e30, -1e30, 1e30, -1e30])], + ) + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == 2 + + +def test_negative_values(db): + """Insert vectors with all negative values. Bit quantization maps all to 0.""" + 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, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([-0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8])], + ) + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == 2 + + # KNN should still work + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 2", + [float_vec([-0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8])], + ).fetchall() + assert len(rows) == 2 + assert rows[0]["rowid"] == 2 + + +def test_single_dimension(db): + """Single-dimension vector (edge case for quantization).""" + # int8 quantizer (bit needs dim divisible by 8) + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + db.execute("INSERT INTO t(rowid, embedding) VALUES (1, ?)", [float_vec([1.0] * 8)]) + db.execute("INSERT INTO t(rowid, embedding) VALUES (2, ?)", [float_vec([5.0] * 8)]) + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0] * 8)], + ).fetchall() + assert rows[0]["rowid"] == 1 + + +# ============================================================================ +# vec_debug() verification +# ============================================================================ + + +def test_vec_debug_contains_rescore(db): + """vec_debug() should contain 'rescore' in build flags when compiled with SQLITE_VEC_ENABLE_RESCORE.""" + row = db.execute("SELECT vec_debug() as d").fetchone() + assert "rescore" in row["d"] + + +# ============================================================================ +# Insert batch recall test +# ============================================================================ + + +def test_insert_batch_recall(db): + """Insert 150 rows and verify KNN recall is reasonable (>0.6).""" + dim = 16 + n = 150 + k = 10 + random.seed(77) + + db.execute( + f"CREATE VIRTUAL TABLE t_rescore USING vec0(" + f" embedding float[{dim}] indexed by rescore(quantizer=int8, oversample=16)" + f")" + ) + db.execute(f"CREATE VIRTUAL TABLE t_flat USING vec0(embedding float[{dim}])") + + vectors = [[random.gauss(0, 1) for _ in range(dim)] for _ in range(n)] + for i, v in enumerate(vectors): + blob = float_vec(v) + db.execute( + "INSERT INTO t_rescore(rowid, embedding) VALUES (?, ?)", [i + 1, blob] + ) + db.execute("INSERT INTO t_flat(rowid, embedding) VALUES (?, ?)", [i + 1, blob]) + + query = float_vec([random.gauss(0, 1) for _ in range(dim)]) + + rescore_rows = db.execute( + "SELECT rowid FROM t_rescore WHERE embedding MATCH ? ORDER BY distance LIMIT ?", + [query, k], + ).fetchall() + flat_rows = db.execute( + "SELECT rowid FROM t_flat WHERE embedding MATCH ? ORDER BY distance LIMIT ?", + [query, k], + ).fetchall() + + rescore_ids = {r["rowid"] for r in rescore_rows} + flat_ids = {r["rowid"] for r in flat_rows} + recall = len(rescore_ids & flat_ids) / k + assert recall >= 0.6, f"Recall too low: {recall}" + + +# ============================================================================ +# Distance metric variants +# ============================================================================ + + +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( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] distance_metric=cosine indexed by rescore(quantizer=int8)" + ")" + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (3, ?)", + [float_vec([1.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 2", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ).fetchall() + assert rows[0]["rowid"] == 1 + assert rows[0]["distance"] < 0.01 diff --git a/tests/test-rescore.py b/tests/test-rescore.py new file mode 100644 index 00000000..0632d864 --- /dev/null +++ b/tests/test-rescore.py @@ -0,0 +1,1004 @@ +"""Tests for the rescore index feature in sqlite-vec.""" + +import struct +import sqlite3 +import pytest +import math +import random + + +def float_vec(values): + """Pack a list of floats into a blob for sqlite-vec.""" + return struct.pack(f"{len(values)}f", *values) + + +def unpack_float_vec(blob): + """Unpack a float vector blob.""" + n = len(blob) // 4 + return list(struct.unpack(f"{n}f", blob)) + + +# ============================================================================ +# Creation tests +# ============================================================================ + + +def test_create_bit(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit)" + ")" + ) + # Table exists and has the right structure + row = db.execute( + "SELECT count(*) as cnt FROM sqlite_master WHERE name LIKE 't_%'" + ).fetchone() + assert row["cnt"] > 0 + + +def test_create_int8(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=int8)" + ")" + ) + row = db.execute( + "SELECT count(*) as cnt FROM sqlite_master WHERE name LIKE 't_%'" + ).fetchone() + assert row["cnt"] > 0 + + +def test_create_with_oversample(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit, oversample=16)" + ")" + ) + row = db.execute( + "SELECT count(*) as cnt FROM sqlite_master WHERE name LIKE 't_%'" + ).fetchone() + assert row["cnt"] > 0 + + +def test_create_with_distance_metric(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] distance_metric=cosine indexed by rescore(quantizer=bit)" + ")" + ) + row = db.execute( + "SELECT count(*) as cnt FROM sqlite_master WHERE name LIKE 't_%'" + ).fetchone() + assert row["cnt"] > 0 + + +def test_create_error_missing_quantizer(db): + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(oversample=8)" + ")" + ) + + +def test_create_error_invalid_quantizer(db): + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=float)" + ")" + ) + + +def test_create_error_on_bit_column(db): + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding bit[1024] indexed by rescore(quantizer=bit)" + ")" + ) + + +def test_create_error_on_int8_column(db): + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding int8[128] indexed by rescore(quantizer=bit)" + ")" + ) + + +def test_create_error_bad_oversample_zero(db): + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit, oversample=0)" + ")" + ) + + +def test_create_error_bad_oversample_too_large(db): + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit, oversample=999)" + ")" + ) + + +def test_create_error_bit_dim_not_divisible_by_8(db): + with pytest.raises(sqlite3.OperationalError): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[100] indexed by rescore(quantizer=bit)" + ")" + ) + + +# ============================================================================ +# Shadow table tests +# ============================================================================ + + +def test_shadow_tables_exist(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit)" + ")" + ) + tables = [ + r[0] + for r in db.execute( + "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 't_%' ORDER BY name" + ).fetchall() + ] + assert "t_rescore_chunks00" in tables + assert "t_rescore_vectors00" in tables + # Rescore columns don't create _vector_chunks + assert "t_vector_chunks00" not in tables + + +def test_drop_cleans_up(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit)" + ")" + ) + db.execute("DROP TABLE t") + tables = [ + r[0] + for r in db.execute( + "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 't_%'" + ).fetchall() + ] + assert len(tables) == 0 + + +# ============================================================================ +# Insert tests +# ============================================================================ + + +def test_insert_single(db): + 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] * 8)]) + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == 1 + + +def test_insert_multiple(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + for i in range(10): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec([float(i)] * 8)], + ) + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == 10 + + +# ============================================================================ +# Delete tests +# ============================================================================ + + +def test_delete_single(db): + 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] * 8)]) + db.execute("DELETE FROM t WHERE rowid = 1") + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == 0 + + +def test_delete_and_reinsert(db): + 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] * 8)]) + db.execute("DELETE FROM t WHERE rowid = 1") + db.execute("INSERT INTO t(rowid, embedding) VALUES (2, ?)", [float_vec([2.0] * 8)]) + row = db.execute("SELECT count(*) as cnt FROM t").fetchone() + assert row["cnt"] == 1 + + +def test_point_query_returns_float(db): + """SELECT by rowid should return the original float vector, not quantized.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + vals = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8] + db.execute("INSERT INTO t(rowid, embedding) VALUES (1, ?)", [float_vec(vals)]) + row = db.execute("SELECT embedding FROM t WHERE rowid = 1").fetchone() + result = unpack_float_vec(row["embedding"]) + for a, b in zip(result, vals): + assert abs(a - b) < 1e-6 + + +# ============================================================================ +# KNN tests +# ============================================================================ + + +def test_knn_basic_bit(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + # Insert vectors where [1,0,0,...] is closest to query [1,0,0,...] + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (3, ?)", + [float_vec([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ).fetchall() + assert len(rows) == 1 + assert rows[0]["rowid"] == 1 + + +def test_knn_basic_int8(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (3, ?)", + [float_vec([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ).fetchall() + assert len(rows) == 1 + assert rows[0]["rowid"] == 1 + + +def test_knn_returns_float_distances(db): + """KNN should return float-precision distances, not quantized distances.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + v1 = [1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + v2 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0] + db.execute("INSERT INTO t(rowid, embedding) VALUES (1, ?)", [float_vec(v1)]) + db.execute("INSERT INTO t(rowid, embedding) VALUES (2, ?)", [float_vec(v2)]) + + query = [1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 2", + [float_vec(query)], + ).fetchall() + + # First result should be exact match with distance ~0 + assert rows[0]["rowid"] == 1 + assert rows[0]["distance"] < 0.01 + + # Second result should have a float distance + # sqrt((1-0)^2 + (0.5-0)^2 + (0-1)^2) = sqrt(2.25) = 1.5 + assert abs(rows[1]["distance"] - 1.5) < 0.01 + + +def test_knn_recall(db): + """With enough vectors, rescore should achieve good recall (>0.9).""" + dim = 32 + n = 1000 + k = 10 + random.seed(42) + + db.execute( + "CREATE VIRTUAL TABLE t_rescore USING vec0(" + f" embedding float[{dim}] indexed by rescore(quantizer=bit, oversample=16)" + ")" + ) + db.execute(f"CREATE VIRTUAL TABLE t_flat USING vec0(embedding float[{dim}])") + + vectors = [[random.gauss(0, 1) for _ in range(dim)] for _ in range(n)] + for i, v in enumerate(vectors): + blob = float_vec(v) + db.execute( + "INSERT INTO t_rescore(rowid, embedding) VALUES (?, ?)", [i + 1, blob] + ) + db.execute("INSERT INTO t_flat(rowid, embedding) VALUES (?, ?)", [i + 1, blob]) + + query = float_vec([random.gauss(0, 1) for _ in range(dim)]) + + rescore_rows = db.execute( + "SELECT rowid FROM t_rescore WHERE embedding MATCH ? ORDER BY distance LIMIT ?", + [query, k], + ).fetchall() + flat_rows = db.execute( + "SELECT rowid FROM t_flat WHERE embedding MATCH ? ORDER BY distance LIMIT ?", + [query, k], + ).fetchall() + + rescore_ids = {r["rowid"] for r in rescore_rows} + flat_ids = {r["rowid"] for r in flat_rows} + recall = len(rescore_ids & flat_ids) / k + assert recall >= 0.7, f"Recall too low: {recall}" + + +def test_knn_cosine(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] distance_metric=cosine indexed by rescore(quantizer=bit)" + ")" + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (1, ?)", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (2, ?)", + [float_vec([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ) + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])], + ).fetchall() + assert rows[0]["rowid"] == 1 + # cosine distance of identical vectors should be ~0 + assert rows[0]["distance"] < 0.01 + + +def test_knn_empty_table(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 5", + [float_vec([1.0] * 8)], + ).fetchall() + assert len(rows) == 0 + + +def test_knn_k_larger_than_n(db): + 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] * 8)]) + db.execute("INSERT INTO t(rowid, embedding) VALUES (2, ?)", [float_vec([2.0] * 8)]) + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [float_vec([1.0] * 8)], + ).fetchall() + assert len(rows) == 2 + + +# ============================================================================ +# Integration / edge case tests +# ============================================================================ + + +def test_knn_with_rowid_in(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + for i in range(5): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec([float(i)] * 8)], + ) + # Only search within rowids 1, 3, 5 + rows = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? AND rowid IN (1, 3, 5) ORDER BY distance LIMIT 3", + [float_vec([0.0] * 8)], + ).fetchall() + result_ids = {r["rowid"] for r in rows} + assert result_ids <= {1, 3, 5} + + +def test_knn_after_deletes(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + for i in range(10): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec([float(i)] * 8)], + ) + # Delete the closest match (rowid 1 = [0,0,...]) + db.execute("DELETE FROM t WHERE rowid = 1") + rows = db.execute( + "SELECT rowid, distance FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 5", + [float_vec([0.0] * 8)], + ).fetchall() + # Verify ordering: rowid 2 ([1]*8) should be closest, then 3 ([2]*8), etc. + assert len(rows) >= 2 + assert rows[0]["distance"] <= rows[1]["distance"] + # rowid 2 = [1,1,...] → L2 = sqrt(8) ≈ 2.83, rowid 3 = [2,2,...] → L2 = sqrt(32) ≈ 5.66 + assert ( + rows[0]["rowid"] == 2 + ), f"Expected rowid 2, got {rows[0]['rowid']} with dist={rows[0]['distance']}" + + +def test_oversample_effect(db): + """Higher oversample should give equal or better recall.""" + dim = 32 + n = 500 + k = 10 + random.seed(123) + + vectors = [[random.gauss(0, 1) for _ in range(dim)] for _ in range(n)] + query = float_vec([random.gauss(0, 1) for _ in range(dim)]) + + recalls = [] + for oversample in [2, 16]: + tname = f"t_os{oversample}" + db.execute( + f"CREATE VIRTUAL TABLE {tname} USING vec0(" + f" embedding float[{dim}] indexed by rescore(quantizer=bit, oversample={oversample})" + ")" + ) + for i, v in enumerate(vectors): + db.execute( + f"INSERT INTO {tname}(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec(v)], + ) + rows = db.execute( + f"SELECT rowid FROM {tname} WHERE embedding MATCH ? ORDER BY distance LIMIT ?", + [query, k], + ).fetchall() + recalls.append({r["rowid"] for r in rows}) + + # Also get ground truth + db.execute(f"CREATE VIRTUAL TABLE t_flat USING vec0(embedding float[{dim}])") + for i, v in enumerate(vectors): + db.execute( + "INSERT INTO t_flat(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec(v)], + ) + gt_rows = db.execute( + "SELECT rowid FROM t_flat WHERE embedding MATCH ? ORDER BY distance LIMIT ?", + [query, k], + ).fetchall() + gt_ids = {r["rowid"] for r in gt_rows} + + recall_low = len(recalls[0] & gt_ids) / k + recall_high = len(recalls[1] & gt_ids) / k + assert recall_high >= recall_low + + +def test_multiple_vector_columns(db): + """One column with rescore, one without.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " v1 float[8] indexed by rescore(quantizer=bit)," + " v2 float[8]" + ")" + ) + db.execute( + "INSERT INTO t(rowid, v1, v2) VALUES (1, ?, ?)", + [float_vec([1.0] * 8), float_vec([0.0] * 8)], + ) + db.execute( + "INSERT INTO t(rowid, v1, v2) VALUES (2, ?, ?)", + [float_vec([0.0] * 8), float_vec([1.0] * 8)], + ) + + # KNN on v1 (rescore path) + rows = db.execute( + "SELECT rowid FROM t WHERE v1 MATCH ? ORDER BY distance LIMIT 1", + [float_vec([1.0] * 8)], + ).fetchall() + assert rows[0]["rowid"] == 1 + + # KNN on v2 (normal path) + rows = db.execute( + "SELECT rowid FROM t WHERE v2 MATCH ? ORDER BY distance LIMIT 1", + [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 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() + + +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): + 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): + """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)" + ")" + ) + + 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 + + +def test_runtime_oversample(db): + """oversample can be changed at query time via FTS5-style command.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit, oversample=2)" + ")" + ) + random.seed(200) + for i in range(200): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [i + 1, float_vec([random.gauss(0, 1) for _ in range(128)])], + ) + + query = float_vec([random.gauss(0, 1) for _ in range(128)]) + + # KNN with default oversample=2 (low) + rows_low = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [query], + ).fetchall() + assert len(rows_low) == 10 + + # Change oversample at runtime to high value + db.execute("INSERT INTO t(t) VALUES ('oversample=32')") + + # KNN with oversample=32 (high) — same or better recall + rows_high = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [query], + ).fetchall() + assert len(rows_high) == 10 + + # Reset to original + db.execute("INSERT INTO t(t) VALUES ('oversample=2')") + + rows_reset = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? ORDER BY distance LIMIT 10", + [query], + ).fetchall() + assert len(rows_reset) == 10 + # After reset, should match the original low-oversample results + assert [r["rowid"] for r in rows_reset] == [r["rowid"] for r in rows_low] + + +def test_runtime_oversample_error(db): + """Invalid oversample values should error.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit)" + ")" + ) + with pytest.raises(sqlite3.OperationalError, match="oversample must be >= 1"): + db.execute("INSERT INTO t(t) VALUES ('oversample=0')") + + with pytest.raises(sqlite3.OperationalError, match="oversample must be >= 1"): + db.execute("INSERT INTO t(t) VALUES ('oversample=-5')") + + +def test_unknown_command_errors(db): + """Unknown command strings should produce a clear error.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[128] indexed by rescore(quantizer=bit)" + ")" + ) + with pytest.raises(sqlite3.OperationalError, match="unknown vec0 command"): + db.execute("INSERT INTO t(t) VALUES ('not_a_real_command')") + + +# ============================================================================ +# Distance constraint tests (asg017/sqlite-vec#308) +# ============================================================================ + + +def test_knn_distance_constraint_le(db): + """A `distance <= threshold` constraint must be honored in rescore KNN.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + # rowid 1 is the exact match (distance 0); rowid 2 is far away. + v1 = [1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + v2 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0] + db.execute("INSERT INTO t(rowid, embedding) VALUES (1, ?)", [float_vec(v1)]) + db.execute("INSERT INTO t(rowid, embedding) VALUES (2, ?)", [float_vec(v2)]) + + query = [1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + rows = db.execute( + "SELECT rowid, distance FROM t " + "WHERE embedding MATCH ? AND k = 10 AND distance <= 0.5 " + "ORDER BY distance", + [float_vec(query)], + ).fetchall() + + # Only the exact match (distance ~0) is within the 0.5 threshold; + # rowid 2 (distance 1.5) must be filtered out. + assert [r["rowid"] for r in rows] == [1] + assert all(r["distance"] <= 0.5 for r in rows) + + +def test_knn_distance_constraint_lt_gt(db): + """`distance < x` and `distance > x` constraints must be honored.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + v1 = [1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + v2 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0] + db.execute("INSERT INTO t(rowid, embedding) VALUES (1, ?)", [float_vec(v1)]) + db.execute("INSERT INTO t(rowid, embedding) VALUES (2, ?)", [float_vec(v2)]) + + query = [1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + # Exclude the exact match, keep only the far vector. + rows = db.execute( + "SELECT rowid, distance FROM t " + "WHERE embedding MATCH ? AND k = 10 AND distance > 0.5 " + "ORDER BY distance", + [float_vec(query)], + ).fetchall() + assert [r["rowid"] for r in rows] == [2] + + +# ============================================================================ +# Behavior this fork adds or keeps for rescore columns +# ============================================================================ + + +def _rescore_table(db, quantizer, n=40, dim=16, chunk_size=8, seed=11): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + f" embedding float[{dim}] indexed by rescore(quantizer={quantizer})," + f" chunk_size={chunk_size}" + ")" + ) + rng = random.Random(seed) + vecs = {} + for rowid in range(1, n + 1): + vecs[rowid] = [rng.uniform(-1.5, 1.5) for _ in range(dim)] + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [rowid, float_vec(vecs[rowid])], + ) + return vecs + + +def _f32(x): + return struct.unpack("f", struct.pack("f", x))[0] + + +def _quantize(quantizer, vec): + if quantizer == "bit": + out = bytearray(len(vec) // 8) + for i, x in enumerate(vec): + if _f32(x) >= 0: + out[i // 8] |= 1 << (i % 8) + return bytes(out) + # the C quantizer's float32 arithmetic, truncated toward zero + step = _f32(2.0 / 255.0) + out = [] + for x in vec: + v = _f32(_f32(_f32(x) + 1.0) / step) + v = _f32(v - 128.0) + if not v <= 127.0: + v = 127.0 + if not v >= -128.0: + v = -128.0 + out.append(int(v)) + return struct.pack(f"{len(vec)}b", *out) + + +def _assert_quantized_slots(db, quantizer): + """Each row's slot in _rescore_chunks00 holds the quantized form of its + vector in _rescore_vectors00, each free slot is zero, and every chunk has + its _rescore_chunks00 row.""" + qsize = None + for rowid, chunk_id, offset in db.execute( + "select rowid, chunk_id, chunk_offset from t_rowids" + ).fetchall(): + vector = unpack_float_vec( + db.execute( + "select vector from t_rescore_vectors00 where rowid = ?", [rowid] + ).fetchone()[0] + ) + expected = _quantize(quantizer, vector) + qsize = len(expected) + chunk = db.execute( + "select vectors from t_rescore_chunks00 where rowid = ?", [chunk_id] + ).fetchone()[0] + assert chunk[offset * qsize : (offset + 1) * qsize] == expected + for chunk_id, validity, vectors in db.execute( + "select c.chunk_id, c.validity, r.vectors from t_chunks c " + "join t_rescore_chunks00 r on r.rowid = c.chunk_id" + ).fetchall(): + size = len(vectors) // (len(validity) * 8) + for j in range(len(validity) * 8): + if not (validity[j // 8] >> (j % 8)) & 1: + assert not any(vectors[j * size : (j + 1) * size]) + assert ( + db.execute( + "select count(*) from t_chunks " + "where chunk_id not in (select rowid from t_rescore_chunks00)" + ).fetchone()[0] + == 0 + ) + assert ( + db.execute( + "select count(*) from t_rescore_chunks00 " + "where rowid not in (select chunk_id from t_chunks)" + ).fetchone()[0] + == 0 + ) + assert ( + db.execute( + "select count(*) from t_rescore_vectors00 " + "where rowid not in (select rowid from t_rowids)" + ).fetchone()[0] + == 0 + ) + + +def test_point_query_missing_rowid(db): + """A point query for a rowid not in the table returns no row.""" + 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] * 8)]) + assert db.execute("SELECT rowid FROM t WHERE rowid = 999").fetchall() == [] + assert [r[0] for r in db.execute("SELECT rowid FROM t WHERE rowid = 1")] == [1] + + +@pytest.mark.parametrize("quantizer", ["bit", "int8"]) +def test_quantized_slots_follow_writes(db, quantizer): + """Inserts, updates, deletes that empty chunks, and optimize keep each row's + quantized vector in its own slot.""" + rng = random.Random(5) + _rescore_table(db, quantizer) + _assert_quantized_slots(db, quantizer) + + db.execute("DELETE FROM t WHERE rowid % 3 = 0") + _assert_quantized_slots(db, quantizer) + + for rowid in range(1, 41, 7): + db.execute( + "UPDATE t SET embedding = ? WHERE rowid = ?", + [float_vec([rng.uniform(-1.5, 1.5) for _ in range(16)]), rowid], + ) + _assert_quantized_slots(db, quantizer) + + # rows 1 to 16 fill the first two chunks, so their deletion empties them + db.execute("DELETE FROM t WHERE rowid <= 16") + _assert_quantized_slots(db, quantizer) + + db.execute("INSERT INTO t(t) VALUES ('optimize')") + _assert_quantized_slots(db, quantizer) + + for rowid in range(100, 120): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", + [rowid, float_vec([rng.uniform(-1.5, 1.5) for _ in range(16)])], + ) + _assert_quantized_slots(db, quantizer) + + +def test_optimize_keeps_knn_results(db): + """optimize moves rows to new chunks without changing what a KNN query + returns, here with an oversample small enough that the quantized vectors + decide the candidates.""" + vecs = _rescore_table(db, "int8", n=200) + db.execute("INSERT INTO t(t) VALUES ('oversample=1')") + db.execute("DELETE FROM t WHERE rowid % 2 = 0") + query = float_vec([0.3] * 16) + knn = "SELECT rowid, distance FROM t WHERE embedding MATCH ? AND k = 5" + before = db.execute(knn, [query]).fetchall() + db.execute("INSERT INTO t(t) VALUES ('optimize')") + after = db.execute(knn, [query]).fetchall() + assert [tuple(r) for r in after] == [tuple(r) for r in before] + assert db.execute("SELECT count(*) FROM t_rescore_vectors00").fetchone()[0] == 100 + + +def test_delete_last_row_deletes_rescore_chunk(db): + """A DELETE that empties a chunk deletes its _rescore_chunks00 row too.""" + _rescore_table(db, "bit", n=16) + assert db.execute("SELECT count(*) FROM t_rescore_chunks00").fetchone()[0] == 2 + db.execute("DELETE FROM t WHERE rowid <= 8") + assert db.execute("SELECT count(*) FROM t_rescore_chunks00").fetchone()[0] == 1 + assert db.execute("SELECT count(*) FROM t_rescore_vectors00").fetchone()[0] == 8 + db.execute("DELETE FROM t") + assert db.execute("SELECT count(*) FROM t_rescore_chunks00").fetchone()[0] == 0 + assert db.execute("SELECT count(*) FROM t_rescore_vectors00").fetchone()[0] == 0 + + +def test_knn_distance_constraint_non_numeric(db): + """No distance compares with NULL, and every distance is less than any TEXT + or BLOB, as for a flat column.""" + _rescore_table(db, "bit", n=10) + query = float_vec([0.0] * 16) + knn = "SELECT rowid FROM t WHERE embedding MATCH ? AND k = 5 AND distance {} ?" + for op, value, expected in [ + ("<", None, 0), + (">", None, 0), + ("<", "a", 5), + ("<=", b"\x00", 5), + (">", "a", 0), + (">=", b"\x00", 0), + ]: + rows = db.execute(knn.format(op), [query, value]).fetchall() + assert len(rows) == expected, (op, value) + + +def test_knn_mmr(db): + """mmr_lambda reranks a rescore column's candidates by their float32 + vectors.""" + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=int8)" + ")" + ) + vectors = { + 1: [1.0, 0, 0, 0, 0, 0, 0, 0], + 2: [0.99, 0.1, 0, 0, 0, 0, 0, 0], + 3: [0.98, 0.2, 0, 0, 0, 0, 0, 0], + 4: [0, 1.0, 0, 0, 0, 0, 0, 0], + 5: [0, 0, 1.0, 0, 0, 0, 0, 0], + } + for rowid, v in vectors.items(): + db.execute( + "INSERT INTO t(rowid, embedding) VALUES (?, ?)", [rowid, float_vec(v)] + ) + query = float_vec([1.0, 0, 0, 0, 0, 0, 0, 0]) + plain = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? AND k = 3", [query] + ).fetchall() + assert [r[0] for r in plain] == [1, 2, 3] + diverse = db.execute( + "SELECT rowid FROM t WHERE embedding MATCH ? AND k = 3 AND mmr_lambda = 0.1", + [query], + ).fetchall() + assert [r[0] for r in diverse][0] == 1 + assert {r[0] for r in diverse} != {1, 2, 3} + + +def test_runtime_oversample_rejects_trailing_text(db): + db.execute( + "CREATE VIRTUAL TABLE t USING vec0(" + " embedding float[8] indexed by rescore(quantizer=bit)" + ")" + ) + for command in ["oversample=8x", "oversample=", "oversample= 8", "oversample=+8"]: + with pytest.raises(sqlite3.OperationalError, match="oversample must be >= 1"): + db.execute("INSERT INTO t(t) VALUES (?)", [command]) + db.execute("INSERT INTO t(t) VALUES ('oversample=200')") + + +def test_vec_debug_build_flags(db): + assert "rescore" in db.execute("SELECT vec_debug()").fetchone()[0] diff --git a/tests/test-unit.c b/tests/test-unit.c index 21d1fd40..5806c3c5 100644 --- a/tests/test-unit.c +++ b/tests/test-unit.c @@ -3,6 +3,7 @@ #include #include #include +#include #define countof(x) (sizeof(x) / sizeof((x)[0])) @@ -107,6 +108,24 @@ void test_vec0_token_next() { assert(token.token_type == TOKEN_TYPE_DIGIT); assert(token.end - token.start == 2); + // Left paren + input = "("; + rc = vec0_token_next(input, input + 1, &token); + assert(rc == VEC0_TOKEN_RESULT_SOME); + assert(token.token_type == TOKEN_TYPE_LPAREN); + + // Right paren + input = ")"; + rc = vec0_token_next(input, input + 1, &token); + assert(rc == VEC0_TOKEN_RESULT_SOME); + assert(token.token_type == TOKEN_TYPE_RPAREN); + + // Comma + input = ","; + rc = vec0_token_next(input, input + 1, &token); + assert(rc == VEC0_TOKEN_RESULT_SOME); + assert(token.token_type == TOKEN_TYPE_COMMA); + printf(" All vec0_token_next tests passed.\n"); } @@ -385,6 +404,91 @@ void test_vec0_parse_vector_column() { assert(rc != SQLITE_OK); } + // indexed by flat() + { + const char *input = "emb float[768] indexed by flat()"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_FLAT); + assert(col.dimensions == 768); + sqlite3_free(col.name); + } + + // indexed by flat() with distance_metric + { + const char *input = + "emb float[768] distance_metric=cosine indexed by flat()"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_FLAT); + assert(col.distance_metric == VEC0_DISTANCE_METRIC_COSINE); + sqlite3_free(col.name); + } + + // indexed by flat() on int8 + { + const char *input = "emb int8[256] indexed by flat()"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_FLAT); + assert(col.element_type == SQLITE_VEC_ELEMENT_TYPE_INT8); + sqlite3_free(col.name); + } + + // indexed by flat() on bit + { + const char *input = "emb bit[64] indexed by flat()"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_FLAT); + assert(col.element_type == SQLITE_VEC_ELEMENT_TYPE_BIT); + sqlite3_free(col.name); + } + + // default index_type is FLAT + { + const char *input = "emb float[768]"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_FLAT); + sqlite3_free(col.name); + } + + // Error: indexed by (missing type name) + { + const char *input = "emb float[768] indexed by"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_ERROR); + } + + // Error: indexed by unknown() + { + const char *input = "emb float[768] indexed by unknown()"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_ERROR); + } + + // Error: indexed by flat (missing parens) + { + const char *input = "emb float[768] indexed by flat"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_ERROR); + } + + // Error: indexed flat() (missing "by") + { + const char *input = "emb float[768] indexed flat()"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_ERROR); + } + + // Error: a prefix of a keyword, even when not strict + { + const char *input = "emb float[768] index by flat()"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, false); + assert(rc == SQLITE_ERROR); + } + printf(" All vec0_parse_vector_column tests passed.\n"); } @@ -495,6 +599,234 @@ void test_vec0_fullscan_schema_rollback() { } } +#if SQLITE_VEC_ENABLE_RESCORE + +void test_rescore_quantize_float_to_bit() { + printf("Starting %s...\n", __func__); + uint8_t dst[16]; + + // All positive -> all bits 1 + { + float src[8] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; + memset(dst, 0, sizeof(dst)); + rescore_quantize_float_to_bit(src, dst, 8); + assert(dst[0] == 0xFF); + } + + // All negative -> all bits 0 + { + float src[8] = {-1.0f, -2.0f, -3.0f, -4.0f, -5.0f, -6.0f, -7.0f, -8.0f}; + memset(dst, 0xFF, sizeof(dst)); + rescore_quantize_float_to_bit(src, dst, 8); + assert(dst[0] == 0x00); + } + + // Alternating positive/negative + { + float src[8] = {1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f}; + rescore_quantize_float_to_bit(src, dst, 8); + // bits 0,2,4,6 set => 0b01010101 = 0x55 + assert(dst[0] == 0x55); + } + + // Zero values -> bit is set (>= 0.0f) + { + float src[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + rescore_quantize_float_to_bit(src, dst, 8); + assert(dst[0] == 0xFF); + } + + // 128 dimensions -> 16 bytes output + { + float src[128]; + for (int i = 0; i < 128; i++) + src[i] = (i % 2 == 0) ? 1.0f : -1.0f; + memset(dst, 0, 16); + rescore_quantize_float_to_bit(src, dst, 128); + // Even indices set: bits 0,2,4,6 in each byte => 0x55 + for (int i = 0; i < 16; i++) { + assert(dst[i] == 0x55); + } + } + + printf(" All rescore_quantize_float_to_bit tests passed.\n"); +} + +void test_rescore_quantize_float_to_int8() { + printf("Starting %s...\n", __func__); + int8_t dst[16]; + + // [-1, 1] maps onto [-128, 127] in steps of 2/255, truncating toward zero. + // 1.0 lands just below 127 in float arithmetic, so it truncates to 126. + { + float src[6] = {-1.0f, -0.5f, 0.0f, 0.5f, 1.0f, 1.004f}; + rescore_quantize_float_to_int8(src, dst, 6); + assert(dst[0] == -128); + assert(dst[1] == -64); + assert(dst[2] == 0); + assert(dst[3] == 63); + assert(dst[4] == 126); + assert(dst[5] == 127); + } + + // Values outside [-1, 1] clamp, and NaN clamps to 127 + { + float src[5] = {2.0f, -2.0f, INFINITY, -INFINITY, NAN}; + rescore_quantize_float_to_int8(src, dst, 5); + assert(dst[0] == 127); + assert(dst[1] == -128); + assert(dst[2] == 127); + assert(dst[3] == -128); + assert(dst[4] == 127); + } + + // The mapping never decreases + { + int8_t prev = -128; + for (int i = 0; i <= 2200; i++) { + float src = -1.1f + (float)i * 0.001f; + int8_t out; + rescore_quantize_float_to_int8(&src, &out, 1); + assert(out >= prev); + prev = out; + } + } + + printf(" All rescore_quantize_float_to_int8 tests passed.\n"); +} + +void test_rescore_quantized_byte_size() { + printf("Starting %s...\n", __func__); + struct VectorColumnDefinition col; + memset(&col, 0, sizeof(col)); + + // Bit quantizer: dims/8 + col.rescore.quantizer_type = VEC0_RESCORE_QUANTIZER_BIT; + col.dimensions = 128; + assert(rescore_quantized_byte_size(&col) == 16); + col.dimensions = 8; + assert(rescore_quantized_byte_size(&col) == 1); + col.dimensions = 1024; + assert(rescore_quantized_byte_size(&col) == 128); + + // Int8 quantizer: dims + col.rescore.quantizer_type = VEC0_RESCORE_QUANTIZER_INT8; + col.dimensions = 128; + assert(rescore_quantized_byte_size(&col) == 128); + col.dimensions = 8; + assert(rescore_quantized_byte_size(&col) == 8); + col.dimensions = 1024; + assert(rescore_quantized_byte_size(&col) == 1024); + + printf(" All rescore_quantized_byte_size tests passed.\n"); +} + +void test_vec0_parse_vector_column_rescore() { + printf("Starting %s...\n", __func__); + struct VectorColumnDefinition col; + int rc; + + // Basic bit quantizer + { + const char *input = "emb float[128] indexed by rescore(quantizer=bit)"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_RESCORE); + assert(col.rescore.quantizer_type == VEC0_RESCORE_QUANTIZER_BIT); + assert(col.rescore.oversample == 8); // default + assert(col.rescore.oversample_search == 0); + sqlite3_free(col.name); + } + + // Int8 quantizer + { + const char *input = "emb float[128] indexed by rescore(quantizer=int8)"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_RESCORE); + assert(col.rescore.quantizer_type == VEC0_RESCORE_QUANTIZER_INT8); + sqlite3_free(col.name); + } + + // Bit quantizer with oversample, in either order + { + const char *input = + "emb float[128] indexed by rescore(quantizer=bit, oversample=16)"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_RESCORE); + assert(col.rescore.quantizer_type == VEC0_RESCORE_QUANTIZER_BIT); + assert(col.rescore.oversample == 16); + sqlite3_free(col.name); + } + { + const char *input = + "emb float[128] indexed by rescore(oversample=128, quantizer=int8)"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.rescore.quantizer_type == VEC0_RESCORE_QUANTIZER_INT8); + assert(col.rescore.oversample == 128); + sqlite3_free(col.name); + } + + // With distance_metric=cosine + { + const char *input = "emb float[128] distance_metric=cosine indexed by " + "rescore(quantizer=int8)"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, true); + assert(rc == SQLITE_OK); + assert(col.index_type == VEC0_INDEX_TYPE_RESCORE); + assert(col.distance_metric == VEC0_DISTANCE_METRIC_COSINE); + assert(col.rescore.quantizer_type == VEC0_RESCORE_QUANTIZER_INT8); + sqlite3_free(col.name); + } + + const char *errors[] = { + // a non-float element type + "emb int8[128] indexed by rescore(quantizer=bit)", + "emb bit[128] indexed by rescore(quantizer=bit)", + // dimensions not divisible by 8 for the bit quantizer + "emb float[100] indexed by rescore(quantizer=bit)", + // no quantizer + "emb float[128] indexed by rescore(oversample=8)", + "emb float[128] indexed by rescore()", + "emb float[128] indexed by rescore", + // an unknown quantizer or option + "emb float[128] indexed by rescore(quantizer=float)", + "emb float[128] indexed by rescore(quantizer=bit, k=8)", + // oversample outside 1 to 128, or not an integer + "emb float[128] indexed by rescore(quantizer=bit, oversample=0)", + "emb float[128] indexed by rescore(quantizer=bit, oversample=129)", + "emb float[128] indexed by rescore(quantizer=bit, oversample=bit)", + // malformed options + "emb float[128] indexed by rescore(quantizer=bit", + "emb float[128] indexed by rescore(quantizer=bit,)", + "emb float[128] indexed by rescore(quantizer bit)", + "emb float[128] indexed by rescore(quantizer=bit oversample=8)", + }; + for (size_t i = 0; i < countof(errors); i++) { + rc = vec0_parse_vector_column(errors[i], (int)strlen(errors[i]), &col, + true); + assert(rc == SQLITE_ERROR); + } + + // Error: a prefix of a keyword, even when not strict + { + const char *input = "emb float[128] indexed by rescor(quantizer=bit)"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, false); + assert(rc == SQLITE_ERROR); + } + { + const char *input = "emb float[128] indexed by rescore(quant=bit)"; + rc = vec0_parse_vector_column(input, (int)strlen(input), &col, false); + assert(rc == SQLITE_ERROR); + } + + printf(" All vec0_parse_vector_column_rescore tests passed.\n"); +} + +#endif /* SQLITE_VEC_ENABLE_RESCORE */ + int main() { printf("Starting unit tests...\n"); test_vec0_token_next(); @@ -502,5 +834,11 @@ int main() { test_vec0_parse_vector_column(); test_vec0_parse_partition_key_definition(); test_vec0_fullscan_schema_rollback(); +#if SQLITE_VEC_ENABLE_RESCORE + test_rescore_quantize_float_to_bit(); + test_rescore_quantize_float_to_int8(); + test_rescore_quantized_byte_size(); + test_vec0_parse_vector_column_rescore(); +#endif printf("All unit tests passed.\n"); }