feat(python): expose code analyzer options on FTS and tokenize

Part of #4364 (Python side; the TypeScript `FtsOptions` change will follow in a separate PR).

Lance already supports the code analyzer, but `FTS` and `lancedb.tokenize()` had no way to set it, so `FTS({'analyzer': 'code'})` and `FTS(split_identifiers=True)` raised `TypeError`.

## Changes

- `FTS` and `lancedb.tokenize()` gain `analyzer` (`"text"` or `"code"`), `split_identifiers`, `split_on_numerics`, `preserve_original` and `index_operators`. They default to `None`, which keeps the tokenizer's own defaults, so existing indexes and calls are unchanged.
- Validation follows pylance: `analyzer="code"` with a `base_tokenizer` other than `"code"` or the default `"simple"` is rejected, `analyzer="text"` with `base_tokenizer="code"` is rejected, and turning on a code flag without the code analyzer is rejected. Passing a flag as `False` is always allowed.
- `analyzer="code"` with the default `base_tokenizer="simple"` selects the code tokenizer. `FTS` can't tell an explicit `"simple"` from the default, so that case is accepted too.
- The deprecated `create_fts_index` is left alone; it points users at `create_index(config=FTS(...))`.
- Rust `FtsIndexBuilder` is Lance's `InvertedIndexParams`, so the Rust SDK already had these options. The remote client serializes the same struct, so the options reach LanceDB Distributed in the `create_index` request body. A new case in the Rust `test_create_index` checks that body.

## Tests

- `python/tests/test_fts.py`: tokenizer output for each flag, validation errors for `tokenize` and `FTS`, `False` flags on a text index, and local sync and async indexes that match `http`, `request` and `parseHTTPRequest` with splitting on and not match subwords with it off.
- `cargo test --features remote remote::table::tests::test_create_index`
This commit is contained in:
SHAIK SHIVAJI
2026-11-02 16:49:14 +08:00
committed by GitHub
parent 558f64c45f
commit 45edaa350e
7 changed files with 315 additions and 5 deletions
+21 -2
View File
@@ -14,7 +14,7 @@ use crate::{
connection::Connection,
error::PythonErrorExt,
expr::PyExpr,
index::{IndexConfig, extract_index_params},
index::{CodeAnalyzerOptions, IndexConfig, extract_index_params},
query::{Query, TakeQuery},
table::scannable::PyScannable,
};
@@ -1030,7 +1030,12 @@ impl From<LanceDbFtsToken> for FtsToken {
ascii_folding = true,
ngram_min_length = 3,
ngram_max_length = 3,
prefix_only = false
prefix_only = false,
analyzer = None,
split_identifiers = None,
split_on_numerics = None,
preserve_original = None,
index_operators = None
))]
#[allow(clippy::too_many_arguments)]
pub fn tokenize(
@@ -1046,7 +1051,20 @@ pub fn tokenize(
ngram_min_length: u32,
ngram_max_length: u32,
prefix_only: bool,
analyzer: Option<String>,
split_identifiers: Option<bool>,
split_on_numerics: Option<bool>,
preserve_original: Option<bool>,
index_operators: Option<bool>,
) -> PyResult<Vec<FtsToken>> {
let code_options = CodeAnalyzerOptions {
analyzer,
split_identifiers,
split_on_numerics,
preserve_original,
index_operators,
};
let base_tokenizer = code_options.resolve_base_tokenizer(base_tokenizer)?;
let mut params = FtsIndexBuilder::default()
.base_tokenizer(base_tokenizer)
.language(&language)
@@ -1069,6 +1087,7 @@ pub fn tokenize(
if let Some(remove_stop_words) = remove_stop_words {
params = params.remove_stop_words(remove_stop_words);
}
let params = code_options.apply(params);
let tokens = lancedb_tokenize(&query, &params).infer_error()?;
Ok(tokens.into_iter().map(FtsToken::from).collect())
}