1use lance_core::utils::row_addr_remap::RowAddrRemap;
5use std::{
6 ops::Bound,
7 sync::{Arc, Mutex},
8};
9
10use arrow_array::{Array, LargeBinaryArray, RecordBatch, StructArray, UInt8Array};
11use arrow_schema::{DataType, Field, Field as ArrowField, Schema, SortOptions};
12use async_trait::async_trait;
13use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
14use datafusion::{
15 execution::SendableRecordBatchStream,
16 physical_plan::{ExecutionPlan, projection::ProjectionExec, sorts::sort::SortExec},
17};
18use datafusion_common::{ScalarValue, config::ConfigOptions};
19use datafusion_expr::{Expr, Operator, ScalarUDF};
20use datafusion_physical_expr::{
21 PhysicalExpr, PhysicalSortExpr, ScalarFunctionExpr,
22 expressions::{Column, Literal},
23};
24use futures::StreamExt;
25use lance_core::deepsize::DeepSizeOf;
26use lance_datafusion::exec::{
27 LanceExecutionOptions, OneShotExec, execute_plan, get_session_context,
28};
29use lance_datafusion::udf::json::JsonbType;
30use prost::Message;
31use roaring::RoaringBitmap;
32use serde::{Deserialize, Serialize};
33
34use lance_core::{Error, ROW_ID, Result, cache::LanceCache, error::LanceOptionExt};
35
36use crate::{
37 Index, IndexType,
38 metrics::MetricsCollector,
39 registry::IndexPluginRegistry,
40 scalar::{
41 AnyQuery, CreatedIndex, IndexStore, RowIdRemapper, ScalarIndex, SearchResult,
42 UpdateCriteria,
43 expression::{IndexedExpression, ScalarIndexExpr, ScalarIndexSearch, ScalarQueryParser},
44 registry::{
45 BasicTrainer, ScalarIndexPlugin, TrainingCriteria, TrainingOrdering, TrainingRequest,
46 VALUE_COLUMN_NAME,
47 },
48 },
49};
50
51const JSON_INDEX_VERSION: u32 = 0;
52
53#[derive(Debug)]
57pub struct JsonIndex {
58 target_index: Arc<dyn ScalarIndex>,
59 path: String,
60}
61
62impl JsonIndex {
63 pub fn new(target_index: Arc<dyn ScalarIndex>, path: String) -> Self {
64 Self { target_index, path }
65 }
66}
67
68impl DeepSizeOf for JsonIndex {
69 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
70 self.target_index.deep_size_of_children(context) + self.path.deep_size_of_children(context)
71 }
72}
73
74#[async_trait]
75impl Index for JsonIndex {
76 fn as_any(&self) -> &dyn std::any::Any {
77 self
78 }
79
80 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
81 self
82 }
83
84 fn index_type(&self) -> IndexType {
85 IndexType::Scalar
88 }
89
90 async fn prewarm(&self) -> Result<()> {
91 self.target_index.prewarm().await
92 }
93
94 fn statistics(&self) -> Result<serde_json::Value> {
95 todo!()
96 }
97
98 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
99 self.target_index.calculate_included_frags().await
100 }
101}
102
103#[async_trait]
104impl ScalarIndex for JsonIndex {
105 async fn search(
106 &self,
107 query: &dyn AnyQuery,
108 metrics: &dyn MetricsCollector,
109 ) -> Result<SearchResult> {
110 let query = query.as_any().downcast_ref::<JsonQuery>().unwrap();
111 self.target_index
112 .search(query.target_query.as_ref(), metrics)
113 .await
114 }
115
116 fn can_remap(&self) -> bool {
117 self.target_index.can_remap()
118 }
119
120 async fn remap(
121 &self,
122 mapping: &RowAddrRemap,
123 dest_store: &dyn IndexStore,
124 ) -> Result<CreatedIndex> {
125 let target_created = self.target_index.remap(mapping, dest_store).await?;
126 let json_details = crate::pb::JsonIndexDetails {
127 path: self.path.clone(),
128 target_details: Some(target_created.index_details),
129 };
130 Ok(CreatedIndex {
131 index_details: prost_types::Any::from_msg(&json_details)?,
132 index_version: JSON_INDEX_VERSION,
134 files: target_created.files,
135 })
136 }
137
138 async fn update(
139 &self,
140 new_data: SendableRecordBatchStream,
141 dest_store: &dyn IndexStore,
142 old_data_filter: Option<super::OldIndexDataFilter>,
143 ) -> Result<CreatedIndex> {
144 let target_created = self
145 .target_index
146 .update(new_data, dest_store, old_data_filter)
147 .await?;
148 let json_details = crate::pb::JsonIndexDetails {
149 path: self.path.clone(),
150 target_details: Some(target_created.index_details),
151 };
152 Ok(CreatedIndex {
153 index_details: prost_types::Any::from_msg(&json_details)?,
154 index_version: JSON_INDEX_VERSION,
156 files: target_created.files,
157 })
158 }
159
160 fn update_criteria(&self) -> UpdateCriteria {
161 self.target_index.update_criteria()
162 }
163
164 fn derive_index_params(&self) -> Result<super::ScalarIndexParams> {
165 self.target_index.derive_index_params()
166 }
167}
168
169#[derive(Debug, Serialize, Deserialize)]
171pub struct JsonIndexParameters {
172 target_index_type: String,
173 target_index_parameters: Option<String>,
174 path: String,
175}
176
177#[derive(Debug, Clone)]
182pub struct JsonQuery {
183 target_query: Arc<dyn AnyQuery>,
184 path: String,
185}
186
187impl JsonQuery {
188 pub fn new(target_query: Arc<dyn AnyQuery>, path: String) -> Self {
189 Self { target_query, path }
190 }
191}
192
193impl PartialEq for JsonQuery {
194 fn eq(&self, other: &Self) -> bool {
195 self.target_query.dyn_eq(other.target_query.as_ref()) && self.path == other.path
196 }
197}
198
199impl AnyQuery for JsonQuery {
200 fn as_any(&self) -> &dyn std::any::Any {
201 self
202 }
203
204 fn format(&self, col: &str) -> String {
205 format!("Json({}->{})", self.target_query.format(col), self.path)
206 }
207
208 fn to_expr(&self, _col: String) -> Expr {
209 todo!()
210 }
211
212 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool {
213 match other.as_any().downcast_ref::<Self>() {
214 Some(o) => self == o,
215 None => false,
216 }
217 }
218}
219
220#[derive(Debug)]
221pub struct JsonQueryParser {
222 path: String,
223 target_parser: Box<dyn ScalarQueryParser>,
224}
225
226impl JsonQueryParser {
227 pub fn new(path: String, target_parser: Box<dyn ScalarQueryParser>) -> Self {
228 Self {
229 path,
230 target_parser,
231 }
232 }
233
234 fn wrap_search(&self, target_expr: IndexedExpression) -> IndexedExpression {
235 if let Some(scalar_query) = target_expr.scalar_query {
236 let scalar_query = match scalar_query {
237 ScalarIndexExpr::Query(ScalarIndexSearch {
238 column,
239 index_name,
240 index_type,
241 query,
242 needs_recheck,
243 fragment_bitmap,
244 }) => ScalarIndexExpr::Query(ScalarIndexSearch {
245 column,
246 index_name,
247 index_type,
248 query: Arc::new(JsonQuery::new(query, self.path.clone())),
249 needs_recheck,
250 fragment_bitmap,
251 }),
252 _ => unreachable!(),
254 };
255 IndexedExpression {
256 scalar_query: Some(scalar_query),
257 refine_expr: target_expr.refine_expr,
258 }
259 } else {
260 target_expr
261 }
262 }
263}
264
265impl ScalarQueryParser for JsonQueryParser {
266 fn visit_between(
267 &self,
268 column: &str,
269 low: &Bound<ScalarValue>,
270 high: &Bound<ScalarValue>,
271 ) -> Option<IndexedExpression> {
272 self.target_parser
273 .visit_between(column, low, high)
274 .map(|target_expr| self.wrap_search(target_expr))
275 }
276 fn visit_in_list(&self, column: &str, in_list: &[ScalarValue]) -> Option<IndexedExpression> {
277 self.target_parser
278 .visit_in_list(column, in_list)
279 .map(|target_expr| self.wrap_search(target_expr))
280 }
281 fn visit_is_bool(&self, column: &str, value: bool) -> Option<IndexedExpression> {
282 self.target_parser
283 .visit_is_bool(column, value)
284 .map(|target_expr| self.wrap_search(target_expr))
285 }
286 fn visit_is_null(&self, column: &str) -> Option<IndexedExpression> {
287 self.target_parser
288 .visit_is_null(column)
289 .map(|target_expr| self.wrap_search(target_expr))
290 }
291 fn visit_comparison(
292 &self,
293 column: &str,
294 value: &ScalarValue,
295 op: &Operator,
296 ) -> Option<IndexedExpression> {
297 self.target_parser
298 .visit_comparison(column, value, op)
299 .map(|target_expr| self.wrap_search(target_expr))
300 }
301 fn visit_scalar_function(
302 &self,
303 column: &str,
304 data_type: &DataType,
305 func: &ScalarUDF,
306 args: &[Expr],
307 ) -> Option<IndexedExpression> {
308 self.target_parser
309 .visit_scalar_function(column, data_type, func, args)
310 .map(|target_expr| self.wrap_search(target_expr))
311 }
312
313 fn is_valid_reference(&self, func: &Expr, _data_type: &DataType) -> Option<DataType> {
315 match func {
316 Expr::ScalarFunction(udf) => {
317 let json_functions = [
319 "json_extract",
320 "json_get",
321 "json_get_int",
322 "json_get_float",
323 "json_get_bool",
324 "json_get_string",
325 ];
326 if !json_functions.contains(&udf.name()) {
327 return None;
328 }
329 if udf.args.len() != 2 {
330 return None;
331 }
332 match &udf.args[1] {
335 Expr::Literal(ScalarValue::Utf8(Some(path)), _) => {
336 if path == &self.path {
337 match udf.name() {
339 "json_get_int" => Some(DataType::Int64),
340 "json_get_float" => Some(DataType::Float64),
341 "json_get_bool" => Some(DataType::Boolean),
342 "json_get_string" | "json_extract" => Some(DataType::Utf8),
343 _ => None,
344 }
345 } else {
346 None
347 }
348 }
349 _ => None,
350 }
351 }
352 _ => None,
353 }
354 }
355}
356
357pub struct JsonTrainingRequest {
358 parameters: JsonIndexParameters,
359 target_request: Box<dyn TrainingRequest>,
360 criteria: TrainingCriteria,
361}
362
363impl JsonTrainingRequest {
364 pub fn new(parameters: JsonIndexParameters, target_request: Box<dyn TrainingRequest>) -> Self {
365 let target_criteria = target_request.criteria();
366 let mut criteria = TrainingCriteria::new(TrainingOrdering::None);
377 criteria.needs_row_ids = target_criteria.needs_row_ids;
378 criteria.needs_row_addrs = target_criteria.needs_row_addrs;
379 Self {
380 parameters,
381 target_request,
382 criteria,
383 }
384 }
385}
386
387impl TrainingRequest for JsonTrainingRequest {
388 fn as_any(&self) -> &dyn std::any::Any {
389 self
390 }
391
392 fn criteria(&self) -> &TrainingCriteria {
393 &self.criteria
394 }
395}
396
397#[derive(Default)]
399pub struct JsonIndexPlugin {
400 registry: Mutex<Option<Arc<IndexPluginRegistry>>>,
401}
402
403impl std::fmt::Debug for JsonIndexPlugin {
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 write!(f, "JsonIndexPlugin")
406 }
407}
408
409impl JsonIndexPlugin {
410 fn registry(&self) -> Result<Arc<IndexPluginRegistry>> {
411 Ok(self.registry.lock().unwrap().as_ref().expect_ok()?.clone())
412 }
413
414 async fn extract_json_with_type_info(
416 data: SendableRecordBatchStream,
417 path: String,
418 ) -> Result<(SendableRecordBatchStream, DataType)> {
419 let input = Arc::new(OneShotExec::new(data));
420 let input_schema = input.schema();
421 let value_column_idx = input_schema
422 .column_with_name(VALUE_COLUMN_NAME)
423 .expect_ok()?
424 .0;
425 let row_id_column_idx = input_schema.column_with_name(ROW_ID).expect_ok()?.0;
426
427 let exprs = vec![
429 (
430 Arc::new(ScalarFunctionExpr::try_new(
431 Arc::new(lance_datafusion::udf::json::json_extract_with_type_udf()),
432 vec![
433 Arc::new(Column::new(VALUE_COLUMN_NAME, value_column_idx)),
434 Arc::new(Literal::new(ScalarValue::Utf8(Some(path)))),
435 ],
436 &input_schema,
437 Arc::new(ConfigOptions::default()),
438 )?) as Arc<dyn PhysicalExpr>,
439 "json_result".to_string(),
440 ),
441 (
442 Arc::new(Column::new(ROW_ID, row_id_column_idx)) as Arc<dyn PhysicalExpr>,
443 ROW_ID.to_string(),
444 ),
445 ];
446
447 let project = ProjectionExec::try_new(exprs, input)?;
448 let ctx = get_session_context(&LanceExecutionOptions::default());
449 let mut stream = project.execute(0, ctx.task_ctx())?;
450
451 let mut all_batches = Vec::new();
453 let mut inferred_type: Option<DataType> = None;
454
455 while let Some(batch_result) = stream.next().await {
456 let batch = batch_result?;
457
458 if inferred_type.is_none()
460 && let Some(json_result_column) = batch.column_by_name("json_result")
461 && let Some(struct_array) =
462 json_result_column.as_any().downcast_ref::<StructArray>()
463 && let Some(type_array) = struct_array.column_by_name("type_tag")
464 && let Some(uint8_array) = type_array.as_any().downcast_ref::<UInt8Array>()
465 {
466 for i in 0..uint8_array.len() {
468 if !uint8_array.is_null(i) {
469 let type_tag = uint8_array.value(i);
470 let jsonb_type = JsonbType::from_u8(type_tag).ok_or_else(|| {
471 Error::invalid_input_source(
472 format!("Invalid type tag: {}", type_tag).into(),
473 )
474 })?;
475
476 inferred_type = Some(match jsonb_type {
478 JsonbType::Null => continue, JsonbType::Boolean => DataType::Boolean,
480 JsonbType::Int64 => DataType::Int64,
481 JsonbType::Float64 => DataType::Float64,
482 JsonbType::String => DataType::Utf8,
483 JsonbType::Array => DataType::LargeBinary,
484 JsonbType::Object => DataType::LargeBinary,
485 });
486 break;
487 }
488 }
489 }
490
491 all_batches.push(batch);
492 }
493
494 let inferred_type = inferred_type.unwrap_or(DataType::Utf8);
496
497 let schema = all_batches
499 .first()
500 .map(|b| b.schema())
501 .ok_or_else(|| Error::invalid_input_source("No batches in stream".into()))?;
502
503 let recreated_stream = Box::pin(RecordBatchStreamAdapter::new(
504 schema,
505 futures::stream::iter(all_batches.into_iter().map(Ok)),
506 )) as SendableRecordBatchStream;
507
508 Ok((recreated_stream, inferred_type))
509 }
510
511 async fn convert_stream_by_type(
513 data: SendableRecordBatchStream,
514 target_type: DataType,
515 ) -> Result<SendableRecordBatchStream> {
516 let input = Arc::new(OneShotExec::new(data));
517 let _input_schema = input.schema();
518 let ctx = get_session_context(&LanceExecutionOptions::default());
519 let mut stream = input.execute(0, ctx.task_ctx())?;
520
521 let mut converted_batches = Vec::new();
522
523 while let Some(batch_result) = stream.next().await {
524 let batch = batch_result?;
525
526 let json_result_column = batch
528 .column_by_name("json_result")
529 .ok_or_else(|| Error::invalid_input_source("Missing json_result column".into()))?;
530
531 let struct_array = json_result_column
532 .as_any()
533 .downcast_ref::<StructArray>()
534 .ok_or_else(|| Error::invalid_input_source("json_result is not a struct".into()))?;
535
536 let value_array = struct_array.column_by_name("value").ok_or_else(|| {
537 Error::invalid_input_source("Missing value column in struct".into())
538 })?;
539
540 let binary_array = value_array
541 .as_any()
542 .downcast_ref::<LargeBinaryArray>()
543 .ok_or_else(|| Error::invalid_input_source("value is not LargeBinary".into()))?;
544
545 let converted_array: Arc<dyn Array> =
547 match target_type {
548 DataType::Boolean => {
549 let mut builder =
550 arrow_array::builder::BooleanBuilder::with_capacity(binary_array.len());
551 for i in 0..binary_array.len() {
552 if binary_array.is_null(i) {
553 builder.append_null();
554 } else if let Some(bytes) = binary_array.value(i).into() {
555 let raw_jsonb = jsonb::RawJsonb::new(bytes);
556 match jsonb::from_raw_jsonb::<bool>(&raw_jsonb) {
558 Ok(bool_val) => builder.append_value(bool_val),
559 Err(e) => {
560 return Err(Error::invalid_input_source(format!(
561 "Failed to deserialize JSONB to bool at index {}: {}",
562 i, e
563 )
564 .into()));
565 }
566 }
567 } else {
568 builder.append_null();
569 }
570 }
571 Arc::new(builder.finish())
572 }
573 DataType::Int64 => {
574 let mut builder =
575 arrow_array::builder::Int64Builder::with_capacity(binary_array.len());
576 for i in 0..binary_array.len() {
577 if binary_array.is_null(i) {
578 builder.append_null();
579 } else if let Some(bytes) = binary_array.value(i).into() {
580 let raw_jsonb = jsonb::RawJsonb::new(bytes);
581 match jsonb::from_raw_jsonb::<i64>(&raw_jsonb) {
583 Ok(int_val) => builder.append_value(int_val),
584 Err(e) => {
585 return Err(Error::invalid_input_source(format!(
586 "Failed to deserialize JSONB to i64 at index {}: {}",
587 i, e
588 )
589 .into()));
590 }
591 }
592 } else {
593 builder.append_null();
594 }
595 }
596 Arc::new(builder.finish())
597 }
598 DataType::Float64 => {
599 let mut builder =
600 arrow_array::builder::Float64Builder::with_capacity(binary_array.len());
601 for i in 0..binary_array.len() {
602 if binary_array.is_null(i) {
603 builder.append_null();
604 } else if let Some(bytes) = binary_array.value(i).into() {
605 let raw_jsonb = jsonb::RawJsonb::new(bytes);
606 match jsonb::from_raw_jsonb::<f64>(&raw_jsonb) {
608 Ok(float_val) => builder.append_value(float_val),
609 Err(e) => {
610 return Err(Error::invalid_input_source(format!(
611 "Failed to deserialize JSONB to f64 at index {}: {}",
612 i, e
613 )
614 .into()));
615 }
616 }
617 } else {
618 builder.append_null();
619 }
620 }
621 Arc::new(builder.finish())
622 }
623 DataType::Utf8 => {
624 let mut builder = arrow_array::builder::StringBuilder::with_capacity(
625 binary_array.len(),
626 1024,
627 );
628 for i in 0..binary_array.len() {
629 if binary_array.is_null(i) {
630 builder.append_null();
631 } else if let Some(bytes) = binary_array.value(i).into() {
632 let raw_jsonb = jsonb::RawJsonb::new(bytes);
633 match jsonb::from_raw_jsonb::<String>(&raw_jsonb) {
635 Ok(str_val) => builder.append_value(&str_val),
636 Err(_) => {
637 builder.append_value(raw_jsonb.to_string());
639 }
640 }
641 } else {
642 builder.append_null();
643 }
644 }
645 Arc::new(builder.finish())
646 }
647 DataType::LargeBinary => {
648 value_array.clone()
650 }
651 _ => {
652 return Err(Error::invalid_input_source(
653 format!("Unsupported target type: {:?}", target_type).into(),
654 ));
655 }
656 };
657
658 let row_id_column = batch
660 .column_by_name(ROW_ID)
661 .ok_or_else(|| Error::invalid_input_source("Missing row_id column".into()))?
662 .clone();
663
664 let new_schema = Arc::new(Schema::new(vec![
666 ArrowField::new(VALUE_COLUMN_NAME, target_type.clone(), true),
667 ArrowField::new(ROW_ID, DataType::UInt64, false),
668 ]));
669
670 let new_batch =
671 RecordBatch::try_new(new_schema.clone(), vec![converted_array, row_id_column])?;
672
673 converted_batches.push(new_batch);
674 }
675
676 let schema = converted_batches
678 .first()
679 .map(|b| b.schema())
680 .ok_or_else(|| Error::invalid_input_source("No batches to convert".into()))?;
681
682 Ok(Box::pin(RecordBatchStreamAdapter::new(
683 schema,
684 futures::stream::iter(converted_batches.into_iter().map(Ok)),
685 )))
686 }
687
688 async fn sort_stream_by_value(
696 data: SendableRecordBatchStream,
697 ) -> Result<SendableRecordBatchStream> {
698 let input = Arc::new(OneShotExec::new(data));
699 let value_idx = input.schema().index_of(VALUE_COLUMN_NAME)?;
700 let sort_expr = PhysicalSortExpr {
701 expr: Arc::new(Column::new(VALUE_COLUMN_NAME, value_idx)),
702 options: SortOptions {
703 descending: false,
704 nulls_first: true,
705 },
706 };
707 let plan = Arc::new(SortExec::new([sort_expr].into(), input));
708 execute_plan(
709 plan,
710 LanceExecutionOptions {
711 use_spilling: true,
712 ..Default::default()
713 },
714 )
715 }
716}
717
718#[async_trait]
719impl BasicTrainer for JsonIndexPlugin {
720 fn new_training_request(
721 &self,
722 params: &str,
723 field: &Field,
724 ) -> Result<Box<dyn TrainingRequest>> {
725 if !matches!(field.data_type(), DataType::Binary | DataType::LargeBinary) {
726 return Err(Error::invalid_input_source(
727 "A JSON index can only be created on a Binary or LargeBinary field.".into(),
728 ));
729 }
730
731 let target_type = DataType::Utf8;
733
734 let params = serde_json::from_str::<JsonIndexParameters>(params)?;
735 let registry = self.registry()?;
736 let target_plugin = registry.get_plugin_by_name(¶ms.target_index_type)?;
737 let target_trainer = target_plugin.basic_trainer().ok_or_else(|| {
738 Error::invalid_input_source(
739 format!("The '{}' index type does not support basic training, please refer to the index's documentation for more details on how to create this index.", params.target_index_type).into(),
740 )
741 })?;
742 let target_request = target_trainer.new_training_request(
743 params.target_index_parameters.as_deref().unwrap_or("{}"),
744 &Field::new("", target_type, true),
745 )?;
746
747 Ok(Box::new(JsonTrainingRequest::new(params, target_request)))
748 }
749
750 async fn train_index(
751 &self,
752 data: SendableRecordBatchStream,
753 index_store: &dyn IndexStore,
754 request: Box<dyn TrainingRequest>,
755 fragment_ids: Option<Vec<u32>>,
756 progress: Arc<dyn crate::progress::IndexBuildProgress>,
757 ) -> Result<CreatedIndex> {
758 let request = (request as Box<dyn std::any::Any>)
759 .downcast::<JsonTrainingRequest>()
760 .unwrap();
761 let path = request.parameters.path.clone();
762
763 let (data_stream, inferred_type) =
765 Self::extract_json_with_type_info(data, path.clone()).await?;
766
767 let converted_stream =
769 Self::convert_stream_by_type(data_stream, inferred_type.clone()).await?;
770
771 let converted_stream =
780 if request.target_request.criteria().ordering == TrainingOrdering::Values {
781 Self::sort_stream_by_value(converted_stream).await?
782 } else {
783 converted_stream
784 };
785
786 let registry = self.registry()?;
788 let target_plugin = registry.get_plugin_by_name(&request.parameters.target_index_type)?;
789
790 let target_trainer = target_plugin.basic_trainer().ok_or_else(|| {
792 Error::invalid_input_source(
793 format!("The '{}' index type does not support basic training, please refer to the index's documentation for more details on how to create this index.", request.parameters.target_index_type).into(),
794 )
795 })?;
796 let target_request = target_trainer.new_training_request(
797 request
798 .parameters
799 .target_index_parameters
800 .as_deref()
801 .unwrap_or("{}"),
802 &Field::new("", inferred_type, true),
803 )?;
804
805 let target_index = target_trainer
806 .train_index(
807 converted_stream,
808 index_store,
809 target_request,
810 fragment_ids,
811 progress,
812 )
813 .await?;
814
815 let index_details = crate::pb::JsonIndexDetails {
816 path,
817 target_details: Some(target_index.index_details),
818 };
819 Ok(CreatedIndex {
820 index_details: prost_types::Any::from_msg(&index_details)?,
821 index_version: JSON_INDEX_VERSION,
822 files: target_index.files,
823 })
824 }
825}
826
827#[async_trait]
828impl ScalarIndexPlugin for JsonIndexPlugin {
829 fn basic_trainer(&self) -> Option<&dyn BasicTrainer> {
830 Some(self)
831 }
832
833 fn name(&self) -> &str {
834 "Json"
835 }
836
837 fn provides_exact_answer(&self) -> bool {
838 true
840 }
841
842 fn attach_registry(&self, registry: Arc<IndexPluginRegistry>) {
843 let mut reg_ref = self.registry.lock().unwrap();
844 *reg_ref = Some(registry);
845 }
846
847 fn version(&self) -> u32 {
848 JSON_INDEX_VERSION
849 }
850
851 fn new_query_parser(
852 &self,
853 index_name: String,
854 index_details: &prost_types::Any,
855 ) -> Option<Box<dyn ScalarQueryParser>> {
856 let registry = self.registry().unwrap();
858 let json_details =
859 crate::pb::JsonIndexDetails::decode(index_details.value.as_slice()).unwrap();
860 let target_details = json_details.target_details.as_ref().expect_ok().unwrap();
861 let target_plugin = registry.get_plugin_by_details(target_details).unwrap();
862 let target_parser = target_plugin.new_query_parser(index_name, index_details)?;
864 Some(Box::new(JsonQueryParser::new(
865 json_details.path.clone(),
866 target_parser,
867 )) as Box<dyn ScalarQueryParser>)
868 }
869
870 async fn load_index(
871 &self,
872 index_store: Arc<dyn IndexStore>,
873 index_details: &prost_types::Any,
874 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
875 cache: &LanceCache,
876 ) -> Result<Arc<dyn ScalarIndex>> {
877 let registry = self.registry().unwrap();
878 let json_details = crate::pb::JsonIndexDetails::decode(index_details.value.as_slice())?;
879 let target_details = json_details.target_details.as_ref().expect_ok()?;
880 let target_plugin = registry.get_plugin_by_details(target_details).unwrap();
881 let target_index = target_plugin
882 .load_index(index_store, target_details, frag_reuse_index, cache)
883 .await?;
884 Ok(Arc::new(JsonIndex::new(target_index, json_details.path)))
885 }
886
887 fn details_as_json(&self, details: &prost_types::Any) -> Result<serde_json::Value> {
888 let registry = self.registry().unwrap();
889 let json_details = crate::pb::JsonIndexDetails::decode(details.value.as_slice())?;
890 let target_details = json_details.target_details.as_ref().expect_ok()?;
891 let target_plugin = registry.get_plugin_by_details(target_details).unwrap();
892 let target_details_json = target_plugin.details_as_json(target_details)?;
893 Ok(serde_json::json!({
894 "path": json_details.path,
895 "target_details": target_details_json,
896 }))
897 }
898}
899
900#[cfg(test)]
901mod tests {
902 use super::*;
903 use crate::scalar::{SargableQuery, TextQuery};
904 use arrow_array::{ArrayRef, RecordBatch};
905 use arrow_schema::{DataType, Field, Schema};
906 use rstest::rstest;
907 use std::ops::Bound;
908 use std::sync::Arc;
909
910 #[tokio::test]
914 async fn test_json_extract_with_type_info() {
915 use arrow_array::{LargeBinaryArray, UInt64Array};
916 use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
917 use futures::stream;
918
919 let json_data = vec![
921 r#"{"name": "Alice", "age": 30, "active": true}"#,
922 r#"{"name": "Bob", "age": 25, "active": false}"#,
923 r#"{"name": "Charlie", "age": 35, "active": true}"#,
924 ];
925
926 let mut jsonb_values = Vec::new();
928 for json_str in &json_data {
929 let owned_jsonb: jsonb::OwnedJsonb = json_str.parse().unwrap();
930 jsonb_values.push(Some(owned_jsonb.to_vec()));
931 }
932
933 let schema = Arc::new(Schema::new(vec![
935 Field::new(VALUE_COLUMN_NAME, DataType::LargeBinary, true),
936 Field::new(ROW_ID, DataType::UInt64, false),
937 ]));
938
939 let jsonb_array = LargeBinaryArray::from(
940 jsonb_values
941 .iter()
942 .map(|v| v.as_deref())
943 .collect::<Vec<_>>(),
944 );
945 let row_ids = UInt64Array::from(vec![1, 2, 3]);
946
947 let batch = RecordBatch::try_new(
948 schema.clone(),
949 vec![
950 Arc::new(jsonb_array) as ArrayRef,
951 Arc::new(row_ids) as ArrayRef,
952 ],
953 )
954 .unwrap();
955
956 let stream = Box::pin(RecordBatchStreamAdapter::new(
957 schema.clone(),
958 stream::iter(vec![Ok(batch)]),
959 )) as SendableRecordBatchStream;
960
961 let (_result_stream, inferred_type) =
963 JsonIndexPlugin::extract_json_with_type_info(stream, "$.age".to_string())
964 .await
965 .unwrap();
966
967 assert_eq!(inferred_type, DataType::Int64);
968
969 let batch2 = RecordBatch::try_new(
971 schema.clone(),
972 vec![
973 Arc::new(LargeBinaryArray::from(vec![
974 json_data[0]
975 .parse::<jsonb::OwnedJsonb>()
976 .ok()
977 .map(|j| j.to_vec())
978 .as_deref(),
979 json_data[1]
980 .parse::<jsonb::OwnedJsonb>()
981 .ok()
982 .map(|j| j.to_vec())
983 .as_deref(),
984 json_data[2]
985 .parse::<jsonb::OwnedJsonb>()
986 .ok()
987 .map(|j| j.to_vec())
988 .as_deref(),
989 ])) as ArrayRef,
990 Arc::new(UInt64Array::from(vec![1, 2, 3])) as ArrayRef,
991 ],
992 )
993 .unwrap();
994
995 let stream2 = Box::pin(RecordBatchStreamAdapter::new(
996 schema.clone(),
997 stream::iter(vec![Ok(batch2)]),
998 )) as SendableRecordBatchStream;
999
1000 let (_, inferred_type) =
1002 JsonIndexPlugin::extract_json_with_type_info(stream2, "$.active".to_string())
1003 .await
1004 .unwrap();
1005
1006 assert_eq!(inferred_type, DataType::Boolean);
1007
1008 let batch3 = RecordBatch::try_new(
1010 schema.clone(),
1011 vec![
1012 Arc::new(LargeBinaryArray::from(vec![
1013 json_data[0]
1014 .parse::<jsonb::OwnedJsonb>()
1015 .ok()
1016 .map(|j| j.to_vec())
1017 .as_deref(),
1018 json_data[1]
1019 .parse::<jsonb::OwnedJsonb>()
1020 .ok()
1021 .map(|j| j.to_vec())
1022 .as_deref(),
1023 json_data[2]
1024 .parse::<jsonb::OwnedJsonb>()
1025 .ok()
1026 .map(|j| j.to_vec())
1027 .as_deref(),
1028 ])) as ArrayRef,
1029 Arc::new(UInt64Array::from(vec![1, 2, 3])) as ArrayRef,
1030 ],
1031 )
1032 .unwrap();
1033
1034 let stream3 = Box::pin(RecordBatchStreamAdapter::new(
1035 schema,
1036 stream::iter(vec![Ok(batch3)]),
1037 )) as SendableRecordBatchStream;
1038
1039 let (_, inferred_type) =
1041 JsonIndexPlugin::extract_json_with_type_info(stream3, "$.name".to_string())
1042 .await
1043 .unwrap();
1044
1045 assert_eq!(inferred_type, DataType::Utf8);
1046 }
1047
1048 async fn train_and_load_json_index(
1053 store: Arc<dyn IndexStore>,
1054 target_index_type: &str,
1055 path: &str,
1056 json_docs: &[&str],
1057 ) -> Arc<dyn ScalarIndex> {
1058 use crate::progress::noop_progress;
1059 use arrow_array::{LargeBinaryArray, UInt64Array};
1060 use futures::stream;
1061
1062 let jsonb: Vec<Vec<u8>> = json_docs
1063 .iter()
1064 .map(|s| s.parse::<jsonb::OwnedJsonb>().unwrap().to_vec())
1065 .collect();
1066
1067 let schema = Arc::new(Schema::new(vec![
1068 Field::new(VALUE_COLUMN_NAME, DataType::LargeBinary, true),
1069 Field::new(ROW_ID, DataType::UInt64, false),
1070 ]));
1071 let batch = RecordBatch::try_new(
1072 schema.clone(),
1073 vec![
1074 Arc::new(LargeBinaryArray::from(
1075 jsonb.iter().map(|v| Some(v.as_slice())).collect::<Vec<_>>(),
1076 )) as ArrayRef,
1077 Arc::new(UInt64Array::from_iter_values(0..json_docs.len() as u64)) as ArrayRef,
1078 ],
1079 )
1080 .unwrap();
1081 let data = Box::pin(RecordBatchStreamAdapter::new(
1082 schema,
1083 stream::iter(vec![Ok(batch)]),
1084 )) as SendableRecordBatchStream;
1085
1086 let registry = IndexPluginRegistry::with_default_plugins();
1087 let plugin = registry.get_plugin_by_name("json").unwrap();
1088 let trainer = plugin.basic_trainer().unwrap();
1089 let params = format!(r#"{{"target_index_type":"{target_index_type}","path":"{path}"}}"#);
1090 let request = trainer
1091 .new_training_request(
1092 ¶ms,
1093 &Field::new(VALUE_COLUMN_NAME, DataType::LargeBinary, true),
1094 )
1095 .unwrap();
1096
1097 assert_eq!(request.criteria().ordering, TrainingOrdering::None);
1102
1103 let created = trainer
1104 .train_index(data, store.as_ref(), request, None, noop_progress())
1105 .await
1106 .unwrap();
1107
1108 plugin
1109 .load_index(store, &created.index_details, None, &LanceCache::no_cache())
1110 .await
1111 .unwrap()
1112 }
1113
1114 fn local_json_index_store() -> (Arc<dyn IndexStore>, lance_core::utils::tempfile::TempObjDir) {
1115 use crate::scalar::lance_format::LanceIndexStore;
1116 use lance_core::utils::tempfile::TempObjDir;
1117 use lance_io::object_store::ObjectStore;
1118
1119 let tmpdir = TempObjDir::default();
1120 let store = Arc::new(LanceIndexStore::new(
1121 Arc::new(ObjectStore::local()),
1122 tmpdir.clone(),
1123 Arc::new(LanceCache::no_cache()),
1124 )) as Arc<dyn IndexStore>;
1125 (store, tmpdir)
1126 }
1127
1128 static FLOAT_INDEX_CASE_GUARD: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1147
1148 #[rstest]
1149 #[case::range_gt_zero(
1150 SargableQuery::Range(Bound::Excluded(ScalarValue::Float64(Some(0.0))), Bound::Unbounded),
1151 vec![0, 1]
1152 )]
1153 #[case::range_gte_page_min(
1154 SargableQuery::Range(Bound::Included(ScalarValue::Float64(Some(10.5))), Bound::Unbounded),
1155 vec![0, 1]
1156 )]
1157 #[case::equals_non_exact_float(
1158 SargableQuery::Equals(ScalarValue::Float64(Some(40.1))),
1159 vec![1]
1160 )]
1161 #[case::equals_exact_float(SargableQuery::Equals(ScalarValue::Float64(Some(10.5))), vec![0])]
1162 #[case::range_covers_all(
1163 SargableQuery::Range(Bound::Unbounded, Bound::Excluded(ScalarValue::Float64(Some(100.0)))),
1164 vec![0, 1, 2]
1165 )]
1166 #[tokio::test]
1167 async fn test_json_float_btree_index_unsorted_input(
1168 #[case] query: SargableQuery,
1169 #[case] expected: Vec<u64>,
1170 ) {
1171 let _guard = FLOAT_INDEX_CASE_GUARD.lock().await;
1172 use crate::metrics::NoOpMetricsCollector;
1173 use lance_select::RowAddrTreeMap;
1174
1175 let (store, _tmpdir) = local_json_index_store();
1179 let index = train_and_load_json_index(
1180 store,
1181 "btree",
1182 "latitude",
1183 &[
1184 r#"{"latitude": 10.5}"#,
1185 r#"{"latitude": 40.1}"#,
1186 r#"{"latitude": -3.2}"#,
1187 ],
1188 )
1189 .await;
1190
1191 let json_query = JsonQuery::new(Arc::new(query.clone()), "latitude".to_string());
1192 let result = index
1193 .search(&json_query, &NoOpMetricsCollector)
1194 .await
1195 .unwrap();
1196 assert_eq!(
1197 result,
1198 SearchResult::exact(RowAddrTreeMap::from_iter(expected.iter().copied())),
1199 "query {query:?}"
1200 );
1201 }
1202
1203 #[tokio::test]
1220 async fn test_json_btree_index_null_at_path() {
1221 use crate::metrics::NoOpMetricsCollector;
1222 use lance_select::RowAddrTreeMap;
1223
1224 let _guard = FLOAT_INDEX_CASE_GUARD.lock().await;
1225 let (store, _tmpdir) = local_json_index_store();
1226 let index = train_and_load_json_index(
1227 store,
1228 "btree",
1229 "v",
1230 &[
1231 r#"{"v": 40.1}"#, r#"{"other": 1}"#, r#"{"v": -3.2}"#, r#"{"v": 10.5}"#, ],
1236 )
1237 .await;
1238
1239 let search = |query: SargableQuery| {
1240 let index = index.clone();
1241 let json_query = JsonQuery::new(Arc::new(query), "v".to_string());
1242 async move {
1243 index
1244 .search(&json_query, &NoOpMetricsCollector)
1245 .await
1246 .unwrap()
1247 }
1248 };
1249
1250 assert_eq!(
1256 search(SargableQuery::IsNull()).await,
1257 SearchResult::exact(RowAddrTreeMap::from_iter([1u64])),
1258 "IsNull"
1259 );
1260 assert_eq!(
1261 search(SargableQuery::Range(
1262 Bound::Excluded(ScalarValue::Float64(Some(0.0))),
1263 Bound::Unbounded,
1264 ))
1265 .await,
1266 SearchResult::exact(RowAddrTreeMap::from_iter([0u64, 3]))
1267 .with_nulls(RowAddrTreeMap::from_iter([1u64])),
1268 "> 0"
1269 );
1270 assert_eq!(
1271 search(SargableQuery::Equals(ScalarValue::Float64(Some(40.1)))).await,
1272 SearchResult::exact(RowAddrTreeMap::from_iter([0u64]))
1273 .with_nulls(RowAddrTreeMap::from_iter([1u64])),
1274 "= 40.1"
1275 );
1276 assert_eq!(
1277 search(SargableQuery::Range(
1278 Bound::Unbounded,
1279 Bound::Excluded(ScalarValue::Float64(Some(100.0))),
1280 ))
1281 .await,
1282 SearchResult::exact(RowAddrTreeMap::from_iter([0u64, 2, 3]))
1283 .with_nulls(RowAddrTreeMap::from_iter([1u64])),
1284 "< 100 (null is neither < 100 nor >= 100)"
1285 );
1286 }
1287
1288 #[tokio::test]
1293 async fn test_json_ngram_index_skips_value_sort() {
1294 use crate::metrics::NoOpMetricsCollector;
1295 use lance_select::RowAddrTreeMap;
1296
1297 let (store, _tmpdir) = local_json_index_store();
1298 let index = train_and_load_json_index(
1299 store,
1300 "ngram",
1301 "tag",
1302 &[
1303 r#"{"tag": "unique-charlie"}"#,
1304 r#"{"tag": "unique-alpha"}"#,
1305 r#"{"tag": "unique-bravo"}"#,
1306 ],
1307 )
1308 .await;
1309
1310 let json_query = JsonQuery::new(
1311 Arc::new(TextQuery::StringContains("unique-bravo".to_string())),
1312 "tag".to_string(),
1313 );
1314 let result = index
1315 .search(&json_query, &NoOpMetricsCollector)
1316 .await
1317 .unwrap();
1318 assert_eq!(
1319 result,
1320 SearchResult::at_most(RowAddrTreeMap::from_iter([2u64])),
1321 );
1322 }
1323}