1use pest::Parser;
45use pest_derive::Parser;
46
47use super::query_field_router::{QueryRouterRule, RoutingMode};
48use super::schema::{DenseVectorQuantization, FieldType, Schema, SchemaBuilder};
49use crate::Result;
50use crate::error::Error;
51
52#[derive(Parser)]
53#[grammar = "dsl/sdl/sdl.pest"]
54pub struct SdlParser;
55
56use super::schema::{BinaryDenseVectorConfig, DenseVectorConfig};
57use crate::structures::{
58 IndexSize, QueryWeighting, SparseFormat, SparseQueryConfig, SparseVectorConfig,
59 WeightQuantization,
60};
61
62#[derive(Debug, Clone)]
64pub struct FieldDef {
65 pub name: String,
66 pub field_type: FieldType,
67 pub indexed: bool,
68 pub stored: bool,
69 pub tokenizer: Option<String>,
71 pub multi: bool,
73 pub positions: Option<super::schema::PositionMode>,
75 pub sparse_vector_config: Option<SparseVectorConfig>,
77 pub dense_vector_config: Option<DenseVectorConfig>,
79 pub binary_dense_vector_config: Option<BinaryDenseVectorConfig>,
81 pub fast: bool,
83 pub primary: bool,
85 pub reorder: bool,
87}
88
89#[derive(Debug, Clone)]
91pub struct IndexDef {
92 pub name: String,
93 pub fields: Vec<FieldDef>,
94 pub default_fields: Vec<String>,
95 pub query_routers: Vec<QueryRouterRule>,
97 pub reorder_on_merge: bool,
100}
101
102impl IndexDef {
103 pub fn to_schema(&self) -> Schema {
105 let mut builder = SchemaBuilder::default();
106
107 for field in &self.fields {
108 let f = match field.field_type {
109 FieldType::Text => {
110 let tokenizer = field.tokenizer.as_deref().unwrap_or("simple");
111 builder.add_text_field_with_tokenizer(
112 &field.name,
113 field.indexed,
114 field.stored,
115 tokenizer,
116 )
117 }
118 FieldType::U64 => builder.add_u64_field(&field.name, field.indexed, field.stored),
119 FieldType::I64 => builder.add_i64_field(&field.name, field.indexed, field.stored),
120 FieldType::F64 => builder.add_f64_field(&field.name, field.indexed, field.stored),
121 FieldType::Bytes => builder.add_bytes_field(&field.name, field.stored),
122 FieldType::Json => builder.add_json_field(&field.name, field.stored),
123 FieldType::SparseVector => {
124 if let Some(config) = &field.sparse_vector_config {
125 builder.add_sparse_vector_field_with_config(
126 &field.name,
127 field.indexed,
128 field.stored,
129 config.clone(),
130 )
131 } else {
132 builder.add_sparse_vector_field(&field.name, field.indexed, field.stored)
133 }
134 }
135 FieldType::DenseVector => {
136 let config = field
138 .dense_vector_config
139 .as_ref()
140 .expect("DenseVector field requires dimension to be specified");
141 builder.add_dense_vector_field_with_config(
142 &field.name,
143 field.indexed,
144 field.stored,
145 config.clone(),
146 )
147 }
148 FieldType::BinaryDenseVector => {
149 let config = field
150 .binary_dense_vector_config
151 .as_ref()
152 .expect("BinaryDenseVector field requires dimension to be specified");
153 builder.add_binary_dense_vector_field_with_config(
154 &field.name,
155 field.indexed,
156 field.stored,
157 config.clone(),
158 )
159 }
160 };
161 if field.multi {
162 builder.set_multi(f, true);
163 }
164 if field.fast {
165 builder.set_fast(f, true);
166 }
167 if field.primary {
168 builder.set_primary_key(f);
169 }
170 if field.reorder {
171 builder.set_reorder(f, true);
172 }
173 let positions = field.positions.or({
175 if field.multi
177 && matches!(
178 field.field_type,
179 FieldType::SparseVector
180 | FieldType::DenseVector
181 | FieldType::BinaryDenseVector
182 )
183 {
184 Some(super::schema::PositionMode::Ordinal)
185 } else {
186 None
187 }
188 });
189 if let Some(mode) = positions {
190 builder.set_positions(f, mode);
191 }
192 }
193
194 if !self.default_fields.is_empty() {
196 builder.set_default_fields(self.default_fields.clone());
197 }
198
199 if !self.query_routers.is_empty() {
201 builder.set_query_routers(self.query_routers.clone());
202 }
203
204 builder.set_index_name(self.name.clone());
205
206 if self.reorder_on_merge {
207 if self.fields.iter().any(|f| f.reorder) {
208 builder.set_reorder_on_merge(true);
209 } else {
210 log::warn!(
213 "index '{}': reorder_on_merge is set but no field has the `reorder` attribute — merges will not reorder anything",
214 self.name,
215 );
216 builder.set_reorder_on_merge(true);
217 }
218 }
219
220 builder.build()
221 }
222
223 pub fn to_query_router(&self) -> Result<Option<super::query_field_router::QueryFieldRouter>> {
228 if self.query_routers.is_empty() {
229 return Ok(None);
230 }
231
232 super::query_field_router::QueryFieldRouter::from_rules(&self.query_routers)
233 .map(Some)
234 .map_err(Error::Schema)
235 }
236}
237
238fn parse_field_type(type_str: &str) -> Result<FieldType> {
240 match type_str {
241 "text" | "string" | "str" => Ok(FieldType::Text),
242 "u64" | "uint" | "unsigned" => Ok(FieldType::U64),
243 "i64" | "int" | "integer" => Ok(FieldType::I64),
244 "f64" | "float" | "double" => Ok(FieldType::F64),
245 "bytes" | "binary" | "blob" => Ok(FieldType::Bytes),
246 "json" => Ok(FieldType::Json),
247 "sparse_vector" => Ok(FieldType::SparseVector),
248 "dense_vector" | "vector" => Ok(FieldType::DenseVector),
249 "binary_dense_vector" | "binary_vector" => Ok(FieldType::BinaryDenseVector),
250 _ => Err(Error::Schema(format!("Unknown field type: {}", type_str))),
251 }
252}
253
254#[derive(Debug, Clone, Default)]
256struct IndexConfig {
257 index_type: Option<super::schema::VectorIndexType>,
258 num_clusters: Option<usize>,
259 nprobe: Option<usize>,
260 ivf_routing: Option<super::schema::IvfRoutingMode>,
261 soar: Option<crate::structures::SoarConfig>,
262 binary_index_type: Option<super::schema::BinaryIndexType>,
263 sparse_format: Option<SparseFormat>,
265 quantization: Option<WeightQuantization>,
266 weight_threshold: Option<f32>,
267 block_size: Option<usize>,
268 bmp_block_size: Option<u32>,
269 bmp_grid_bits: Option<u8>,
270 pruning: Option<f32>,
271 min_terms: Option<usize>,
272 doc_mass: Option<f32>,
273 query_tokenizer: Option<String>,
275 query_weighting: Option<QueryWeighting>,
276 query_weight_threshold: Option<f32>,
277 query_max_dims: Option<usize>,
278 query_pruning: Option<f32>,
279 query_min_query_dims: Option<usize>,
280 query_lsp_gamma: Option<usize>,
281 dims: Option<u32>,
283 max_weight: Option<f32>,
284 positions: Option<super::schema::PositionMode>,
286}
287
288struct ParsedAttributes {
290 indexed: bool,
291 stored: bool,
292 multi: bool,
293 fast: bool,
294 primary: bool,
295 reorder: bool,
296 index_config: Option<IndexConfig>,
297}
298
299fn parse_attributes(pair: pest::iterators::Pair<Rule>) -> ParsedAttributes {
301 let mut attrs = ParsedAttributes {
302 indexed: false,
303 stored: false,
304 multi: false,
305 fast: false,
306 primary: false,
307 reorder: false,
308 index_config: None,
309 };
310
311 for attr in pair.into_inner() {
312 if attr.as_rule() == Rule::attribute {
313 let mut found_config = false;
314 for inner in attr.clone().into_inner() {
315 match inner.as_rule() {
316 Rule::indexed_with_config => {
317 attrs.indexed = true;
318 attrs.index_config = Some(parse_index_config(inner));
319 found_config = true;
320 break;
321 }
322 Rule::stored_with_config => {
323 attrs.stored = true;
324 attrs.multi = true; found_config = true;
326 break;
327 }
328 _ => {}
329 }
330 }
331 if !found_config {
332 match attr.as_str() {
333 "indexed" => attrs.indexed = true,
334 "stored" => attrs.stored = true,
335 "fast" => attrs.fast = true,
336 "primary" => attrs.primary = true,
337 "reorder" => attrs.reorder = true,
338 _ => {}
339 }
340 }
341 }
342 }
343
344 attrs
345}
346
347fn parse_index_config(pair: pest::iterators::Pair<Rule>) -> IndexConfig {
349 let mut config = IndexConfig::default();
350
351 for inner in pair.into_inner() {
356 if inner.as_rule() == Rule::index_config_params {
357 for param in inner.into_inner() {
358 if param.as_rule() == Rule::index_config_param {
359 for p in param.into_inner() {
360 parse_single_index_config_param(&mut config, p);
361 }
362 }
363 }
364 }
365 }
366
367 config
368}
369
370fn parse_single_index_config_param(config: &mut IndexConfig, p: pest::iterators::Pair<Rule>) {
372 use super::schema::VectorIndexType;
373
374 match p.as_rule() {
375 Rule::index_type_spec => match p.as_str() {
376 "flat" => {
377 config.index_type = Some(VectorIndexType::Flat);
378 config.binary_index_type = Some(super::schema::BinaryIndexType::Flat);
379 }
380 "ivf" => config.binary_index_type = Some(super::schema::BinaryIndexType::Ivf),
381 "ivf_pq" => config.index_type = Some(VectorIndexType::IvfPq),
382 "ivf_tq" => config.index_type = Some(VectorIndexType::IvfTq),
383 "tq" => config.index_type = Some(VectorIndexType::Tq),
384 _ => {}
385 },
386 Rule::index_type_kwarg => {
387 if let Some(t) = p.into_inner().next() {
389 match t.as_str() {
390 "flat" => {
391 config.index_type = Some(VectorIndexType::Flat);
392 config.binary_index_type = Some(super::schema::BinaryIndexType::Flat);
393 }
394 "ivf" => config.binary_index_type = Some(super::schema::BinaryIndexType::Ivf),
395 "ivf_pq" => config.index_type = Some(VectorIndexType::IvfPq),
396 "ivf_tq" => config.index_type = Some(VectorIndexType::IvfTq),
397 "tq" => config.index_type = Some(VectorIndexType::Tq),
398 _ => {}
399 }
400 }
401 }
402 Rule::num_clusters_kwarg => {
403 if let Some(n) = p.into_inner().next() {
405 config.num_clusters = Some(n.as_str().parse().unwrap_or_else(|_| {
406 log::warn!(
407 "Invalid num_clusters value '{}', using default 256",
408 n.as_str()
409 );
410 256
411 }));
412 }
413 }
414 Rule::nprobe_kwarg => {
415 if let Some(n) = p.into_inner().next() {
417 config.nprobe = Some(n.as_str().parse().unwrap_or_else(|_| {
418 log::warn!("Invalid nprobe value '{}', using default 64", n.as_str());
419 64
420 }));
421 }
422 }
423 Rule::routing_kwarg => {
424 if let Some(value) = p.into_inner().next() {
425 config.ivf_routing = Some(match value.as_str() {
426 "flat" => super::schema::IvfRoutingMode::Flat,
427 "two_level" => super::schema::IvfRoutingMode::TwoLevel,
428 "hnsw" => super::schema::IvfRoutingMode::Hnsw,
429 _ => super::schema::IvfRoutingMode::Auto,
430 });
431 }
432 }
433 Rule::soar_kwarg => {
434 if let Some(s) = p.into_inner().next() {
436 use crate::structures::SoarConfig;
437 config.soar = match s.as_str() {
438 "selective" => Some(SoarConfig::new()),
439 "full" => Some(SoarConfig::full()),
440 "aggressive" => Some(SoarConfig::aggressive()),
441 _ => None, };
443 }
444 }
445 Rule::quantization_kwarg => {
446 if let Some(q) = p.into_inner().next() {
448 config.quantization = Some(match q.as_str() {
449 "float32" | "f32" => WeightQuantization::Float32,
450 "float16" | "f16" => WeightQuantization::Float16,
451 "uint8" | "u8" => WeightQuantization::UInt8,
452 "uint4" | "u4" => WeightQuantization::UInt4,
453 _ => WeightQuantization::default(),
454 });
455 }
456 }
457 Rule::weight_threshold_kwarg => {
458 if let Some(t) = p.into_inner().next() {
460 config.weight_threshold = Some(t.as_str().parse().unwrap_or_else(|_| {
461 log::warn!(
462 "Invalid weight_threshold value '{}', using default 0.0",
463 t.as_str()
464 );
465 0.0
466 }));
467 }
468 }
469 Rule::block_size_kwarg => {
470 if let Some(n) = p.into_inner().next() {
472 config.block_size = Some(n.as_str().parse().unwrap_or_else(|_| {
473 log::warn!(
474 "Invalid block_size value '{}', using default 128",
475 n.as_str()
476 );
477 128
478 }));
479 }
480 }
481 Rule::bmp_grid_bits_kwarg => {
482 if let Some(n) = p.into_inner().next() {
484 config.bmp_grid_bits = Some(n.as_str().parse().unwrap_or_else(|_| {
485 log::warn!(
486 "Invalid bmp_grid_bits value '{}', using default {}",
487 n.as_str(),
488 SparseVectorConfig::DEFAULT_BMP_GRID_BITS,
489 );
490 SparseVectorConfig::DEFAULT_BMP_GRID_BITS
491 }));
492 }
493 }
494 Rule::bmp_block_size_kwarg => {
495 if let Some(n) = p.into_inner().next() {
497 config.bmp_block_size = Some(n.as_str().parse().unwrap_or_else(|_| {
498 log::warn!(
499 "Invalid bmp_block_size value '{}', using default {}",
500 n.as_str(),
501 SparseVectorConfig::DEFAULT_BMP_BLOCK_SIZE,
502 );
503 SparseVectorConfig::DEFAULT_BMP_BLOCK_SIZE
504 }));
505 }
506 }
507 Rule::pruning_kwarg => {
508 if let Some(f) = p.into_inner().next() {
510 config.pruning = Some(f.as_str().parse().unwrap_or_else(|_| {
511 log::warn!("Invalid pruning value '{}', using default 1.0", f.as_str());
512 1.0
513 }));
514 }
515 }
516 Rule::doc_mass_kwarg => {
517 if let Some(f) = p.into_inner().next() {
519 config.doc_mass = Some(f.as_str().parse().unwrap_or_else(|_| {
520 log::warn!("Invalid doc_mass value '{}', using 1.0 (off)", f.as_str());
521 1.0
522 }));
523 }
524 }
525 Rule::min_terms_kwarg => {
526 if let Some(n) = p.into_inner().next() {
527 config.min_terms = Some(n.as_str().parse().unwrap_or_else(|_| {
528 log::warn!("Invalid min_terms value '{}', using default 4", n.as_str());
529 4
530 }));
531 }
532 }
533 Rule::sparse_format_kwarg => {
534 if let Some(f) = p.into_inner().next() {
536 config.sparse_format = Some(match f.as_str() {
537 "bmp" => SparseFormat::Bmp,
538 "maxscore" => SparseFormat::MaxScore,
539 _ => SparseFormat::default(),
540 });
541 }
542 }
543 Rule::sparse_dims_kwarg => {
544 if let Some(n) = p.into_inner().next() {
545 config.dims = Some(n.as_str().parse().unwrap_or_else(|_| {
546 log::warn!("Invalid dims value '{}', using default 105879", n.as_str());
547 105879
548 }));
549 }
550 }
551 Rule::sparse_max_weight_kwarg => {
552 if let Some(f) = p.into_inner().next() {
553 config.max_weight = Some(f.as_str().parse().unwrap_or_else(|_| {
554 log::warn!(
555 "Invalid max_weight value '{}', using default 5.0",
556 f.as_str()
557 );
558 5.0
559 }));
560 }
561 }
562 Rule::query_config_block => {
563 parse_query_config_block(config, p);
565 }
566 Rule::positions_kwarg => {
567 use super::schema::PositionMode;
569 config.positions = Some(match p.as_str() {
570 "ordinal" => PositionMode::Ordinal,
571 "token_position" => PositionMode::TokenPosition,
572 _ => PositionMode::Full, });
574 }
575 _ => {}
576 }
577}
578
579fn parse_query_config_block(config: &mut IndexConfig, pair: pest::iterators::Pair<Rule>) {
581 for inner in pair.into_inner() {
582 if inner.as_rule() == Rule::query_config_params {
583 for param in inner.into_inner() {
584 if param.as_rule() == Rule::query_config_param {
585 for p in param.into_inner() {
586 match p.as_rule() {
587 Rule::query_tokenizer_kwarg => {
588 if let Some(path) = p.into_inner().next()
590 && let Some(inner_path) = path.into_inner().next()
591 {
592 config.query_tokenizer = Some(inner_path.as_str().to_string());
593 }
594 }
595 Rule::query_weighting_kwarg => {
596 if let Some(w) = p.into_inner().next() {
598 config.query_weighting = Some(match w.as_str() {
599 "one" => QueryWeighting::One,
600 "idf" => QueryWeighting::Idf,
601 "idf_file" => QueryWeighting::IdfFile,
602 _ => QueryWeighting::One,
603 });
604 }
605 }
606 Rule::query_weight_threshold_kwarg => {
607 if let Some(t) = p.into_inner().next() {
608 config.query_weight_threshold =
609 Some(t.as_str().parse().unwrap_or_else(|_| {
610 log::warn!(
611 "Invalid query weight_threshold '{}', using 0.0",
612 t.as_str()
613 );
614 0.0
615 }));
616 }
617 }
618 Rule::query_max_dims_kwarg => {
619 if let Some(t) = p.into_inner().next() {
620 config.query_max_dims =
621 Some(t.as_str().parse().unwrap_or_else(|_| {
622 log::warn!(
623 "Invalid query max_dims '{}', using 0",
624 t.as_str()
625 );
626 0
627 }));
628 }
629 }
630 Rule::query_pruning_kwarg => {
631 if let Some(t) = p.into_inner().next() {
632 config.query_pruning =
633 Some(t.as_str().parse().unwrap_or_else(|_| {
634 log::warn!(
635 "Invalid query pruning '{}', using 1.0",
636 t.as_str()
637 );
638 1.0
639 }));
640 }
641 }
642 Rule::query_min_query_dims_kwarg => {
643 if let Some(t) = p.into_inner().next() {
644 config.query_min_query_dims =
645 Some(t.as_str().parse().unwrap_or_else(|_| {
646 log::warn!(
647 "Invalid query min_query_dims '{}', using 4",
648 t.as_str()
649 );
650 4
651 }));
652 }
653 }
654 Rule::query_lsp_gamma_kwarg => {
655 if let Some(value) = p.into_inner().next() {
656 config.query_lsp_gamma =
657 Some(value.as_str().parse().unwrap_or_else(|_| {
658 log::warn!(
659 "Invalid query lsp_gamma '{}', using 0",
660 value.as_str()
661 );
662 0
663 }));
664 }
665 }
666 _ => {}
667 }
668 }
669 }
670 }
671 }
672 }
673}
674
675fn parse_field_def(pair: pest::iterators::Pair<Rule>) -> Result<FieldDef> {
677 let mut inner = pair.into_inner();
678
679 let name = inner
680 .next()
681 .ok_or_else(|| Error::Schema("Missing field name".to_string()))?
682 .as_str()
683 .to_string();
684
685 let field_type_str = inner
686 .next()
687 .ok_or_else(|| Error::Schema("Missing field type".to_string()))?
688 .as_str();
689
690 let field_type = parse_field_type(field_type_str)?;
691
692 let mut tokenizer = None;
694 let mut sparse_vector_config = None;
695 let mut dense_vector_config = None;
696 let mut binary_dense_vector_config = None;
697 let mut indexed = true;
698 let mut stored = true;
699 let mut multi = false;
700 let mut fast = false;
701 let mut primary = false;
702 let mut reorder = false;
703 let mut index_config: Option<IndexConfig> = None;
704
705 for item in inner {
706 match item.as_rule() {
707 Rule::tokenizer_spec => {
708 if let Some(tok_name) = item.into_inner().next() {
710 tokenizer = Some(tok_name.as_str().to_string());
711 }
712 }
713 Rule::sparse_vector_config => {
714 sparse_vector_config = Some(parse_sparse_vector_config(item));
716 }
717 Rule::dense_vector_config => {
718 dense_vector_config = Some(parse_dense_vector_config(item));
720 }
721 Rule::binary_dense_vector_config => {
722 let dim: usize = item
724 .into_inner()
725 .next()
726 .map(|d| d.as_str().parse().unwrap_or(0))
727 .unwrap_or(0);
728 if dim == 0 || !dim.is_multiple_of(8) {
729 return Err(Error::Schema(format!(
730 "BinaryDenseVector dimension must be a positive multiple of 8, got {dim}"
731 )));
732 }
733 binary_dense_vector_config = Some(BinaryDenseVectorConfig::new(dim));
734 }
735 Rule::attributes => {
736 let attrs = parse_attributes(item);
737 indexed = attrs.indexed;
738 stored = attrs.stored;
739 multi = attrs.multi;
740 fast = attrs.fast;
741 primary = attrs.primary;
742 reorder = attrs.reorder;
743 index_config = attrs.index_config;
744 }
745 _ => {}
746 }
747 }
748
749 if field_type == FieldType::BinaryDenseVector
753 && binary_dense_vector_config.is_none()
754 && let Some(ref dv_config) = dense_vector_config
755 {
756 let dim = dv_config.dim;
757 if dim == 0 || !dim.is_multiple_of(8) {
758 return Err(Error::Schema(format!(
759 "BinaryDenseVector dimension must be a positive multiple of 8, got {dim}"
760 )));
761 }
762 binary_dense_vector_config = Some(BinaryDenseVectorConfig::new(dim));
763 dense_vector_config = None;
764 }
765
766 if primary {
768 fast = true;
769 indexed = true;
770 }
771
772 let mut positions = None;
774 if let Some(idx_cfg) = index_config {
775 positions = idx_cfg.positions;
776 if let Some(ref mut bv_config) = binary_dense_vector_config {
777 apply_index_config_to_binary_dense_vector(bv_config, idx_cfg);
778 } else if let Some(ref mut dv_config) = dense_vector_config {
779 apply_index_config_to_dense_vector(dv_config, idx_cfg);
780 } else if field_type == FieldType::SparseVector {
781 let sv_config = sparse_vector_config.get_or_insert(SparseVectorConfig::default());
783 apply_index_config_to_sparse_vector(sv_config, idx_cfg);
784 }
785 }
786
787 Ok(FieldDef {
788 name,
789 field_type,
790 indexed,
791 stored,
792 tokenizer,
793 multi,
794 positions,
795 sparse_vector_config,
796 dense_vector_config,
797 binary_dense_vector_config,
798 fast,
799 primary,
800 reorder,
801 })
802}
803
804fn apply_index_config_to_binary_dense_vector(
806 config: &mut BinaryDenseVectorConfig,
807 idx_cfg: IndexConfig,
808) {
809 if let Some(index_type) = idx_cfg.binary_index_type {
810 config.index_type = index_type;
811 }
812 if idx_cfg.num_clusters.is_some() {
813 config.num_clusters = idx_cfg.num_clusters;
814 }
815 if let Some(nprobe) = idx_cfg.nprobe {
816 config.nprobe = nprobe;
817 }
818 if let Some(routing) = idx_cfg.ivf_routing {
819 config.ivf_routing = routing;
820 }
821}
822
823fn apply_index_config_to_dense_vector(config: &mut DenseVectorConfig, idx_cfg: IndexConfig) {
825 if let Some(index_type) = idx_cfg.index_type {
827 config.index_type = index_type;
828 }
829
830 if config.index_type == super::schema::VectorIndexType::Tq {
833 for (option, present) in [
834 ("num_clusters", idx_cfg.num_clusters.is_some()),
835 ("nprobe", idx_cfg.nprobe.is_some()),
836 ("routing", idx_cfg.ivf_routing.is_some()),
837 ] {
838 if present {
839 log::warn!(
840 "'{option}' has no effect on the 'tq' index (training-free full \
841 scan); ignoring"
842 );
843 }
844 }
845 config.num_clusters = None;
848 config.nprobe = 0;
849 config.ivf_routing = super::schema::IvfRoutingMode::Flat;
850 return apply_soar_to_dense_vector(config, idx_cfg);
851 }
852
853 if idx_cfg.num_clusters.is_some() {
855 config.num_clusters = idx_cfg.num_clusters;
856 }
857
858 if let Some(nprobe) = idx_cfg.nprobe {
860 config.nprobe = nprobe;
861 }
862 if let Some(routing) = idx_cfg.ivf_routing {
863 config.ivf_routing = routing;
864 }
865
866 apply_soar_to_dense_vector(config, idx_cfg);
867}
868
869fn apply_soar_to_dense_vector(config: &mut DenseVectorConfig, idx_cfg: IndexConfig) {
871 if idx_cfg.soar.is_some() {
872 if config.uses_ivf() {
873 config.soar = idx_cfg.soar;
874 } else {
875 log::warn!(
876 "'soar' requires the IVF-PQ index; \
877 ignoring for index type {:?}",
878 config.index_type
879 );
880 }
881 }
882}
883
884fn parse_sparse_vector_config(pair: pest::iterators::Pair<Rule>) -> SparseVectorConfig {
887 let mut index_size = IndexSize::default();
888
889 for inner in pair.into_inner() {
891 if inner.as_rule() == Rule::index_size_spec {
892 index_size = match inner.as_str() {
893 "u16" => IndexSize::U16,
894 "u32" => IndexSize::U32,
895 _ => IndexSize::default(),
896 };
897 }
898 }
899
900 SparseVectorConfig {
901 index_size,
902 ..SparseVectorConfig::default()
903 }
904}
905
906fn apply_index_config_to_sparse_vector(config: &mut SparseVectorConfig, idx_cfg: IndexConfig) {
908 if let Some(f) = idx_cfg.sparse_format {
909 config.format = f;
910 }
911 if let Some(q) = idx_cfg.quantization {
912 config.weight_quantization = q;
913 }
914 if let Some(t) = idx_cfg.weight_threshold {
915 config.weight_threshold = t;
916 }
917 if let Some(bs) = idx_cfg.block_size {
918 let adjusted = bs.next_power_of_two();
919 if adjusted != bs {
920 log::warn!(
921 "block_size {} adjusted to next power of two: {}",
922 bs,
923 adjusted
924 );
925 }
926 config.block_size = adjusted;
927 }
928 if let Some(bs) = idx_cfg.bmp_block_size {
929 let adjusted = bs.next_power_of_two().clamp(1, 256);
930 if adjusted != bs {
931 log::warn!(
932 "bmp_block_size {} adjusted to power of two in 1..=256: {}",
933 bs,
934 adjusted
935 );
936 }
937 config.bmp_block_size = adjusted;
938 }
939 if let Some(bits) = idx_cfg.bmp_grid_bits {
940 if bits == 2 || bits == 4 {
941 config.bmp_grid_bits = bits;
942 } else {
943 log::warn!(
944 "bmp_grid_bits {} unsupported (must be 2 or 4), using {}",
945 bits,
946 SparseVectorConfig::DEFAULT_BMP_GRID_BITS,
947 );
948 config.bmp_grid_bits = SparseVectorConfig::DEFAULT_BMP_GRID_BITS;
949 }
950 }
951 if let Some(p) = idx_cfg.pruning {
952 let clamped = p.clamp(0.0, 1.0);
953 if (clamped - p).abs() > f32::EPSILON {
954 log::warn!(
955 "pruning {} clamped to valid range [0.0, 1.0]: {}",
956 p,
957 clamped
958 );
959 }
960 config.pruning = Some(clamped);
961 }
962 if let Some(mt) = idx_cfg.min_terms {
963 config.min_terms = mt;
964 }
965 if let Some(dm) = idx_cfg.doc_mass {
966 let clamped = dm.clamp(0.0, 1.0);
967 if (clamped - dm).abs() > f32::EPSILON {
968 log::warn!(
969 "doc_mass {} clamped to valid range [0.0, 1.0]: {}",
970 dm,
971 clamped
972 );
973 }
974 config.doc_mass = Some(clamped);
975 }
976 if let Some(d) = idx_cfg.dims {
977 config.dims = Some(d);
978 }
979 if let Some(mw) = idx_cfg.max_weight {
980 config.max_weight = Some(mw);
981 }
982 if idx_cfg.query_tokenizer.is_some()
984 || idx_cfg.query_weighting.is_some()
985 || idx_cfg.query_weight_threshold.is_some()
986 || idx_cfg.query_max_dims.is_some()
987 || idx_cfg.query_pruning.is_some()
988 || idx_cfg.query_min_query_dims.is_some()
989 || idx_cfg.query_lsp_gamma.is_some()
990 {
991 let query_config = config
992 .query_config
993 .get_or_insert(SparseQueryConfig::default());
994 if let Some(tokenizer) = idx_cfg.query_tokenizer {
995 query_config.tokenizer = Some(tokenizer);
996 }
997 if let Some(weighting) = idx_cfg.query_weighting {
998 query_config.weighting = weighting;
999 }
1000 if let Some(t) = idx_cfg.query_weight_threshold {
1001 query_config.weight_threshold = t;
1002 }
1003 if let Some(d) = idx_cfg.query_max_dims {
1004 query_config.max_query_dims = Some(d);
1005 }
1006 if let Some(p) = idx_cfg.query_pruning {
1007 query_config.pruning = Some(p);
1008 }
1009 if let Some(m) = idx_cfg.query_min_query_dims {
1010 query_config.min_query_dims = m;
1011 }
1012 if let Some(gamma) = idx_cfg.query_lsp_gamma {
1013 query_config.lsp_gamma = Some(gamma);
1014 }
1015 }
1016}
1017
1018fn parse_dense_vector_config(pair: pest::iterators::Pair<Rule>) -> DenseVectorConfig {
1021 let mut dim: usize = 0;
1022 let mut quantization = DenseVectorQuantization::F32;
1023
1024 for params in pair.into_inner() {
1026 if params.as_rule() == Rule::dense_vector_params {
1027 for inner in params.into_inner() {
1028 match inner.as_rule() {
1029 Rule::dense_vector_keyword_params => {
1030 for kwarg in inner.into_inner() {
1031 match kwarg.as_rule() {
1032 Rule::dims_kwarg => {
1033 if let Some(d) = kwarg.into_inner().next() {
1034 dim = d.as_str().parse().unwrap_or(0);
1035 }
1036 }
1037 Rule::quant_type_spec => {
1038 quantization = parse_quant_type(kwarg.as_str());
1039 }
1040 _ => {}
1041 }
1042 }
1043 }
1044 Rule::dense_vector_positional_params => {
1045 for item in inner.into_inner() {
1046 match item.as_rule() {
1047 Rule::dimension_spec => {
1048 dim = item.as_str().parse().unwrap_or(0);
1049 }
1050 Rule::quant_type_spec => {
1051 quantization = parse_quant_type(item.as_str());
1052 }
1053 _ => {}
1054 }
1055 }
1056 }
1057 _ => {}
1058 }
1059 }
1060 }
1061 }
1062
1063 DenseVectorConfig::new(dim).with_quantization(quantization)
1064}
1065
1066fn parse_quant_type(s: &str) -> DenseVectorQuantization {
1067 match s.trim() {
1068 "f16" => DenseVectorQuantization::F16,
1069 "uint8" | "u8" => DenseVectorQuantization::UInt8,
1070 _ => DenseVectorQuantization::F32,
1071 }
1072}
1073
1074fn parse_default_fields_def(pair: pest::iterators::Pair<Rule>) -> Vec<String> {
1076 pair.into_inner().map(|p| p.as_str().to_string()).collect()
1077}
1078
1079fn parse_query_router_def(pair: pest::iterators::Pair<Rule>) -> Result<QueryRouterRule> {
1081 let mut pattern = String::new();
1082 let mut substitution = String::new();
1083 let mut target_field = String::new();
1084 let mut mode = RoutingMode::Additional;
1085
1086 for prop in pair.into_inner() {
1087 if prop.as_rule() != Rule::query_router_prop {
1088 continue;
1089 }
1090
1091 for inner in prop.into_inner() {
1092 match inner.as_rule() {
1093 Rule::query_router_pattern => {
1094 if let Some(regex_str) = inner.into_inner().next() {
1095 pattern = parse_string_value(regex_str);
1096 }
1097 }
1098 Rule::query_router_substitution => {
1099 if let Some(quoted) = inner.into_inner().next() {
1100 substitution = parse_string_value(quoted);
1101 }
1102 }
1103 Rule::query_router_target => {
1104 if let Some(ident) = inner.into_inner().next() {
1105 target_field = ident.as_str().to_string();
1106 }
1107 }
1108 Rule::query_router_mode => {
1109 if let Some(mode_val) = inner.into_inner().next() {
1110 mode = match mode_val.as_str() {
1111 "exclusive" => RoutingMode::Exclusive,
1112 "additional" => RoutingMode::Additional,
1113 _ => RoutingMode::Additional,
1114 };
1115 }
1116 }
1117 _ => {}
1118 }
1119 }
1120 }
1121
1122 if pattern.is_empty() {
1123 return Err(Error::Schema("query_router missing 'pattern'".to_string()));
1124 }
1125 if substitution.is_empty() {
1126 return Err(Error::Schema(
1127 "query_router missing 'substitution'".to_string(),
1128 ));
1129 }
1130 if target_field.is_empty() {
1131 return Err(Error::Schema(
1132 "query_router missing 'target_field'".to_string(),
1133 ));
1134 }
1135
1136 Ok(QueryRouterRule {
1137 pattern,
1138 substitution,
1139 target_field,
1140 mode,
1141 })
1142}
1143
1144fn parse_string_value(pair: pest::iterators::Pair<Rule>) -> String {
1146 let s = pair.as_str();
1147 match pair.as_rule() {
1148 Rule::regex_string => {
1149 if let Some(inner) = pair.into_inner().next() {
1151 parse_string_value(inner)
1152 } else {
1153 s.to_string()
1154 }
1155 }
1156 Rule::raw_string => {
1157 s[2..s.len() - 1].to_string()
1159 }
1160 Rule::quoted_string => {
1161 let inner = &s[1..s.len() - 1];
1163 inner
1165 .replace("\\n", "\n")
1166 .replace("\\t", "\t")
1167 .replace("\\\"", "\"")
1168 .replace("\\\\", "\\")
1169 }
1170 _ => s.to_string(),
1171 }
1172}
1173
1174fn parse_index_def(pair: pest::iterators::Pair<Rule>) -> Result<IndexDef> {
1176 let mut inner = pair.into_inner();
1177
1178 let name = inner
1179 .next()
1180 .ok_or_else(|| Error::Schema("Missing index name".to_string()))?
1181 .as_str()
1182 .to_string();
1183
1184 let mut fields = Vec::new();
1185 let mut default_fields = Vec::new();
1186 let mut query_routers = Vec::new();
1187 let mut reorder_on_merge = false;
1188
1189 for item in inner {
1190 match item.as_rule() {
1191 Rule::field_def => {
1192 fields.push(parse_field_def(item)?);
1193 }
1194 Rule::default_fields_def => {
1195 default_fields = parse_default_fields_def(item);
1196 }
1197 Rule::query_router_def => {
1198 query_routers.push(parse_query_router_def(item)?);
1199 }
1200 Rule::reorder_on_merge_def => {
1201 let value = item
1202 .into_inner()
1203 .next()
1204 .map(|b| b.as_str() == "true")
1205 .unwrap_or(false);
1206 reorder_on_merge = value;
1207 }
1208 _ => {}
1209 }
1210 }
1211
1212 let primary_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.primary).collect();
1214 if primary_fields.len() > 1 {
1215 return Err(Error::Schema(format!(
1216 "Index '{}' has {} primary key fields, but at most one is allowed",
1217 name,
1218 primary_fields.len()
1219 )));
1220 }
1221 if let Some(pk) = primary_fields.first() {
1222 if pk.field_type != FieldType::Text {
1223 return Err(Error::Schema(format!(
1224 "Primary key field '{}' must be of type text, got {:?}",
1225 pk.name, pk.field_type
1226 )));
1227 }
1228 if pk.multi {
1229 return Err(Error::Schema(format!(
1230 "Primary key field '{}' cannot be multi-valued",
1231 pk.name
1232 )));
1233 }
1234 }
1235
1236 Ok(IndexDef {
1237 name,
1238 fields,
1239 default_fields,
1240 query_routers,
1241 reorder_on_merge,
1242 })
1243}
1244
1245pub fn parse_sdl(input: &str) -> Result<Vec<IndexDef>> {
1247 let pairs = SdlParser::parse(Rule::file, input)
1248 .map_err(|e| Error::Schema(format!("Parse error: {}", e)))?;
1249
1250 let mut indexes = Vec::new();
1251
1252 for pair in pairs {
1253 if pair.as_rule() == Rule::file {
1254 for inner in pair.into_inner() {
1255 if inner.as_rule() == Rule::index_def {
1256 indexes.push(parse_index_def(inner)?);
1257 }
1258 }
1259 }
1260 }
1261
1262 Ok(indexes)
1263}
1264
1265pub fn parse_single_index(input: &str) -> Result<IndexDef> {
1267 let indexes = parse_sdl(input)?;
1268
1269 if indexes.is_empty() {
1270 return Err(Error::Schema("No index definition found".to_string()));
1271 }
1272
1273 if indexes.len() > 1 {
1274 return Err(Error::Schema(
1275 "Multiple index definitions found, expected one".to_string(),
1276 ));
1277 }
1278
1279 Ok(indexes.into_iter().next().unwrap())
1280}
1281
1282#[cfg(test)]
1283mod tests {
1284 use super::*;
1285
1286 #[test]
1287 fn test_parse_simple_schema() {
1288 let sdl = r#"
1289 index articles {
1290 field title: text [indexed, stored]
1291 field body: text [indexed]
1292 }
1293 "#;
1294
1295 let indexes = parse_sdl(sdl).unwrap();
1296 assert_eq!(indexes.len(), 1);
1297
1298 let index = &indexes[0];
1299 assert_eq!(index.name, "articles");
1300 assert_eq!(index.fields.len(), 2);
1301
1302 assert_eq!(index.fields[0].name, "title");
1303 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1304 assert!(index.fields[0].indexed);
1305 assert!(index.fields[0].stored);
1306
1307 assert_eq!(index.fields[1].name, "body");
1308 assert!(matches!(index.fields[1].field_type, FieldType::Text));
1309 assert!(index.fields[1].indexed);
1310 assert!(!index.fields[1].stored);
1311 }
1312
1313 #[test]
1314 fn test_parse_all_field_types() {
1315 let sdl = r#"
1316 index test {
1317 field text_field: text [indexed, stored]
1318 field u64_field: u64 [indexed, stored]
1319 field i64_field: i64 [indexed, stored]
1320 field f64_field: f64 [indexed, stored]
1321 field bytes_field: bytes [stored]
1322 }
1323 "#;
1324
1325 let indexes = parse_sdl(sdl).unwrap();
1326 let index = &indexes[0];
1327
1328 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1329 assert!(matches!(index.fields[1].field_type, FieldType::U64));
1330 assert!(matches!(index.fields[2].field_type, FieldType::I64));
1331 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1332 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1333 }
1334
1335 #[test]
1336 fn test_parse_with_comments() {
1337 let sdl = r#"
1338 # This is a comment
1339 index articles {
1340 # Title field
1341 field title: text [indexed, stored]
1342 field body: text [indexed] # inline comment not supported yet
1343 }
1344 "#;
1345
1346 let indexes = parse_sdl(sdl).unwrap();
1347 assert_eq!(indexes[0].fields.len(), 2);
1348 }
1349
1350 #[test]
1351 fn test_parse_type_aliases() {
1352 let sdl = r#"
1353 index test {
1354 field a: string [indexed]
1355 field b: int [indexed]
1356 field c: uint [indexed]
1357 field d: float [indexed]
1358 field e: binary [stored]
1359 }
1360 "#;
1361
1362 let indexes = parse_sdl(sdl).unwrap();
1363 let index = &indexes[0];
1364
1365 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1366 assert!(matches!(index.fields[1].field_type, FieldType::I64));
1367 assert!(matches!(index.fields[2].field_type, FieldType::U64));
1368 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1369 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1370 }
1371
1372 #[test]
1373 fn test_to_schema() {
1374 let sdl = r#"
1375 index articles {
1376 field title: text [indexed, stored]
1377 field views: u64 [indexed, stored]
1378 }
1379 "#;
1380
1381 let indexes = parse_sdl(sdl).unwrap();
1382 let schema = indexes[0].to_schema();
1383
1384 assert!(schema.get_field("title").is_some());
1385 assert!(schema.get_field("views").is_some());
1386 assert!(schema.get_field("nonexistent").is_none());
1387 }
1388
1389 #[test]
1390 fn test_default_attributes() {
1391 let sdl = r#"
1392 index test {
1393 field title: text
1394 }
1395 "#;
1396
1397 let indexes = parse_sdl(sdl).unwrap();
1398 let field = &indexes[0].fields[0];
1399
1400 assert!(field.indexed);
1402 assert!(field.stored);
1403 }
1404
1405 #[test]
1406 fn test_multiple_indexes() {
1407 let sdl = r#"
1408 index articles {
1409 field title: text [indexed, stored]
1410 }
1411
1412 index users {
1413 field name: text [indexed, stored]
1414 field email: text [indexed, stored]
1415 }
1416 "#;
1417
1418 let indexes = parse_sdl(sdl).unwrap();
1419 assert_eq!(indexes.len(), 2);
1420 assert_eq!(indexes[0].name, "articles");
1421 assert_eq!(indexes[1].name, "users");
1422 }
1423
1424 #[test]
1425 fn test_tokenizer_spec() {
1426 let sdl = r#"
1427 index articles {
1428 field title: text<en_stem> [indexed, stored]
1429 field body: text<simple> [indexed]
1430 field author: text [indexed, stored]
1431 }
1432 "#;
1433
1434 let indexes = parse_sdl(sdl).unwrap();
1435 let index = &indexes[0];
1436
1437 assert_eq!(index.fields[0].name, "title");
1438 assert_eq!(index.fields[0].tokenizer, Some("en_stem".to_string()));
1439
1440 assert_eq!(index.fields[1].name, "body");
1441 assert_eq!(index.fields[1].tokenizer, Some("simple".to_string()));
1442
1443 assert_eq!(index.fields[2].name, "author");
1444 assert_eq!(index.fields[2].tokenizer, None); }
1446
1447 #[test]
1448 fn test_tokenizer_in_schema() {
1449 let sdl = r#"
1450 index articles {
1451 field title: text<german> [indexed, stored]
1452 field body: text<en_stem> [indexed]
1453 }
1454 "#;
1455
1456 let indexes = parse_sdl(sdl).unwrap();
1457 let schema = indexes[0].to_schema();
1458
1459 let title_field = schema.get_field("title").unwrap();
1460 let title_entry = schema.get_field_entry(title_field).unwrap();
1461 assert_eq!(title_entry.tokenizer, Some("german".to_string()));
1462
1463 let body_field = schema.get_field("body").unwrap();
1464 let body_entry = schema.get_field_entry(body_field).unwrap();
1465 assert_eq!(body_entry.tokenizer, Some("en_stem".to_string()));
1466 }
1467
1468 #[test]
1469 fn test_query_router_basic() {
1470 let sdl = r#"
1471 index documents {
1472 field title: text [indexed, stored]
1473 field uri: text [indexed, stored]
1474
1475 query_router {
1476 pattern: "10\\.\\d{4,}/[^\\s]+"
1477 substitution: "doi://{0}"
1478 target_field: uris
1479 mode: exclusive
1480 }
1481 }
1482 "#;
1483
1484 let indexes = parse_sdl(sdl).unwrap();
1485 let index = &indexes[0];
1486
1487 assert_eq!(index.query_routers.len(), 1);
1488 let router = &index.query_routers[0];
1489 assert_eq!(router.pattern, r"10\.\d{4,}/[^\s]+");
1490 assert_eq!(router.substitution, "doi://{0}");
1491 assert_eq!(router.target_field, "uris");
1492 assert_eq!(router.mode, RoutingMode::Exclusive);
1493 }
1494
1495 #[test]
1496 fn test_query_router_raw_string() {
1497 let sdl = r#"
1498 index documents {
1499 field uris: text [indexed, stored]
1500
1501 query_router {
1502 pattern: r"^pmid:(\d+)$"
1503 substitution: "pubmed://{1}"
1504 target_field: uris
1505 mode: additional
1506 }
1507 }
1508 "#;
1509
1510 let indexes = parse_sdl(sdl).unwrap();
1511 let router = &indexes[0].query_routers[0];
1512
1513 assert_eq!(router.pattern, r"^pmid:(\d+)$");
1514 assert_eq!(router.substitution, "pubmed://{1}");
1515 assert_eq!(router.mode, RoutingMode::Additional);
1516 }
1517
1518 #[test]
1519 fn test_multiple_query_routers() {
1520 let sdl = r#"
1521 index documents {
1522 field uris: text [indexed, stored]
1523
1524 query_router {
1525 pattern: r"^doi:(10\.\d{4,}/[^\s]+)$"
1526 substitution: "doi://{1}"
1527 target_field: uris
1528 mode: exclusive
1529 }
1530
1531 query_router {
1532 pattern: r"^pmid:(\d+)$"
1533 substitution: "pubmed://{1}"
1534 target_field: uris
1535 mode: exclusive
1536 }
1537
1538 query_router {
1539 pattern: r"^arxiv:(\d+\.\d+)$"
1540 substitution: "arxiv://{1}"
1541 target_field: uris
1542 mode: additional
1543 }
1544 }
1545 "#;
1546
1547 let indexes = parse_sdl(sdl).unwrap();
1548 assert_eq!(indexes[0].query_routers.len(), 3);
1549 }
1550
1551 #[test]
1552 fn test_query_router_default_mode() {
1553 let sdl = r#"
1554 index documents {
1555 field uris: text [indexed, stored]
1556
1557 query_router {
1558 pattern: r"test"
1559 substitution: "{0}"
1560 target_field: uris
1561 }
1562 }
1563 "#;
1564
1565 let indexes = parse_sdl(sdl).unwrap();
1566 assert_eq!(indexes[0].query_routers[0].mode, RoutingMode::Additional);
1568 }
1569
1570 #[test]
1571 fn test_multi_attribute() {
1572 let sdl = r#"
1573 index documents {
1574 field uris: text [indexed, stored<multi>]
1575 field title: text [indexed, stored]
1576 }
1577 "#;
1578
1579 let indexes = parse_sdl(sdl).unwrap();
1580 assert_eq!(indexes.len(), 1);
1581
1582 let fields = &indexes[0].fields;
1583 assert_eq!(fields.len(), 2);
1584
1585 assert_eq!(fields[0].name, "uris");
1587 assert!(fields[0].multi, "uris field should have multi=true");
1588
1589 assert_eq!(fields[1].name, "title");
1591 assert!(!fields[1].multi, "title field should have multi=false");
1592
1593 let schema = indexes[0].to_schema();
1595 let uris_field = schema.get_field("uris").unwrap();
1596 let title_field = schema.get_field("title").unwrap();
1597
1598 assert!(schema.get_field_entry(uris_field).unwrap().multi);
1599 assert!(!schema.get_field_entry(title_field).unwrap().multi);
1600 }
1601
1602 #[test]
1603 fn test_sparse_vector_field() {
1604 let sdl = r#"
1605 index documents {
1606 field embedding: sparse_vector [indexed, stored]
1607 }
1608 "#;
1609
1610 let indexes = parse_sdl(sdl).unwrap();
1611 assert_eq!(indexes.len(), 1);
1612 assert_eq!(indexes[0].fields.len(), 1);
1613 assert_eq!(indexes[0].fields[0].name, "embedding");
1614 assert_eq!(indexes[0].fields[0].field_type, FieldType::SparseVector);
1615 assert!(indexes[0].fields[0].sparse_vector_config.is_none());
1616 }
1617
1618 #[test]
1619 fn test_sparse_vector_with_config() {
1620 let sdl = r#"
1621 index documents {
1622 field embedding: sparse_vector<u16> [indexed<quantization: uint8>, stored]
1623 field dense: sparse_vector<u32> [indexed<quantization: float32>]
1624 }
1625 "#;
1626
1627 let indexes = parse_sdl(sdl).unwrap();
1628 assert_eq!(indexes[0].fields.len(), 2);
1629
1630 let f1 = &indexes[0].fields[0];
1632 assert_eq!(f1.name, "embedding");
1633 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1634 assert_eq!(config1.index_size, IndexSize::U16);
1635 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1636
1637 let f2 = &indexes[0].fields[1];
1639 assert_eq!(f2.name, "dense");
1640 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1641 assert_eq!(config2.index_size, IndexSize::U32);
1642 assert_eq!(config2.weight_quantization, WeightQuantization::Float32);
1643 }
1644
1645 #[test]
1646 fn test_sparse_vector_bmp_block_size() {
1647 let sdl = r#"
1648 index documents {
1649 field emb: sparse_vector<u32> [indexed<format: bmp, dims: 105879, bmp_block_size: 256>]
1650 field emb2: sparse_vector<u32> [indexed<format: bmp, dims: 30522>]
1651 }
1652 "#;
1653
1654 let indexes = parse_sdl(sdl).unwrap();
1655 let config1 = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1656 assert_eq!(config1.format, SparseFormat::Bmp);
1657 assert_eq!(config1.bmp_block_size, 256);
1658
1659 let config2 = indexes[0].fields[1].sparse_vector_config.as_ref().unwrap();
1661 assert_eq!(
1662 config2.bmp_block_size,
1663 SparseVectorConfig::DEFAULT_BMP_BLOCK_SIZE
1664 );
1665 }
1666
1667 #[test]
1670 fn test_sparse_vector_bmp_grid_bits() {
1671 let sdl = r#"
1672 index documents {
1673 field emb: sparse_vector<u32> [indexed<format: bmp, dims: 105879, bmp_block_size: 256, bmp_grid_bits: 2>]
1674 field emb2: sparse_vector<u32> [indexed<format: bmp, dims: 30522>]
1675 field emb3: sparse_vector<u32> [indexed<format: bmp, dims: 30522, bmp_grid_bits: 3>]
1676 }
1677 "#;
1678
1679 let indexes = parse_sdl(sdl).unwrap();
1680 let config1 = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1681 assert_eq!(config1.bmp_grid_bits, 2);
1682 let config2 = indexes[0].fields[1].sparse_vector_config.as_ref().unwrap();
1684 assert_eq!(
1685 config2.bmp_grid_bits,
1686 SparseVectorConfig::DEFAULT_BMP_GRID_BITS
1687 );
1688 let config3 = indexes[0].fields[2].sparse_vector_config.as_ref().unwrap();
1690 assert_eq!(
1691 config3.bmp_grid_bits,
1692 SparseVectorConfig::DEFAULT_BMP_GRID_BITS
1693 );
1694 }
1695
1696 #[test]
1697 fn test_sparse_vector_with_weight_threshold() {
1698 let sdl = r#"
1699 index documents {
1700 field embedding: sparse_vector<u16> [indexed<quantization: uint8, weight_threshold: 0.1>, stored]
1701 field embedding2: sparse_vector<u32> [indexed<quantization: float16, weight_threshold: 0.05>]
1702 }
1703 "#;
1704
1705 let indexes = parse_sdl(sdl).unwrap();
1706 assert_eq!(indexes[0].fields.len(), 2);
1707
1708 let f1 = &indexes[0].fields[0];
1710 assert_eq!(f1.name, "embedding");
1711 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1712 assert_eq!(config1.index_size, IndexSize::U16);
1713 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1714 assert!((config1.weight_threshold - 0.1).abs() < 0.001);
1715
1716 let f2 = &indexes[0].fields[1];
1718 assert_eq!(f2.name, "embedding2");
1719 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1720 assert_eq!(config2.index_size, IndexSize::U32);
1721 assert_eq!(config2.weight_quantization, WeightQuantization::Float16);
1722 assert!((config2.weight_threshold - 0.05).abs() < 0.001);
1723 }
1724
1725 #[test]
1726 fn test_sparse_vector_with_pruning() {
1727 let sdl = r#"
1728 index documents {
1729 field embedding: sparse_vector [indexed<quantization: uint8, pruning: 0.1>, stored]
1730 }
1731 "#;
1732
1733 let indexes = parse_sdl(sdl).unwrap();
1734 let f = &indexes[0].fields[0];
1735 assert_eq!(f.name, "embedding");
1736 let config = f.sparse_vector_config.as_ref().unwrap();
1737 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
1738 assert_eq!(config.pruning, Some(0.1));
1739 }
1740
1741 #[test]
1742 fn test_sparse_vector_with_doc_mass() {
1743 let sdl = r#"
1744 index documents {
1745 field embedding: sparse_vector [indexed<quantization: uint8, doc_mass: 0.9>, stored]
1746 }
1747 "#;
1748
1749 let indexes = parse_sdl(sdl).unwrap();
1750 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1751 assert_eq!(config.doc_mass, Some(0.9));
1752
1753 let sdl = r#"
1755 index documents {
1756 field embedding: sparse_vector [indexed<quantization: uint8>]
1757 }
1758 "#;
1759 let indexes = parse_sdl(sdl).unwrap();
1760 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1761 assert_eq!(config.doc_mass, None);
1762 }
1763
1764 #[test]
1765 fn test_dense_vector_field() {
1766 let sdl = r#"
1767 index documents {
1768 field embedding: dense_vector<768> [indexed, stored]
1769 }
1770 "#;
1771
1772 let indexes = parse_sdl(sdl).unwrap();
1773 assert_eq!(indexes.len(), 1);
1774 assert_eq!(indexes[0].fields.len(), 1);
1775
1776 let f = &indexes[0].fields[0];
1777 assert_eq!(f.name, "embedding");
1778 assert_eq!(f.field_type, FieldType::DenseVector);
1779
1780 let config = f.dense_vector_config.as_ref().unwrap();
1781 assert_eq!(config.dim, 768);
1782 }
1783
1784 #[test]
1785 fn test_dense_vector_alias() {
1786 let sdl = r#"
1787 index documents {
1788 field embedding: vector<1536> [indexed]
1789 }
1790 "#;
1791
1792 let indexes = parse_sdl(sdl).unwrap();
1793 assert_eq!(indexes[0].fields[0].field_type, FieldType::DenseVector);
1794 assert_eq!(
1795 indexes[0].fields[0]
1796 .dense_vector_config
1797 .as_ref()
1798 .unwrap()
1799 .dim,
1800 1536
1801 );
1802 }
1803
1804 #[test]
1805 fn test_dense_vector_with_num_clusters() {
1806 let sdl = r#"
1807 index documents {
1808 field embedding: dense_vector<768> [indexed<ivf_tq, num_clusters: 256>, stored]
1809 }
1810 "#;
1811
1812 let indexes = parse_sdl(sdl).unwrap();
1813 assert_eq!(indexes.len(), 1);
1814
1815 let f = &indexes[0].fields[0];
1816 assert_eq!(f.name, "embedding");
1817 assert_eq!(f.field_type, FieldType::DenseVector);
1818
1819 let config = f.dense_vector_config.as_ref().unwrap();
1820 assert_eq!(config.dim, 768);
1821 assert_eq!(config.num_clusters, Some(256));
1822 assert_eq!(config.nprobe, 64); }
1824
1825 #[test]
1826 fn test_dense_vector_with_soar() {
1827 let sdl = r#"
1828 index documents {
1829 field embedding: dense_vector<768> [indexed<ivf_tq, num_clusters: 256, soar: selective>, stored]
1830 }
1831 "#;
1832
1833 let indexes = parse_sdl(sdl).unwrap();
1834 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1835
1836 let soar = config.soar.as_ref().expect("soar should be enabled");
1837 assert_eq!(soar.num_secondary, 1);
1838 assert!(soar.selective);
1839
1840 let sdl = r#"
1842 index documents {
1843 field embedding: dense_vector<768> [indexed<ivf_tq, soar: aggressive>]
1844 }
1845 "#;
1846 let indexes = parse_sdl(sdl).unwrap();
1847 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1848 let soar = config.soar.as_ref().expect("soar should be enabled");
1849 assert_eq!(soar.num_secondary, 2);
1850 assert!(!soar.selective);
1851
1852 let sdl = r#"
1854 index documents {
1855 field embedding: dense_vector<768> [indexed<ivf_tq, soar: off>]
1856 }
1857 "#;
1858 let indexes = parse_sdl(sdl).unwrap();
1859 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1860 assert!(config.soar.is_none());
1861 }
1862
1863 #[test]
1864 fn test_ivf_routing_modes_apply_to_float_and_binary_fields() {
1865 let indexes = parse_sdl(
1866 r#"
1867 index vectors {
1868 field embedding: dense_vector<768> [indexed<ivf_tq, routing: hnsw>]
1869 field hash: binary_dense_vector<512> [indexed<ivf, routing: two_level>]
1870 }
1871 "#,
1872 )
1873 .unwrap();
1874 let schema = indexes[0].to_schema();
1875 let embedding = schema.get_field("embedding").unwrap();
1876 let hash = schema.get_field("hash").unwrap();
1877 assert_eq!(
1878 schema
1879 .get_field_entry(embedding)
1880 .unwrap()
1881 .dense_vector_config
1882 .as_ref()
1883 .unwrap()
1884 .ivf_routing,
1885 super::super::schema::IvfRoutingMode::Hnsw
1886 );
1887 assert_eq!(
1888 schema
1889 .get_field_entry(hash)
1890 .unwrap()
1891 .binary_dense_vector_config
1892 .as_ref()
1893 .unwrap()
1894 .ivf_routing,
1895 super::super::schema::IvfRoutingMode::TwoLevel
1896 );
1897 }
1898
1899 #[test]
1900 fn test_binary_dense_vector_with_ivf() {
1901 let sdl = r#"
1902 index documents {
1903 field hash: binary_dense_vector<512> [indexed<ivf, num_clusters: 128, nprobe: 16>, stored]
1904 }
1905 "#;
1906
1907 let indexes = parse_sdl(sdl).unwrap();
1908 let config = indexes[0].fields[0]
1909 .binary_dense_vector_config
1910 .as_ref()
1911 .unwrap();
1912 assert_eq!(config.dim, 512);
1913 assert_eq!(
1914 config.index_type,
1915 super::super::schema::BinaryIndexType::Ivf
1916 );
1917 assert_eq!(config.num_clusters, Some(128));
1918 assert_eq!(config.nprobe, 16);
1919
1920 let sdl = r#"
1923 index documents {
1924 field hash: binary_dense_vector<512> [indexed]
1925 }
1926 "#;
1927 let indexes = parse_sdl(sdl).unwrap();
1928 let config = indexes[0].fields[0]
1929 .binary_dense_vector_config
1930 .as_ref()
1931 .unwrap();
1932 assert_eq!(
1933 config.index_type,
1934 super::super::schema::BinaryIndexType::Ivf
1935 );
1936 }
1937
1938 #[test]
1939 fn test_dense_vector_with_num_clusters_and_nprobe() {
1940 let sdl = r#"
1941 index documents {
1942 field embedding: dense_vector<1536> [indexed<ivf_tq, num_clusters: 512, nprobe: 64>]
1943 }
1944 "#;
1945
1946 let indexes = parse_sdl(sdl).unwrap();
1947 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1948
1949 assert_eq!(config.dim, 1536);
1950 assert_eq!(config.num_clusters, Some(512));
1951 assert_eq!(config.nprobe, 64);
1952 }
1953
1954 #[test]
1955 fn test_dense_vector_keyword_syntax() {
1956 let sdl = r#"
1957 index documents {
1958 field embedding: dense_vector<dims: 1536> [indexed, stored]
1959 }
1960 "#;
1961
1962 let indexes = parse_sdl(sdl).unwrap();
1963 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1964
1965 assert_eq!(config.dim, 1536);
1966 assert!(config.num_clusters.is_none());
1967 }
1968
1969 #[test]
1970 fn test_dense_vector_keyword_syntax_full() {
1971 let sdl = r#"
1972 index documents {
1973 field embedding: dense_vector<dims: 1536> [indexed<ivf_tq, num_clusters: 256, nprobe: 64>]
1974 }
1975 "#;
1976
1977 let indexes = parse_sdl(sdl).unwrap();
1978 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1979
1980 assert_eq!(config.dim, 1536);
1981 assert_eq!(config.num_clusters, Some(256));
1982 assert_eq!(config.nprobe, 64);
1983 }
1984
1985 #[test]
1986 fn test_dense_vector_keyword_syntax_partial() {
1987 let sdl = r#"
1988 index documents {
1989 field embedding: dense_vector<dims: 768> [indexed<ivf_tq, num_clusters: 128>]
1990 }
1991 "#;
1992
1993 let indexes = parse_sdl(sdl).unwrap();
1994 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1995
1996 assert_eq!(config.dim, 768);
1997 assert_eq!(config.num_clusters, Some(128));
1998 assert_eq!(config.nprobe, 64); }
2000
2001 #[test]
2002 fn test_dense_vector_ivf_tq_index_with_probe() {
2003 use crate::dsl::schema::VectorIndexType;
2004
2005 let sdl = r#"
2006 index documents {
2007 field embedding: dense_vector<dims: 768> [indexed<ivf_tq, num_clusters: 256, nprobe: 64>]
2008 }
2009 "#;
2010
2011 let indexes = parse_sdl(sdl).unwrap();
2012 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2013
2014 assert_eq!(config.dim, 768);
2015 assert_eq!(config.index_type, VectorIndexType::IvfTq);
2016 assert_eq!(config.num_clusters, Some(256));
2017 assert_eq!(config.nprobe, 64);
2018 }
2019
2020 #[test]
2021 fn test_dense_vector_ivf_tq_index_without_explicit_probe() {
2022 use crate::dsl::schema::VectorIndexType;
2023
2024 let sdl = r#"
2025 index documents {
2026 field embedding: dense_vector<dims: 1536> [indexed<ivf_tq, num_clusters: 512>]
2027 }
2028 "#;
2029
2030 let indexes = parse_sdl(sdl).unwrap();
2031 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2032
2033 assert_eq!(config.dim, 1536);
2034 assert_eq!(config.index_type, VectorIndexType::IvfTq);
2035 assert_eq!(config.num_clusters, Some(512));
2036 }
2037
2038 #[test]
2039 fn test_dense_vector_ivf_tq_no_clusters() {
2040 use crate::dsl::schema::VectorIndexType;
2041
2042 let sdl = r#"
2043 index documents {
2044 field embedding: dense_vector<dims: 768> [indexed<ivf_tq>]
2045 }
2046 "#;
2047
2048 let indexes = parse_sdl(sdl).unwrap();
2049 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2050
2051 assert_eq!(config.dim, 768);
2052 assert_eq!(config.index_type, VectorIndexType::IvfTq);
2053 assert!(config.num_clusters.is_none());
2054 }
2055
2056 #[test]
2057 fn removed_ivf_pq_still_parses_to_the_reserved_variant() {
2058 use crate::dsl::schema::VectorIndexType;
2059
2060 let sdl = r#"
2063 index test {
2064 field embedding: dense_vector<8> [indexed<ivf_pq>]
2065 }
2066 "#;
2067 let indexes = parse_sdl(sdl).unwrap();
2068 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2069 assert_eq!(config.index_type, VectorIndexType::IvfPq);
2070
2071 let mut builder = crate::dsl::SchemaBuilder::default();
2072 builder.add_dense_vector_field_with_config("embedding", true, true, config.clone());
2073 let schema = builder.build();
2074 let error = crate::dsl::schema::reject_removed_vector_index_types(&schema)
2075 .expect_err("removed index types must be rejected at the index gate");
2076 assert!(error.contains("ivf_tq"), "{error}");
2077 assert!(error.contains("removed"), "{error}");
2078 }
2079
2080 #[test]
2081 fn test_dense_vector_flat_index() {
2082 use crate::dsl::schema::VectorIndexType;
2083
2084 let sdl = r#"
2085 index documents {
2086 field embedding: dense_vector<dims: 768> [indexed<flat>]
2087 }
2088 "#;
2089
2090 let indexes = parse_sdl(sdl).unwrap();
2091 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2092
2093 assert_eq!(config.dim, 768);
2094 assert_eq!(config.index_type, VectorIndexType::Flat);
2095 }
2096
2097 #[test]
2098 fn test_dense_vector_default_index_type() {
2099 use crate::dsl::schema::VectorIndexType;
2100
2101 let sdl = r#"
2103 index documents {
2104 field embedding: dense_vector<dims: 768> [indexed]
2105 }
2106 "#;
2107
2108 let indexes = parse_sdl(sdl).unwrap();
2109 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2110
2111 assert_eq!(config.dim, 768);
2112 assert_eq!(config.index_type, VectorIndexType::IvfTq);
2113 }
2114
2115 #[test]
2116 fn test_dense_vector_f16_quantization() {
2117 use crate::dsl::schema::{DenseVectorQuantization, VectorIndexType};
2118
2119 let sdl = r#"
2120 index documents {
2121 field embedding: dense_vector<768, f16> [indexed]
2122 }
2123 "#;
2124
2125 let indexes = parse_sdl(sdl).unwrap();
2126 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2127
2128 assert_eq!(config.dim, 768);
2129 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2130 assert_eq!(config.index_type, VectorIndexType::IvfTq);
2131 }
2132
2133 #[test]
2134 fn test_dense_vector_uint8_quantization() {
2135 use crate::dsl::schema::DenseVectorQuantization;
2136
2137 let sdl = r#"
2138 index documents {
2139 field embedding: dense_vector<1024, uint8> [indexed<ivf_tq>]
2140 }
2141 "#;
2142
2143 let indexes = parse_sdl(sdl).unwrap();
2144 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2145
2146 assert_eq!(config.dim, 1024);
2147 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2148 }
2149
2150 #[test]
2151 fn test_dense_vector_u8_alias() {
2152 use crate::dsl::schema::DenseVectorQuantization;
2153
2154 let sdl = r#"
2155 index documents {
2156 field embedding: dense_vector<512, u8> [indexed]
2157 }
2158 "#;
2159
2160 let indexes = parse_sdl(sdl).unwrap();
2161 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2162
2163 assert_eq!(config.dim, 512);
2164 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2165 }
2166
2167 #[test]
2168 fn test_dense_vector_default_f32_quantization() {
2169 use crate::dsl::schema::DenseVectorQuantization;
2170
2171 let sdl = r#"
2173 index documents {
2174 field embedding: dense_vector<768> [indexed]
2175 }
2176 "#;
2177
2178 let indexes = parse_sdl(sdl).unwrap();
2179 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2180
2181 assert_eq!(config.dim, 768);
2182 assert_eq!(config.quantization, DenseVectorQuantization::F32);
2183 }
2184
2185 #[test]
2186 fn test_dense_vector_keyword_with_quantization() {
2187 use crate::dsl::schema::DenseVectorQuantization;
2188
2189 let sdl = r#"
2190 index documents {
2191 field embedding: dense_vector<dims: 768, f16> [indexed]
2192 }
2193 "#;
2194
2195 let indexes = parse_sdl(sdl).unwrap();
2196 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2197
2198 assert_eq!(config.dim, 768);
2199 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2200 }
2201
2202 #[test]
2203 fn test_json_field_type() {
2204 let sdl = r#"
2205 index documents {
2206 field title: text [indexed, stored]
2207 field metadata: json [stored]
2208 field extra: json
2209 }
2210 "#;
2211
2212 let indexes = parse_sdl(sdl).unwrap();
2213 let index = &indexes[0];
2214
2215 assert_eq!(index.fields.len(), 3);
2216
2217 assert_eq!(index.fields[1].name, "metadata");
2219 assert!(matches!(index.fields[1].field_type, FieldType::Json));
2220 assert!(index.fields[1].stored);
2221 assert_eq!(index.fields[2].name, "extra");
2225 assert!(matches!(index.fields[2].field_type, FieldType::Json));
2226
2227 let schema = index.to_schema();
2229 let metadata_field = schema.get_field("metadata").unwrap();
2230 let entry = schema.get_field_entry(metadata_field).unwrap();
2231 assert_eq!(entry.field_type, FieldType::Json);
2232 assert!(!entry.indexed); assert!(entry.stored);
2234 }
2235
2236 #[test]
2237 fn test_sparse_vector_query_config() {
2238 use crate::structures::QueryWeighting;
2239
2240 let sdl = r#"
2241 index documents {
2242 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "Alibaba-NLP/gte-Qwen2-1.5B-instruct", weighting: idf>>]
2243 }
2244 "#;
2245
2246 let indexes = parse_sdl(sdl).unwrap();
2247 let index = &indexes[0];
2248
2249 assert_eq!(index.fields.len(), 1);
2250 assert_eq!(index.fields[0].name, "embedding");
2251 assert!(matches!(
2252 index.fields[0].field_type,
2253 FieldType::SparseVector
2254 ));
2255
2256 let config = index.fields[0].sparse_vector_config.as_ref().unwrap();
2257 assert_eq!(config.index_size, IndexSize::U16);
2258 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2259
2260 let query_config = config.query_config.as_ref().unwrap();
2262 assert_eq!(
2263 query_config.tokenizer.as_deref(),
2264 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2265 );
2266 assert_eq!(query_config.weighting, QueryWeighting::Idf);
2267
2268 let schema = index.to_schema();
2270 let embedding_field = schema.get_field("embedding").unwrap();
2271 let entry = schema.get_field_entry(embedding_field).unwrap();
2272 let sv_config = entry.sparse_vector_config.as_ref().unwrap();
2273 let qc = sv_config.query_config.as_ref().unwrap();
2274 assert_eq!(
2275 qc.tokenizer.as_deref(),
2276 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2277 );
2278 assert_eq!(qc.weighting, QueryWeighting::Idf);
2279 }
2280
2281 #[test]
2282 fn test_sparse_vector_query_config_weighting_one() {
2283 use crate::structures::QueryWeighting;
2284
2285 let sdl = r#"
2286 index documents {
2287 field embedding: sparse_vector [indexed<query<weighting: one>>]
2288 }
2289 "#;
2290
2291 let indexes = parse_sdl(sdl).unwrap();
2292 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2293
2294 let query_config = config.query_config.as_ref().unwrap();
2295 assert!(query_config.tokenizer.is_none());
2296 assert_eq!(query_config.weighting, QueryWeighting::One);
2297 }
2298
2299 #[test]
2300 fn test_sparse_vector_query_config_weighting_idf_file() {
2301 use crate::structures::QueryWeighting;
2302
2303 let sdl = r#"
2304 index documents {
2305 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "opensearch-neural-sparse-encoding-v1", weighting: idf_file>>]
2306 }
2307 "#;
2308
2309 let indexes = parse_sdl(sdl).unwrap();
2310 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2311
2312 let query_config = config.query_config.as_ref().unwrap();
2313 assert_eq!(
2314 query_config.tokenizer.as_deref(),
2315 Some("opensearch-neural-sparse-encoding-v1")
2316 );
2317 assert_eq!(query_config.weighting, QueryWeighting::IdfFile);
2318
2319 let schema = indexes[0].to_schema();
2321 let field = schema.get_field("embedding").unwrap();
2322 let entry = schema.get_field_entry(field).unwrap();
2323 let sc = entry.sparse_vector_config.as_ref().unwrap();
2324 let qc = sc.query_config.as_ref().unwrap();
2325 assert_eq!(qc.weighting, QueryWeighting::IdfFile);
2326 }
2327
2328 #[test]
2329 fn test_sparse_vector_query_config_pruning_params() {
2330 let sdl = r#"
2331 index documents {
2332 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<weighting: idf, weight_threshold: 0.03, max_dims: 25, pruning: 0.2, lsp_gamma: 500>>]
2333 }
2334 "#;
2335
2336 let indexes = parse_sdl(sdl).unwrap();
2337 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2338
2339 let qc = config.query_config.as_ref().unwrap();
2340 assert_eq!(qc.weighting, QueryWeighting::Idf);
2341 assert!((qc.weight_threshold - 0.03).abs() < 0.001);
2342 assert_eq!(qc.max_query_dims, Some(25));
2343 assert!((qc.pruning.unwrap() - 0.2).abs() < 0.001);
2344 assert_eq!(qc.lsp_gamma, Some(500));
2345
2346 let schema = indexes[0].to_schema();
2348 let field = schema.get_field("embedding").unwrap();
2349 let entry = schema.get_field_entry(field).unwrap();
2350 let sc = entry.sparse_vector_config.as_ref().unwrap();
2351 let rqc = sc.query_config.as_ref().unwrap();
2352 assert!((rqc.weight_threshold - 0.03).abs() < 0.001);
2353 assert_eq!(rqc.max_query_dims, Some(25));
2354 assert!((rqc.pruning.unwrap() - 0.2).abs() < 0.001);
2355 assert_eq!(rqc.lsp_gamma, Some(500));
2356 }
2357
2358 #[test]
2359 fn test_sparse_vector_format_maxscore() {
2360 let sdl = r#"
2361 index documents {
2362 field embedding: sparse_vector<u16> [indexed<format: maxscore, quantization: uint8>]
2363 }
2364 "#;
2365
2366 let indexes = parse_sdl(sdl).unwrap();
2367 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2368 assert_eq!(config.format, SparseFormat::MaxScore);
2369 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2370
2371 let schema = indexes[0].to_schema();
2373 let field = schema.get_field("embedding").unwrap();
2374 let entry = schema.get_field_entry(field).unwrap();
2375 let sc = entry.sparse_vector_config.as_ref().unwrap();
2376 assert_eq!(sc.format, SparseFormat::MaxScore);
2377 }
2378
2379 #[test]
2380 fn test_sparse_vector_format_bmp() {
2381 let sdl = r#"
2382 index documents {
2383 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>]
2384 }
2385 "#;
2386
2387 let indexes = parse_sdl(sdl).unwrap();
2388 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2389 assert_eq!(config.format, SparseFormat::Bmp);
2390 }
2391
2392 #[test]
2393 fn test_fast_attribute() {
2394 let sdl = r#"
2395 index products {
2396 field name: text [indexed, stored]
2397 field price: f64 [indexed, fast]
2398 field category: text [indexed, stored, fast]
2399 field count: u64 [fast]
2400 field score: i64 [indexed, stored, fast]
2401 }
2402 "#;
2403
2404 let indexes = parse_sdl(sdl).unwrap();
2405 assert_eq!(indexes.len(), 1);
2406 let index = &indexes[0];
2407 assert_eq!(index.fields.len(), 5);
2408
2409 assert!(!index.fields[0].fast);
2411 assert!(index.fields[1].fast);
2413 assert!(matches!(index.fields[1].field_type, FieldType::F64));
2414 assert!(index.fields[2].fast);
2416 assert!(matches!(index.fields[2].field_type, FieldType::Text));
2417 assert!(index.fields[3].fast);
2419 assert!(matches!(index.fields[3].field_type, FieldType::U64));
2420 assert!(index.fields[4].fast);
2422 assert!(matches!(index.fields[4].field_type, FieldType::I64));
2423
2424 let schema = index.to_schema();
2426 let price_field = schema.get_field("price").unwrap();
2427 assert!(schema.get_field_entry(price_field).unwrap().fast);
2428
2429 let category_field = schema.get_field("category").unwrap();
2430 assert!(schema.get_field_entry(category_field).unwrap().fast);
2431
2432 let name_field = schema.get_field("name").unwrap();
2433 assert!(!schema.get_field_entry(name_field).unwrap().fast);
2434 }
2435
2436 #[test]
2437 fn test_primary_attribute() {
2438 let sdl = r#"
2439 index documents {
2440 field id: text [primary, stored]
2441 field title: text [indexed, stored]
2442 }
2443 "#;
2444
2445 let indexes = parse_sdl(sdl).unwrap();
2446 assert_eq!(indexes.len(), 1);
2447 let index = &indexes[0];
2448 assert_eq!(index.fields.len(), 2);
2449
2450 let id_field = &index.fields[0];
2452 assert!(id_field.primary, "id should be primary");
2453 assert!(id_field.fast, "primary implies fast");
2454 assert!(id_field.indexed, "primary implies indexed");
2455
2456 assert!(!index.fields[1].primary);
2458
2459 let schema = index.to_schema();
2461 let id = schema.get_field("id").unwrap();
2462 let id_entry = schema.get_field_entry(id).unwrap();
2463 assert!(id_entry.primary_key);
2464 assert!(id_entry.fast);
2465 assert!(id_entry.indexed);
2466
2467 let title = schema.get_field("title").unwrap();
2468 assert!(!schema.get_field_entry(title).unwrap().primary_key);
2469
2470 assert_eq!(schema.primary_field(), Some(id));
2472 }
2473
2474 #[test]
2475 fn test_primary_with_other_attributes() {
2476 let sdl = r#"
2477 index documents {
2478 field id: text<simple> [primary, indexed, stored]
2479 field body: text [indexed]
2480 }
2481 "#;
2482
2483 let indexes = parse_sdl(sdl).unwrap();
2484 let id_field = &indexes[0].fields[0];
2485 assert!(id_field.primary);
2486 assert!(id_field.indexed);
2487 assert!(id_field.stored);
2488 assert!(id_field.fast);
2489 assert_eq!(id_field.tokenizer, Some("simple".to_string()));
2490 }
2491
2492 #[test]
2493 fn test_primary_only_one_allowed() {
2494 let sdl = r#"
2495 index documents {
2496 field id: text [primary]
2497 field alt_id: text [primary]
2498 }
2499 "#;
2500
2501 let result = parse_sdl(sdl);
2502 assert!(result.is_err());
2503 let err = result.unwrap_err().to_string();
2504 assert!(
2505 err.contains("primary key"),
2506 "Error should mention primary key: {}",
2507 err
2508 );
2509 }
2510
2511 #[test]
2512 fn test_primary_must_be_text() {
2513 let sdl = r#"
2514 index documents {
2515 field id: u64 [primary]
2516 }
2517 "#;
2518
2519 let result = parse_sdl(sdl);
2520 assert!(result.is_err());
2521 let err = result.unwrap_err().to_string();
2522 assert!(
2523 err.contains("text"),
2524 "Error should mention text type: {}",
2525 err
2526 );
2527 }
2528
2529 #[test]
2530 fn test_primary_cannot_be_multi() {
2531 let sdl = r#"
2532 index documents {
2533 field id: text [primary, stored<multi>]
2534 }
2535 "#;
2536
2537 let result = parse_sdl(sdl);
2538 assert!(result.is_err());
2539 let err = result.unwrap_err().to_string();
2540 assert!(err.contains("multi"), "Error should mention multi: {}", err);
2541 }
2542
2543 #[test]
2544 fn test_no_primary_field() {
2545 let sdl = r#"
2547 index documents {
2548 field title: text [indexed, stored]
2549 }
2550 "#;
2551
2552 let indexes = parse_sdl(sdl).unwrap();
2553 let schema = indexes[0].to_schema();
2554 assert!(schema.primary_field().is_none());
2555 }
2556
2557 #[test]
2558 fn test_reorder_attribute() {
2559 let sdl = r#"
2560 index documents {
2561 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>, reorder]
2562 field embedding2: sparse_vector [indexed<format: bmp>]
2563 }
2564 "#;
2565
2566 let indexes = parse_sdl(sdl).unwrap();
2567 assert_eq!(indexes[0].fields.len(), 2);
2568
2569 assert!(indexes[0].fields[0].reorder);
2571 assert!(!indexes[0].fields[1].reorder);
2573
2574 let schema = indexes[0].to_schema();
2576 let f1 = schema.get_field("embedding").unwrap();
2577 assert!(schema.get_field_entry(f1).unwrap().reorder);
2578
2579 let f2 = schema.get_field("embedding2").unwrap();
2580 assert!(!schema.get_field_entry(f2).unwrap().reorder);
2581
2582 assert!(!schema.reorder_on_merge());
2584 }
2585
2586 #[test]
2587 fn test_reorder_on_merge_index_option() {
2588 let sdl = r#"
2589 index documents {
2590 reorder_on_merge: true
2591 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2592 }
2593 "#;
2594
2595 let indexes = parse_sdl(sdl).unwrap();
2596 assert!(indexes[0].reorder_on_merge);
2597 let schema = indexes[0].to_schema();
2598 assert!(schema.reorder_on_merge());
2599
2600 let sdl_off = r#"
2602 index documents {
2603 reorder_on_merge: false
2604 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2605 }
2606 "#;
2607 let indexes = parse_sdl(sdl_off).unwrap();
2608 assert!(!indexes[0].reorder_on_merge);
2609 assert!(!indexes[0].to_schema().reorder_on_merge());
2610
2611 let schema_on = parse_sdl(sdl).unwrap()[0].to_schema();
2613 let json = serde_json::to_string(&schema_on).unwrap();
2614 let back: crate::dsl::Schema = serde_json::from_str(&json).unwrap();
2615 assert!(back.reorder_on_merge());
2616 }
2617}