fix(rust): reject bitmap indexes on JSON fields (#3895)

## Summary

- reject whole-document `lance.json` fields during native BITMAP index
preparation
- preserve BITMAP support for raw `LargeBinary` fields
- return guidance to use a JSON-path scalar index or FTS instead
- add regression coverage for the logical JSON type while retaining the
existing raw binary coverage

## Root cause

Native scalar-index validation resolved the complete Arrow field but
checked BITMAP compatibility only against its physical data type.
Because `lance.json` is stored as `LargeBinary`, it was incorrectly
accepted under the raw binary compatibility rule.

The fix reuses Lance’s `lance_arrow::json::is_json_field` helper before
physical type validation. Remote serialization is unchanged, so remote
clients continue to send the requested BITMAP type for server-side
validation.

## Validation

- `cargo fmt --all -- --check`
- `cargo test --quiet --features remote -p lancedb
test_create_bitmap_index -- --nocapture`
- `cargo check --quiet --features remote --tests --examples`
- `cargo clippy --quiet --features remote --tests --examples`
- `cargo test --quiet --features remote --tests`

Fixes #3889

<!-- lance-gatekeeper-fix:v1 agent=e097fc02a548edc0d0be2e18c65c03a3
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
This commit is contained in:
lancedb-gatefixer[bot]
2026-08-21 16:44:21 -07:00
committed by GitHub
parent c0df2c63b6
commit 5468f3d490
+38
View File
@@ -12,6 +12,7 @@ use arrow_schema::{DataType, Field};
use lance::index::DatasetIndexExt;
use lance::index::vector::VectorIndexParams;
use lance::index::vector::utils::infer_vector_dim;
use lance_arrow::json::is_json_field;
use lance_index::IndexType;
use lance_index::scalar::{BuiltinIndexType, ScalarIndexParams};
use lance_index::vector::bq::RQBuildParams;
@@ -219,6 +220,14 @@ impl NativeTable {
)))
}
Index::Bitmap(_) => {
if is_json_field(field) {
return Err(Error::Schema {
message: format!(
"A BITMAP index cannot be created on the whole-document lance.json field `{}`. Create a JSON-path scalar index for structured equality or range predicates, or use FTS for document search",
field.name()
),
});
}
Self::validate_index_type(field, "Bitmap", supported_bitmap_data_type)?;
Ok(Box::new(ScalarIndexParams::for_builtin(
BuiltinIndexType::Bitmap,
@@ -1465,6 +1474,35 @@ mod tests {
assert_eq!(stats.distance_type, None);
}
#[tokio::test]
async fn test_create_bitmap_index_rejects_lance_json() {
let conn = connect("memory://").execute().await.unwrap();
let schema = Arc::new(Schema::new(vec![lance_arrow::json::json_field(
"metadata", true,
)]));
let table = conn
.create_empty_table("json_bitmap", schema)
.execute()
.await
.unwrap();
let err = table
.create_index(&["metadata"], Index::Bitmap(Default::default()))
.execute()
.await
.expect_err("a whole-document lance.json field must not support a bitmap index");
let message = err.to_string();
assert!(
message.contains("lance.json"),
"unexpected error: {message}"
);
assert!(
message.contains("JSON-path scalar index"),
"unexpected error: {message}"
);
assert!(message.contains("FTS"), "unexpected error: {message}");
}
#[tokio::test]
async fn test_create_label_list_index() {
let conn = connect("memory://").execute().await.unwrap();