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 _ => {}
383 },
384 Rule::index_type_kwarg => {
385 if let Some(t) = p.into_inner().next() {
387 match t.as_str() {
388 "flat" => {
389 config.index_type = Some(VectorIndexType::Flat);
390 config.binary_index_type = Some(super::schema::BinaryIndexType::Flat);
391 }
392 "ivf" => config.binary_index_type = Some(super::schema::BinaryIndexType::Ivf),
393 "ivf_pq" => config.index_type = Some(VectorIndexType::IvfPq),
394 _ => {}
395 }
396 }
397 }
398 Rule::num_clusters_kwarg => {
399 if let Some(n) = p.into_inner().next() {
401 config.num_clusters = Some(n.as_str().parse().unwrap_or_else(|_| {
402 log::warn!(
403 "Invalid num_clusters value '{}', using default 256",
404 n.as_str()
405 );
406 256
407 }));
408 }
409 }
410 Rule::nprobe_kwarg => {
411 if let Some(n) = p.into_inner().next() {
413 config.nprobe = Some(n.as_str().parse().unwrap_or_else(|_| {
414 log::warn!("Invalid nprobe value '{}', using default 64", n.as_str());
415 64
416 }));
417 }
418 }
419 Rule::routing_kwarg => {
420 if let Some(value) = p.into_inner().next() {
421 config.ivf_routing = Some(match value.as_str() {
422 "flat" => super::schema::IvfRoutingMode::Flat,
423 "two_level" => super::schema::IvfRoutingMode::TwoLevel,
424 "hnsw" => super::schema::IvfRoutingMode::Hnsw,
425 _ => super::schema::IvfRoutingMode::Auto,
426 });
427 }
428 }
429 Rule::soar_kwarg => {
430 if let Some(s) = p.into_inner().next() {
432 use crate::structures::SoarConfig;
433 config.soar = match s.as_str() {
434 "selective" => Some(SoarConfig::new()),
435 "full" => Some(SoarConfig::full()),
436 "aggressive" => Some(SoarConfig::aggressive()),
437 _ => None, };
439 }
440 }
441 Rule::quantization_kwarg => {
442 if let Some(q) = p.into_inner().next() {
444 config.quantization = Some(match q.as_str() {
445 "float32" | "f32" => WeightQuantization::Float32,
446 "float16" | "f16" => WeightQuantization::Float16,
447 "uint8" | "u8" => WeightQuantization::UInt8,
448 "uint4" | "u4" => WeightQuantization::UInt4,
449 _ => WeightQuantization::default(),
450 });
451 }
452 }
453 Rule::weight_threshold_kwarg => {
454 if let Some(t) = p.into_inner().next() {
456 config.weight_threshold = Some(t.as_str().parse().unwrap_or_else(|_| {
457 log::warn!(
458 "Invalid weight_threshold value '{}', using default 0.0",
459 t.as_str()
460 );
461 0.0
462 }));
463 }
464 }
465 Rule::block_size_kwarg => {
466 if let Some(n) = p.into_inner().next() {
468 config.block_size = Some(n.as_str().parse().unwrap_or_else(|_| {
469 log::warn!(
470 "Invalid block_size value '{}', using default 128",
471 n.as_str()
472 );
473 128
474 }));
475 }
476 }
477 Rule::bmp_grid_bits_kwarg => {
478 if let Some(n) = p.into_inner().next() {
480 config.bmp_grid_bits = Some(n.as_str().parse().unwrap_or_else(|_| {
481 log::warn!(
482 "Invalid bmp_grid_bits value '{}', using default {}",
483 n.as_str(),
484 SparseVectorConfig::DEFAULT_BMP_GRID_BITS,
485 );
486 SparseVectorConfig::DEFAULT_BMP_GRID_BITS
487 }));
488 }
489 }
490 Rule::bmp_block_size_kwarg => {
491 if let Some(n) = p.into_inner().next() {
493 config.bmp_block_size = Some(n.as_str().parse().unwrap_or_else(|_| {
494 log::warn!(
495 "Invalid bmp_block_size value '{}', using default {}",
496 n.as_str(),
497 SparseVectorConfig::DEFAULT_BMP_BLOCK_SIZE,
498 );
499 SparseVectorConfig::DEFAULT_BMP_BLOCK_SIZE
500 }));
501 }
502 }
503 Rule::pruning_kwarg => {
504 if let Some(f) = p.into_inner().next() {
506 config.pruning = Some(f.as_str().parse().unwrap_or_else(|_| {
507 log::warn!("Invalid pruning value '{}', using default 1.0", f.as_str());
508 1.0
509 }));
510 }
511 }
512 Rule::doc_mass_kwarg => {
513 if let Some(f) = p.into_inner().next() {
515 config.doc_mass = Some(f.as_str().parse().unwrap_or_else(|_| {
516 log::warn!("Invalid doc_mass value '{}', using 1.0 (off)", f.as_str());
517 1.0
518 }));
519 }
520 }
521 Rule::min_terms_kwarg => {
522 if let Some(n) = p.into_inner().next() {
523 config.min_terms = Some(n.as_str().parse().unwrap_or_else(|_| {
524 log::warn!("Invalid min_terms value '{}', using default 4", n.as_str());
525 4
526 }));
527 }
528 }
529 Rule::sparse_format_kwarg => {
530 if let Some(f) = p.into_inner().next() {
532 config.sparse_format = Some(match f.as_str() {
533 "bmp" => SparseFormat::Bmp,
534 "maxscore" => SparseFormat::MaxScore,
535 _ => SparseFormat::default(),
536 });
537 }
538 }
539 Rule::sparse_dims_kwarg => {
540 if let Some(n) = p.into_inner().next() {
541 config.dims = Some(n.as_str().parse().unwrap_or_else(|_| {
542 log::warn!("Invalid dims value '{}', using default 105879", n.as_str());
543 105879
544 }));
545 }
546 }
547 Rule::sparse_max_weight_kwarg => {
548 if let Some(f) = p.into_inner().next() {
549 config.max_weight = Some(f.as_str().parse().unwrap_or_else(|_| {
550 log::warn!(
551 "Invalid max_weight value '{}', using default 5.0",
552 f.as_str()
553 );
554 5.0
555 }));
556 }
557 }
558 Rule::query_config_block => {
559 parse_query_config_block(config, p);
561 }
562 Rule::positions_kwarg => {
563 use super::schema::PositionMode;
565 config.positions = Some(match p.as_str() {
566 "ordinal" => PositionMode::Ordinal,
567 "token_position" => PositionMode::TokenPosition,
568 _ => PositionMode::Full, });
570 }
571 _ => {}
572 }
573}
574
575fn parse_query_config_block(config: &mut IndexConfig, pair: pest::iterators::Pair<Rule>) {
577 for inner in pair.into_inner() {
578 if inner.as_rule() == Rule::query_config_params {
579 for param in inner.into_inner() {
580 if param.as_rule() == Rule::query_config_param {
581 for p in param.into_inner() {
582 match p.as_rule() {
583 Rule::query_tokenizer_kwarg => {
584 if let Some(path) = p.into_inner().next()
586 && let Some(inner_path) = path.into_inner().next()
587 {
588 config.query_tokenizer = Some(inner_path.as_str().to_string());
589 }
590 }
591 Rule::query_weighting_kwarg => {
592 if let Some(w) = p.into_inner().next() {
594 config.query_weighting = Some(match w.as_str() {
595 "one" => QueryWeighting::One,
596 "idf" => QueryWeighting::Idf,
597 "idf_file" => QueryWeighting::IdfFile,
598 _ => QueryWeighting::One,
599 });
600 }
601 }
602 Rule::query_weight_threshold_kwarg => {
603 if let Some(t) = p.into_inner().next() {
604 config.query_weight_threshold =
605 Some(t.as_str().parse().unwrap_or_else(|_| {
606 log::warn!(
607 "Invalid query weight_threshold '{}', using 0.0",
608 t.as_str()
609 );
610 0.0
611 }));
612 }
613 }
614 Rule::query_max_dims_kwarg => {
615 if let Some(t) = p.into_inner().next() {
616 config.query_max_dims =
617 Some(t.as_str().parse().unwrap_or_else(|_| {
618 log::warn!(
619 "Invalid query max_dims '{}', using 0",
620 t.as_str()
621 );
622 0
623 }));
624 }
625 }
626 Rule::query_pruning_kwarg => {
627 if let Some(t) = p.into_inner().next() {
628 config.query_pruning =
629 Some(t.as_str().parse().unwrap_or_else(|_| {
630 log::warn!(
631 "Invalid query pruning '{}', using 1.0",
632 t.as_str()
633 );
634 1.0
635 }));
636 }
637 }
638 Rule::query_min_query_dims_kwarg => {
639 if let Some(t) = p.into_inner().next() {
640 config.query_min_query_dims =
641 Some(t.as_str().parse().unwrap_or_else(|_| {
642 log::warn!(
643 "Invalid query min_query_dims '{}', using 4",
644 t.as_str()
645 );
646 4
647 }));
648 }
649 }
650 Rule::query_lsp_gamma_kwarg => {
651 if let Some(value) = p.into_inner().next() {
652 config.query_lsp_gamma =
653 Some(value.as_str().parse().unwrap_or_else(|_| {
654 log::warn!(
655 "Invalid query lsp_gamma '{}', using 0",
656 value.as_str()
657 );
658 0
659 }));
660 }
661 }
662 _ => {}
663 }
664 }
665 }
666 }
667 }
668 }
669}
670
671fn parse_field_def(pair: pest::iterators::Pair<Rule>) -> Result<FieldDef> {
673 let mut inner = pair.into_inner();
674
675 let name = inner
676 .next()
677 .ok_or_else(|| Error::Schema("Missing field name".to_string()))?
678 .as_str()
679 .to_string();
680
681 let field_type_str = inner
682 .next()
683 .ok_or_else(|| Error::Schema("Missing field type".to_string()))?
684 .as_str();
685
686 let field_type = parse_field_type(field_type_str)?;
687
688 let mut tokenizer = None;
690 let mut sparse_vector_config = None;
691 let mut dense_vector_config = None;
692 let mut binary_dense_vector_config = None;
693 let mut indexed = true;
694 let mut stored = true;
695 let mut multi = false;
696 let mut fast = false;
697 let mut primary = false;
698 let mut reorder = false;
699 let mut index_config: Option<IndexConfig> = None;
700
701 for item in inner {
702 match item.as_rule() {
703 Rule::tokenizer_spec => {
704 if let Some(tok_name) = item.into_inner().next() {
706 tokenizer = Some(tok_name.as_str().to_string());
707 }
708 }
709 Rule::sparse_vector_config => {
710 sparse_vector_config = Some(parse_sparse_vector_config(item));
712 }
713 Rule::dense_vector_config => {
714 dense_vector_config = Some(parse_dense_vector_config(item));
716 }
717 Rule::binary_dense_vector_config => {
718 let dim: usize = item
720 .into_inner()
721 .next()
722 .map(|d| d.as_str().parse().unwrap_or(0))
723 .unwrap_or(0);
724 if dim == 0 || !dim.is_multiple_of(8) {
725 return Err(Error::Schema(format!(
726 "BinaryDenseVector dimension must be a positive multiple of 8, got {dim}"
727 )));
728 }
729 binary_dense_vector_config = Some(BinaryDenseVectorConfig::new(dim));
730 }
731 Rule::attributes => {
732 let attrs = parse_attributes(item);
733 indexed = attrs.indexed;
734 stored = attrs.stored;
735 multi = attrs.multi;
736 fast = attrs.fast;
737 primary = attrs.primary;
738 reorder = attrs.reorder;
739 index_config = attrs.index_config;
740 }
741 _ => {}
742 }
743 }
744
745 if field_type == FieldType::BinaryDenseVector
749 && binary_dense_vector_config.is_none()
750 && let Some(ref dv_config) = dense_vector_config
751 {
752 let dim = dv_config.dim;
753 if dim == 0 || !dim.is_multiple_of(8) {
754 return Err(Error::Schema(format!(
755 "BinaryDenseVector dimension must be a positive multiple of 8, got {dim}"
756 )));
757 }
758 binary_dense_vector_config = Some(BinaryDenseVectorConfig::new(dim));
759 dense_vector_config = None;
760 }
761
762 if primary {
764 fast = true;
765 indexed = true;
766 }
767
768 let mut positions = None;
770 if let Some(idx_cfg) = index_config {
771 positions = idx_cfg.positions;
772 if let Some(ref mut bv_config) = binary_dense_vector_config {
773 apply_index_config_to_binary_dense_vector(bv_config, idx_cfg);
774 } else if let Some(ref mut dv_config) = dense_vector_config {
775 apply_index_config_to_dense_vector(dv_config, idx_cfg);
776 } else if field_type == FieldType::SparseVector {
777 let sv_config = sparse_vector_config.get_or_insert(SparseVectorConfig::default());
779 apply_index_config_to_sparse_vector(sv_config, idx_cfg);
780 }
781 }
782
783 Ok(FieldDef {
784 name,
785 field_type,
786 indexed,
787 stored,
788 tokenizer,
789 multi,
790 positions,
791 sparse_vector_config,
792 dense_vector_config,
793 binary_dense_vector_config,
794 fast,
795 primary,
796 reorder,
797 })
798}
799
800fn apply_index_config_to_binary_dense_vector(
802 config: &mut BinaryDenseVectorConfig,
803 idx_cfg: IndexConfig,
804) {
805 if let Some(index_type) = idx_cfg.binary_index_type {
806 config.index_type = index_type;
807 }
808 if idx_cfg.num_clusters.is_some() {
809 config.num_clusters = idx_cfg.num_clusters;
810 }
811 if let Some(nprobe) = idx_cfg.nprobe {
812 config.nprobe = nprobe;
813 }
814 if let Some(routing) = idx_cfg.ivf_routing {
815 config.ivf_routing = routing;
816 }
817}
818
819fn apply_index_config_to_dense_vector(config: &mut DenseVectorConfig, idx_cfg: IndexConfig) {
821 if let Some(index_type) = idx_cfg.index_type {
823 config.index_type = index_type;
824 }
825
826 if idx_cfg.num_clusters.is_some() {
828 config.num_clusters = idx_cfg.num_clusters;
829 }
830
831 if let Some(nprobe) = idx_cfg.nprobe {
833 config.nprobe = nprobe;
834 }
835 if let Some(routing) = idx_cfg.ivf_routing {
836 config.ivf_routing = routing;
837 }
838
839 if idx_cfg.soar.is_some() {
841 if config.uses_ivf() {
842 config.soar = idx_cfg.soar;
843 } else {
844 log::warn!(
845 "'soar' requires the IVF-PQ index; \
846 ignoring for index type {:?}",
847 config.index_type
848 );
849 }
850 }
851}
852
853fn parse_sparse_vector_config(pair: pest::iterators::Pair<Rule>) -> SparseVectorConfig {
856 let mut index_size = IndexSize::default();
857
858 for inner in pair.into_inner() {
860 if inner.as_rule() == Rule::index_size_spec {
861 index_size = match inner.as_str() {
862 "u16" => IndexSize::U16,
863 "u32" => IndexSize::U32,
864 _ => IndexSize::default(),
865 };
866 }
867 }
868
869 SparseVectorConfig {
870 index_size,
871 ..SparseVectorConfig::default()
872 }
873}
874
875fn apply_index_config_to_sparse_vector(config: &mut SparseVectorConfig, idx_cfg: IndexConfig) {
877 if let Some(f) = idx_cfg.sparse_format {
878 config.format = f;
879 }
880 if let Some(q) = idx_cfg.quantization {
881 config.weight_quantization = q;
882 }
883 if let Some(t) = idx_cfg.weight_threshold {
884 config.weight_threshold = t;
885 }
886 if let Some(bs) = idx_cfg.block_size {
887 let adjusted = bs.next_power_of_two();
888 if adjusted != bs {
889 log::warn!(
890 "block_size {} adjusted to next power of two: {}",
891 bs,
892 adjusted
893 );
894 }
895 config.block_size = adjusted;
896 }
897 if let Some(bs) = idx_cfg.bmp_block_size {
898 let adjusted = bs.next_power_of_two().clamp(1, 256);
899 if adjusted != bs {
900 log::warn!(
901 "bmp_block_size {} adjusted to power of two in 1..=256: {}",
902 bs,
903 adjusted
904 );
905 }
906 config.bmp_block_size = adjusted;
907 }
908 if let Some(bits) = idx_cfg.bmp_grid_bits {
909 if bits == 2 || bits == 4 {
910 config.bmp_grid_bits = bits;
911 } else {
912 log::warn!(
913 "bmp_grid_bits {} unsupported (must be 2 or 4), using {}",
914 bits,
915 SparseVectorConfig::DEFAULT_BMP_GRID_BITS,
916 );
917 config.bmp_grid_bits = SparseVectorConfig::DEFAULT_BMP_GRID_BITS;
918 }
919 }
920 if let Some(p) = idx_cfg.pruning {
921 let clamped = p.clamp(0.0, 1.0);
922 if (clamped - p).abs() > f32::EPSILON {
923 log::warn!(
924 "pruning {} clamped to valid range [0.0, 1.0]: {}",
925 p,
926 clamped
927 );
928 }
929 config.pruning = Some(clamped);
930 }
931 if let Some(mt) = idx_cfg.min_terms {
932 config.min_terms = mt;
933 }
934 if let Some(dm) = idx_cfg.doc_mass {
935 let clamped = dm.clamp(0.0, 1.0);
936 if (clamped - dm).abs() > f32::EPSILON {
937 log::warn!(
938 "doc_mass {} clamped to valid range [0.0, 1.0]: {}",
939 dm,
940 clamped
941 );
942 }
943 config.doc_mass = Some(clamped);
944 }
945 if let Some(d) = idx_cfg.dims {
946 config.dims = Some(d);
947 }
948 if let Some(mw) = idx_cfg.max_weight {
949 config.max_weight = Some(mw);
950 }
951 if idx_cfg.query_tokenizer.is_some()
953 || idx_cfg.query_weighting.is_some()
954 || idx_cfg.query_weight_threshold.is_some()
955 || idx_cfg.query_max_dims.is_some()
956 || idx_cfg.query_pruning.is_some()
957 || idx_cfg.query_min_query_dims.is_some()
958 || idx_cfg.query_lsp_gamma.is_some()
959 {
960 let query_config = config
961 .query_config
962 .get_or_insert(SparseQueryConfig::default());
963 if let Some(tokenizer) = idx_cfg.query_tokenizer {
964 query_config.tokenizer = Some(tokenizer);
965 }
966 if let Some(weighting) = idx_cfg.query_weighting {
967 query_config.weighting = weighting;
968 }
969 if let Some(t) = idx_cfg.query_weight_threshold {
970 query_config.weight_threshold = t;
971 }
972 if let Some(d) = idx_cfg.query_max_dims {
973 query_config.max_query_dims = Some(d);
974 }
975 if let Some(p) = idx_cfg.query_pruning {
976 query_config.pruning = Some(p);
977 }
978 if let Some(m) = idx_cfg.query_min_query_dims {
979 query_config.min_query_dims = m;
980 }
981 if let Some(gamma) = idx_cfg.query_lsp_gamma {
982 query_config.lsp_gamma = Some(gamma);
983 }
984 }
985}
986
987fn parse_dense_vector_config(pair: pest::iterators::Pair<Rule>) -> DenseVectorConfig {
990 let mut dim: usize = 0;
991 let mut quantization = DenseVectorQuantization::F32;
992
993 for params in pair.into_inner() {
995 if params.as_rule() == Rule::dense_vector_params {
996 for inner in params.into_inner() {
997 match inner.as_rule() {
998 Rule::dense_vector_keyword_params => {
999 for kwarg in inner.into_inner() {
1000 match kwarg.as_rule() {
1001 Rule::dims_kwarg => {
1002 if let Some(d) = kwarg.into_inner().next() {
1003 dim = d.as_str().parse().unwrap_or(0);
1004 }
1005 }
1006 Rule::quant_type_spec => {
1007 quantization = parse_quant_type(kwarg.as_str());
1008 }
1009 _ => {}
1010 }
1011 }
1012 }
1013 Rule::dense_vector_positional_params => {
1014 for item in inner.into_inner() {
1015 match item.as_rule() {
1016 Rule::dimension_spec => {
1017 dim = item.as_str().parse().unwrap_or(0);
1018 }
1019 Rule::quant_type_spec => {
1020 quantization = parse_quant_type(item.as_str());
1021 }
1022 _ => {}
1023 }
1024 }
1025 }
1026 _ => {}
1027 }
1028 }
1029 }
1030 }
1031
1032 DenseVectorConfig::new(dim).with_quantization(quantization)
1033}
1034
1035fn parse_quant_type(s: &str) -> DenseVectorQuantization {
1036 match s.trim() {
1037 "f16" => DenseVectorQuantization::F16,
1038 "uint8" | "u8" => DenseVectorQuantization::UInt8,
1039 _ => DenseVectorQuantization::F32,
1040 }
1041}
1042
1043fn parse_default_fields_def(pair: pest::iterators::Pair<Rule>) -> Vec<String> {
1045 pair.into_inner().map(|p| p.as_str().to_string()).collect()
1046}
1047
1048fn parse_query_router_def(pair: pest::iterators::Pair<Rule>) -> Result<QueryRouterRule> {
1050 let mut pattern = String::new();
1051 let mut substitution = String::new();
1052 let mut target_field = String::new();
1053 let mut mode = RoutingMode::Additional;
1054
1055 for prop in pair.into_inner() {
1056 if prop.as_rule() != Rule::query_router_prop {
1057 continue;
1058 }
1059
1060 for inner in prop.into_inner() {
1061 match inner.as_rule() {
1062 Rule::query_router_pattern => {
1063 if let Some(regex_str) = inner.into_inner().next() {
1064 pattern = parse_string_value(regex_str);
1065 }
1066 }
1067 Rule::query_router_substitution => {
1068 if let Some(quoted) = inner.into_inner().next() {
1069 substitution = parse_string_value(quoted);
1070 }
1071 }
1072 Rule::query_router_target => {
1073 if let Some(ident) = inner.into_inner().next() {
1074 target_field = ident.as_str().to_string();
1075 }
1076 }
1077 Rule::query_router_mode => {
1078 if let Some(mode_val) = inner.into_inner().next() {
1079 mode = match mode_val.as_str() {
1080 "exclusive" => RoutingMode::Exclusive,
1081 "additional" => RoutingMode::Additional,
1082 _ => RoutingMode::Additional,
1083 };
1084 }
1085 }
1086 _ => {}
1087 }
1088 }
1089 }
1090
1091 if pattern.is_empty() {
1092 return Err(Error::Schema("query_router missing 'pattern'".to_string()));
1093 }
1094 if substitution.is_empty() {
1095 return Err(Error::Schema(
1096 "query_router missing 'substitution'".to_string(),
1097 ));
1098 }
1099 if target_field.is_empty() {
1100 return Err(Error::Schema(
1101 "query_router missing 'target_field'".to_string(),
1102 ));
1103 }
1104
1105 Ok(QueryRouterRule {
1106 pattern,
1107 substitution,
1108 target_field,
1109 mode,
1110 })
1111}
1112
1113fn parse_string_value(pair: pest::iterators::Pair<Rule>) -> String {
1115 let s = pair.as_str();
1116 match pair.as_rule() {
1117 Rule::regex_string => {
1118 if let Some(inner) = pair.into_inner().next() {
1120 parse_string_value(inner)
1121 } else {
1122 s.to_string()
1123 }
1124 }
1125 Rule::raw_string => {
1126 s[2..s.len() - 1].to_string()
1128 }
1129 Rule::quoted_string => {
1130 let inner = &s[1..s.len() - 1];
1132 inner
1134 .replace("\\n", "\n")
1135 .replace("\\t", "\t")
1136 .replace("\\\"", "\"")
1137 .replace("\\\\", "\\")
1138 }
1139 _ => s.to_string(),
1140 }
1141}
1142
1143fn parse_index_def(pair: pest::iterators::Pair<Rule>) -> Result<IndexDef> {
1145 let mut inner = pair.into_inner();
1146
1147 let name = inner
1148 .next()
1149 .ok_or_else(|| Error::Schema("Missing index name".to_string()))?
1150 .as_str()
1151 .to_string();
1152
1153 let mut fields = Vec::new();
1154 let mut default_fields = Vec::new();
1155 let mut query_routers = Vec::new();
1156 let mut reorder_on_merge = false;
1157
1158 for item in inner {
1159 match item.as_rule() {
1160 Rule::field_def => {
1161 fields.push(parse_field_def(item)?);
1162 }
1163 Rule::default_fields_def => {
1164 default_fields = parse_default_fields_def(item);
1165 }
1166 Rule::query_router_def => {
1167 query_routers.push(parse_query_router_def(item)?);
1168 }
1169 Rule::reorder_on_merge_def => {
1170 let value = item
1171 .into_inner()
1172 .next()
1173 .map(|b| b.as_str() == "true")
1174 .unwrap_or(false);
1175 reorder_on_merge = value;
1176 }
1177 _ => {}
1178 }
1179 }
1180
1181 let primary_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.primary).collect();
1183 if primary_fields.len() > 1 {
1184 return Err(Error::Schema(format!(
1185 "Index '{}' has {} primary key fields, but at most one is allowed",
1186 name,
1187 primary_fields.len()
1188 )));
1189 }
1190 if let Some(pk) = primary_fields.first() {
1191 if pk.field_type != FieldType::Text {
1192 return Err(Error::Schema(format!(
1193 "Primary key field '{}' must be of type text, got {:?}",
1194 pk.name, pk.field_type
1195 )));
1196 }
1197 if pk.multi {
1198 return Err(Error::Schema(format!(
1199 "Primary key field '{}' cannot be multi-valued",
1200 pk.name
1201 )));
1202 }
1203 }
1204
1205 Ok(IndexDef {
1206 name,
1207 fields,
1208 default_fields,
1209 query_routers,
1210 reorder_on_merge,
1211 })
1212}
1213
1214pub fn parse_sdl(input: &str) -> Result<Vec<IndexDef>> {
1216 let pairs = SdlParser::parse(Rule::file, input)
1217 .map_err(|e| Error::Schema(format!("Parse error: {}", e)))?;
1218
1219 let mut indexes = Vec::new();
1220
1221 for pair in pairs {
1222 if pair.as_rule() == Rule::file {
1223 for inner in pair.into_inner() {
1224 if inner.as_rule() == Rule::index_def {
1225 indexes.push(parse_index_def(inner)?);
1226 }
1227 }
1228 }
1229 }
1230
1231 Ok(indexes)
1232}
1233
1234pub fn parse_single_index(input: &str) -> Result<IndexDef> {
1236 let indexes = parse_sdl(input)?;
1237
1238 if indexes.is_empty() {
1239 return Err(Error::Schema("No index definition found".to_string()));
1240 }
1241
1242 if indexes.len() > 1 {
1243 return Err(Error::Schema(
1244 "Multiple index definitions found, expected one".to_string(),
1245 ));
1246 }
1247
1248 Ok(indexes.into_iter().next().unwrap())
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253 use super::*;
1254
1255 #[test]
1256 fn test_parse_simple_schema() {
1257 let sdl = r#"
1258 index articles {
1259 field title: text [indexed, stored]
1260 field body: text [indexed]
1261 }
1262 "#;
1263
1264 let indexes = parse_sdl(sdl).unwrap();
1265 assert_eq!(indexes.len(), 1);
1266
1267 let index = &indexes[0];
1268 assert_eq!(index.name, "articles");
1269 assert_eq!(index.fields.len(), 2);
1270
1271 assert_eq!(index.fields[0].name, "title");
1272 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1273 assert!(index.fields[0].indexed);
1274 assert!(index.fields[0].stored);
1275
1276 assert_eq!(index.fields[1].name, "body");
1277 assert!(matches!(index.fields[1].field_type, FieldType::Text));
1278 assert!(index.fields[1].indexed);
1279 assert!(!index.fields[1].stored);
1280 }
1281
1282 #[test]
1283 fn test_parse_all_field_types() {
1284 let sdl = r#"
1285 index test {
1286 field text_field: text [indexed, stored]
1287 field u64_field: u64 [indexed, stored]
1288 field i64_field: i64 [indexed, stored]
1289 field f64_field: f64 [indexed, stored]
1290 field bytes_field: bytes [stored]
1291 }
1292 "#;
1293
1294 let indexes = parse_sdl(sdl).unwrap();
1295 let index = &indexes[0];
1296
1297 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1298 assert!(matches!(index.fields[1].field_type, FieldType::U64));
1299 assert!(matches!(index.fields[2].field_type, FieldType::I64));
1300 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1301 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1302 }
1303
1304 #[test]
1305 fn test_parse_with_comments() {
1306 let sdl = r#"
1307 # This is a comment
1308 index articles {
1309 # Title field
1310 field title: text [indexed, stored]
1311 field body: text [indexed] # inline comment not supported yet
1312 }
1313 "#;
1314
1315 let indexes = parse_sdl(sdl).unwrap();
1316 assert_eq!(indexes[0].fields.len(), 2);
1317 }
1318
1319 #[test]
1320 fn test_parse_type_aliases() {
1321 let sdl = r#"
1322 index test {
1323 field a: string [indexed]
1324 field b: int [indexed]
1325 field c: uint [indexed]
1326 field d: float [indexed]
1327 field e: binary [stored]
1328 }
1329 "#;
1330
1331 let indexes = parse_sdl(sdl).unwrap();
1332 let index = &indexes[0];
1333
1334 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1335 assert!(matches!(index.fields[1].field_type, FieldType::I64));
1336 assert!(matches!(index.fields[2].field_type, FieldType::U64));
1337 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1338 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1339 }
1340
1341 #[test]
1342 fn test_to_schema() {
1343 let sdl = r#"
1344 index articles {
1345 field title: text [indexed, stored]
1346 field views: u64 [indexed, stored]
1347 }
1348 "#;
1349
1350 let indexes = parse_sdl(sdl).unwrap();
1351 let schema = indexes[0].to_schema();
1352
1353 assert!(schema.get_field("title").is_some());
1354 assert!(schema.get_field("views").is_some());
1355 assert!(schema.get_field("nonexistent").is_none());
1356 }
1357
1358 #[test]
1359 fn test_default_attributes() {
1360 let sdl = r#"
1361 index test {
1362 field title: text
1363 }
1364 "#;
1365
1366 let indexes = parse_sdl(sdl).unwrap();
1367 let field = &indexes[0].fields[0];
1368
1369 assert!(field.indexed);
1371 assert!(field.stored);
1372 }
1373
1374 #[test]
1375 fn test_multiple_indexes() {
1376 let sdl = r#"
1377 index articles {
1378 field title: text [indexed, stored]
1379 }
1380
1381 index users {
1382 field name: text [indexed, stored]
1383 field email: text [indexed, stored]
1384 }
1385 "#;
1386
1387 let indexes = parse_sdl(sdl).unwrap();
1388 assert_eq!(indexes.len(), 2);
1389 assert_eq!(indexes[0].name, "articles");
1390 assert_eq!(indexes[1].name, "users");
1391 }
1392
1393 #[test]
1394 fn test_tokenizer_spec() {
1395 let sdl = r#"
1396 index articles {
1397 field title: text<en_stem> [indexed, stored]
1398 field body: text<simple> [indexed]
1399 field author: text [indexed, stored]
1400 }
1401 "#;
1402
1403 let indexes = parse_sdl(sdl).unwrap();
1404 let index = &indexes[0];
1405
1406 assert_eq!(index.fields[0].name, "title");
1407 assert_eq!(index.fields[0].tokenizer, Some("en_stem".to_string()));
1408
1409 assert_eq!(index.fields[1].name, "body");
1410 assert_eq!(index.fields[1].tokenizer, Some("simple".to_string()));
1411
1412 assert_eq!(index.fields[2].name, "author");
1413 assert_eq!(index.fields[2].tokenizer, None); }
1415
1416 #[test]
1417 fn test_tokenizer_in_schema() {
1418 let sdl = r#"
1419 index articles {
1420 field title: text<german> [indexed, stored]
1421 field body: text<en_stem> [indexed]
1422 }
1423 "#;
1424
1425 let indexes = parse_sdl(sdl).unwrap();
1426 let schema = indexes[0].to_schema();
1427
1428 let title_field = schema.get_field("title").unwrap();
1429 let title_entry = schema.get_field_entry(title_field).unwrap();
1430 assert_eq!(title_entry.tokenizer, Some("german".to_string()));
1431
1432 let body_field = schema.get_field("body").unwrap();
1433 let body_entry = schema.get_field_entry(body_field).unwrap();
1434 assert_eq!(body_entry.tokenizer, Some("en_stem".to_string()));
1435 }
1436
1437 #[test]
1438 fn test_query_router_basic() {
1439 let sdl = r#"
1440 index documents {
1441 field title: text [indexed, stored]
1442 field uri: text [indexed, stored]
1443
1444 query_router {
1445 pattern: "10\\.\\d{4,}/[^\\s]+"
1446 substitution: "doi://{0}"
1447 target_field: uris
1448 mode: exclusive
1449 }
1450 }
1451 "#;
1452
1453 let indexes = parse_sdl(sdl).unwrap();
1454 let index = &indexes[0];
1455
1456 assert_eq!(index.query_routers.len(), 1);
1457 let router = &index.query_routers[0];
1458 assert_eq!(router.pattern, r"10\.\d{4,}/[^\s]+");
1459 assert_eq!(router.substitution, "doi://{0}");
1460 assert_eq!(router.target_field, "uris");
1461 assert_eq!(router.mode, RoutingMode::Exclusive);
1462 }
1463
1464 #[test]
1465 fn test_query_router_raw_string() {
1466 let sdl = r#"
1467 index documents {
1468 field uris: text [indexed, stored]
1469
1470 query_router {
1471 pattern: r"^pmid:(\d+)$"
1472 substitution: "pubmed://{1}"
1473 target_field: uris
1474 mode: additional
1475 }
1476 }
1477 "#;
1478
1479 let indexes = parse_sdl(sdl).unwrap();
1480 let router = &indexes[0].query_routers[0];
1481
1482 assert_eq!(router.pattern, r"^pmid:(\d+)$");
1483 assert_eq!(router.substitution, "pubmed://{1}");
1484 assert_eq!(router.mode, RoutingMode::Additional);
1485 }
1486
1487 #[test]
1488 fn test_multiple_query_routers() {
1489 let sdl = r#"
1490 index documents {
1491 field uris: text [indexed, stored]
1492
1493 query_router {
1494 pattern: r"^doi:(10\.\d{4,}/[^\s]+)$"
1495 substitution: "doi://{1}"
1496 target_field: uris
1497 mode: exclusive
1498 }
1499
1500 query_router {
1501 pattern: r"^pmid:(\d+)$"
1502 substitution: "pubmed://{1}"
1503 target_field: uris
1504 mode: exclusive
1505 }
1506
1507 query_router {
1508 pattern: r"^arxiv:(\d+\.\d+)$"
1509 substitution: "arxiv://{1}"
1510 target_field: uris
1511 mode: additional
1512 }
1513 }
1514 "#;
1515
1516 let indexes = parse_sdl(sdl).unwrap();
1517 assert_eq!(indexes[0].query_routers.len(), 3);
1518 }
1519
1520 #[test]
1521 fn test_query_router_default_mode() {
1522 let sdl = r#"
1523 index documents {
1524 field uris: text [indexed, stored]
1525
1526 query_router {
1527 pattern: r"test"
1528 substitution: "{0}"
1529 target_field: uris
1530 }
1531 }
1532 "#;
1533
1534 let indexes = parse_sdl(sdl).unwrap();
1535 assert_eq!(indexes[0].query_routers[0].mode, RoutingMode::Additional);
1537 }
1538
1539 #[test]
1540 fn test_multi_attribute() {
1541 let sdl = r#"
1542 index documents {
1543 field uris: text [indexed, stored<multi>]
1544 field title: text [indexed, stored]
1545 }
1546 "#;
1547
1548 let indexes = parse_sdl(sdl).unwrap();
1549 assert_eq!(indexes.len(), 1);
1550
1551 let fields = &indexes[0].fields;
1552 assert_eq!(fields.len(), 2);
1553
1554 assert_eq!(fields[0].name, "uris");
1556 assert!(fields[0].multi, "uris field should have multi=true");
1557
1558 assert_eq!(fields[1].name, "title");
1560 assert!(!fields[1].multi, "title field should have multi=false");
1561
1562 let schema = indexes[0].to_schema();
1564 let uris_field = schema.get_field("uris").unwrap();
1565 let title_field = schema.get_field("title").unwrap();
1566
1567 assert!(schema.get_field_entry(uris_field).unwrap().multi);
1568 assert!(!schema.get_field_entry(title_field).unwrap().multi);
1569 }
1570
1571 #[test]
1572 fn test_sparse_vector_field() {
1573 let sdl = r#"
1574 index documents {
1575 field embedding: sparse_vector [indexed, stored]
1576 }
1577 "#;
1578
1579 let indexes = parse_sdl(sdl).unwrap();
1580 assert_eq!(indexes.len(), 1);
1581 assert_eq!(indexes[0].fields.len(), 1);
1582 assert_eq!(indexes[0].fields[0].name, "embedding");
1583 assert_eq!(indexes[0].fields[0].field_type, FieldType::SparseVector);
1584 assert!(indexes[0].fields[0].sparse_vector_config.is_none());
1585 }
1586
1587 #[test]
1588 fn test_sparse_vector_with_config() {
1589 let sdl = r#"
1590 index documents {
1591 field embedding: sparse_vector<u16> [indexed<quantization: uint8>, stored]
1592 field dense: sparse_vector<u32> [indexed<quantization: float32>]
1593 }
1594 "#;
1595
1596 let indexes = parse_sdl(sdl).unwrap();
1597 assert_eq!(indexes[0].fields.len(), 2);
1598
1599 let f1 = &indexes[0].fields[0];
1601 assert_eq!(f1.name, "embedding");
1602 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1603 assert_eq!(config1.index_size, IndexSize::U16);
1604 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1605
1606 let f2 = &indexes[0].fields[1];
1608 assert_eq!(f2.name, "dense");
1609 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1610 assert_eq!(config2.index_size, IndexSize::U32);
1611 assert_eq!(config2.weight_quantization, WeightQuantization::Float32);
1612 }
1613
1614 #[test]
1615 fn test_sparse_vector_bmp_block_size() {
1616 let sdl = r#"
1617 index documents {
1618 field emb: sparse_vector<u32> [indexed<format: bmp, dims: 105879, bmp_block_size: 256>]
1619 field emb2: sparse_vector<u32> [indexed<format: bmp, dims: 30522>]
1620 }
1621 "#;
1622
1623 let indexes = parse_sdl(sdl).unwrap();
1624 let config1 = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1625 assert_eq!(config1.format, SparseFormat::Bmp);
1626 assert_eq!(config1.bmp_block_size, 256);
1627
1628 let config2 = indexes[0].fields[1].sparse_vector_config.as_ref().unwrap();
1630 assert_eq!(
1631 config2.bmp_block_size,
1632 SparseVectorConfig::DEFAULT_BMP_BLOCK_SIZE
1633 );
1634 }
1635
1636 #[test]
1639 fn test_sparse_vector_bmp_grid_bits() {
1640 let sdl = r#"
1641 index documents {
1642 field emb: sparse_vector<u32> [indexed<format: bmp, dims: 105879, bmp_block_size: 256, bmp_grid_bits: 2>]
1643 field emb2: sparse_vector<u32> [indexed<format: bmp, dims: 30522>]
1644 field emb3: sparse_vector<u32> [indexed<format: bmp, dims: 30522, bmp_grid_bits: 3>]
1645 }
1646 "#;
1647
1648 let indexes = parse_sdl(sdl).unwrap();
1649 let config1 = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1650 assert_eq!(config1.bmp_grid_bits, 2);
1651 let config2 = indexes[0].fields[1].sparse_vector_config.as_ref().unwrap();
1653 assert_eq!(
1654 config2.bmp_grid_bits,
1655 SparseVectorConfig::DEFAULT_BMP_GRID_BITS
1656 );
1657 let config3 = indexes[0].fields[2].sparse_vector_config.as_ref().unwrap();
1659 assert_eq!(
1660 config3.bmp_grid_bits,
1661 SparseVectorConfig::DEFAULT_BMP_GRID_BITS
1662 );
1663 }
1664
1665 #[test]
1666 fn test_sparse_vector_with_weight_threshold() {
1667 let sdl = r#"
1668 index documents {
1669 field embedding: sparse_vector<u16> [indexed<quantization: uint8, weight_threshold: 0.1>, stored]
1670 field embedding2: sparse_vector<u32> [indexed<quantization: float16, weight_threshold: 0.05>]
1671 }
1672 "#;
1673
1674 let indexes = parse_sdl(sdl).unwrap();
1675 assert_eq!(indexes[0].fields.len(), 2);
1676
1677 let f1 = &indexes[0].fields[0];
1679 assert_eq!(f1.name, "embedding");
1680 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1681 assert_eq!(config1.index_size, IndexSize::U16);
1682 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1683 assert!((config1.weight_threshold - 0.1).abs() < 0.001);
1684
1685 let f2 = &indexes[0].fields[1];
1687 assert_eq!(f2.name, "embedding2");
1688 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1689 assert_eq!(config2.index_size, IndexSize::U32);
1690 assert_eq!(config2.weight_quantization, WeightQuantization::Float16);
1691 assert!((config2.weight_threshold - 0.05).abs() < 0.001);
1692 }
1693
1694 #[test]
1695 fn test_sparse_vector_with_pruning() {
1696 let sdl = r#"
1697 index documents {
1698 field embedding: sparse_vector [indexed<quantization: uint8, pruning: 0.1>, stored]
1699 }
1700 "#;
1701
1702 let indexes = parse_sdl(sdl).unwrap();
1703 let f = &indexes[0].fields[0];
1704 assert_eq!(f.name, "embedding");
1705 let config = f.sparse_vector_config.as_ref().unwrap();
1706 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
1707 assert_eq!(config.pruning, Some(0.1));
1708 }
1709
1710 #[test]
1711 fn test_sparse_vector_with_doc_mass() {
1712 let sdl = r#"
1713 index documents {
1714 field embedding: sparse_vector [indexed<quantization: uint8, doc_mass: 0.9>, stored]
1715 }
1716 "#;
1717
1718 let indexes = parse_sdl(sdl).unwrap();
1719 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1720 assert_eq!(config.doc_mass, Some(0.9));
1721
1722 let sdl = r#"
1724 index documents {
1725 field embedding: sparse_vector [indexed<quantization: uint8>]
1726 }
1727 "#;
1728 let indexes = parse_sdl(sdl).unwrap();
1729 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1730 assert_eq!(config.doc_mass, None);
1731 }
1732
1733 #[test]
1734 fn test_dense_vector_field() {
1735 let sdl = r#"
1736 index documents {
1737 field embedding: dense_vector<768> [indexed, stored]
1738 }
1739 "#;
1740
1741 let indexes = parse_sdl(sdl).unwrap();
1742 assert_eq!(indexes.len(), 1);
1743 assert_eq!(indexes[0].fields.len(), 1);
1744
1745 let f = &indexes[0].fields[0];
1746 assert_eq!(f.name, "embedding");
1747 assert_eq!(f.field_type, FieldType::DenseVector);
1748
1749 let config = f.dense_vector_config.as_ref().unwrap();
1750 assert_eq!(config.dim, 768);
1751 }
1752
1753 #[test]
1754 fn test_dense_vector_alias() {
1755 let sdl = r#"
1756 index documents {
1757 field embedding: vector<1536> [indexed]
1758 }
1759 "#;
1760
1761 let indexes = parse_sdl(sdl).unwrap();
1762 assert_eq!(indexes[0].fields[0].field_type, FieldType::DenseVector);
1763 assert_eq!(
1764 indexes[0].fields[0]
1765 .dense_vector_config
1766 .as_ref()
1767 .unwrap()
1768 .dim,
1769 1536
1770 );
1771 }
1772
1773 #[test]
1774 fn test_dense_vector_with_num_clusters() {
1775 let sdl = r#"
1776 index documents {
1777 field embedding: dense_vector<768> [indexed<ivf_pq, num_clusters: 256>, stored]
1778 }
1779 "#;
1780
1781 let indexes = parse_sdl(sdl).unwrap();
1782 assert_eq!(indexes.len(), 1);
1783
1784 let f = &indexes[0].fields[0];
1785 assert_eq!(f.name, "embedding");
1786 assert_eq!(f.field_type, FieldType::DenseVector);
1787
1788 let config = f.dense_vector_config.as_ref().unwrap();
1789 assert_eq!(config.dim, 768);
1790 assert_eq!(config.num_clusters, Some(256));
1791 assert_eq!(config.nprobe, 64); }
1793
1794 #[test]
1795 fn test_dense_vector_with_soar() {
1796 let sdl = r#"
1797 index documents {
1798 field embedding: dense_vector<768> [indexed<ivf_pq, num_clusters: 256, soar: selective>, stored]
1799 }
1800 "#;
1801
1802 let indexes = parse_sdl(sdl).unwrap();
1803 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1804
1805 let soar = config.soar.as_ref().expect("soar should be enabled");
1806 assert_eq!(soar.num_secondary, 1);
1807 assert!(soar.selective);
1808
1809 let sdl = r#"
1811 index documents {
1812 field embedding: dense_vector<768> [indexed<ivf_pq, soar: aggressive>]
1813 }
1814 "#;
1815 let indexes = parse_sdl(sdl).unwrap();
1816 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1817 let soar = config.soar.as_ref().expect("soar should be enabled");
1818 assert_eq!(soar.num_secondary, 2);
1819 assert!(!soar.selective);
1820
1821 let sdl = r#"
1823 index documents {
1824 field embedding: dense_vector<768> [indexed<ivf_pq, soar: off>]
1825 }
1826 "#;
1827 let indexes = parse_sdl(sdl).unwrap();
1828 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1829 assert!(config.soar.is_none());
1830 }
1831
1832 #[test]
1833 fn test_ivf_routing_modes_apply_to_float_and_binary_fields() {
1834 let indexes = parse_sdl(
1835 r#"
1836 index vectors {
1837 field embedding: dense_vector<768> [indexed<ivf_pq, routing: hnsw>]
1838 field hash: binary_dense_vector<512> [indexed<ivf, routing: two_level>]
1839 }
1840 "#,
1841 )
1842 .unwrap();
1843 let schema = indexes[0].to_schema();
1844 let embedding = schema.get_field("embedding").unwrap();
1845 let hash = schema.get_field("hash").unwrap();
1846 assert_eq!(
1847 schema
1848 .get_field_entry(embedding)
1849 .unwrap()
1850 .dense_vector_config
1851 .as_ref()
1852 .unwrap()
1853 .ivf_routing,
1854 super::super::schema::IvfRoutingMode::Hnsw
1855 );
1856 assert_eq!(
1857 schema
1858 .get_field_entry(hash)
1859 .unwrap()
1860 .binary_dense_vector_config
1861 .as_ref()
1862 .unwrap()
1863 .ivf_routing,
1864 super::super::schema::IvfRoutingMode::TwoLevel
1865 );
1866 }
1867
1868 #[test]
1869 fn test_binary_dense_vector_with_ivf() {
1870 let sdl = r#"
1871 index documents {
1872 field hash: binary_dense_vector<512> [indexed<ivf, num_clusters: 128, nprobe: 16>, stored]
1873 }
1874 "#;
1875
1876 let indexes = parse_sdl(sdl).unwrap();
1877 let config = indexes[0].fields[0]
1878 .binary_dense_vector_config
1879 .as_ref()
1880 .unwrap();
1881 assert_eq!(config.dim, 512);
1882 assert_eq!(
1883 config.index_type,
1884 super::super::schema::BinaryIndexType::Ivf
1885 );
1886 assert_eq!(config.num_clusters, Some(128));
1887 assert_eq!(config.nprobe, 16);
1888
1889 let sdl = r#"
1892 index documents {
1893 field hash: binary_dense_vector<512> [indexed]
1894 }
1895 "#;
1896 let indexes = parse_sdl(sdl).unwrap();
1897 let config = indexes[0].fields[0]
1898 .binary_dense_vector_config
1899 .as_ref()
1900 .unwrap();
1901 assert_eq!(
1902 config.index_type,
1903 super::super::schema::BinaryIndexType::Ivf
1904 );
1905 }
1906
1907 #[test]
1908 fn test_dense_vector_with_num_clusters_and_nprobe() {
1909 let sdl = r#"
1910 index documents {
1911 field embedding: dense_vector<1536> [indexed<ivf_pq, num_clusters: 512, nprobe: 64>]
1912 }
1913 "#;
1914
1915 let indexes = parse_sdl(sdl).unwrap();
1916 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1917
1918 assert_eq!(config.dim, 1536);
1919 assert_eq!(config.num_clusters, Some(512));
1920 assert_eq!(config.nprobe, 64);
1921 }
1922
1923 #[test]
1924 fn test_dense_vector_keyword_syntax() {
1925 let sdl = r#"
1926 index documents {
1927 field embedding: dense_vector<dims: 1536> [indexed, stored]
1928 }
1929 "#;
1930
1931 let indexes = parse_sdl(sdl).unwrap();
1932 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1933
1934 assert_eq!(config.dim, 1536);
1935 assert!(config.num_clusters.is_none());
1936 }
1937
1938 #[test]
1939 fn test_dense_vector_keyword_syntax_full() {
1940 let sdl = r#"
1941 index documents {
1942 field embedding: dense_vector<dims: 1536> [indexed<ivf_pq, num_clusters: 256, 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(256));
1951 assert_eq!(config.nprobe, 64);
1952 }
1953
1954 #[test]
1955 fn test_dense_vector_keyword_syntax_partial() {
1956 let sdl = r#"
1957 index documents {
1958 field embedding: dense_vector<dims: 768> [indexed<ivf_pq, num_clusters: 128>]
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, 768);
1966 assert_eq!(config.num_clusters, Some(128));
1967 assert_eq!(config.nprobe, 64); }
1969
1970 #[test]
1971 fn test_dense_vector_ivf_pq_index_with_probe() {
1972 use crate::dsl::schema::VectorIndexType;
1973
1974 let sdl = r#"
1975 index documents {
1976 field embedding: dense_vector<dims: 768> [indexed<ivf_pq, num_clusters: 256, nprobe: 64>]
1977 }
1978 "#;
1979
1980 let indexes = parse_sdl(sdl).unwrap();
1981 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1982
1983 assert_eq!(config.dim, 768);
1984 assert_eq!(config.index_type, VectorIndexType::IvfPq);
1985 assert_eq!(config.num_clusters, Some(256));
1986 assert_eq!(config.nprobe, 64);
1987 }
1988
1989 #[test]
1990 fn test_dense_vector_ivf_pq_index_without_explicit_probe() {
1991 use crate::dsl::schema::VectorIndexType;
1992
1993 let sdl = r#"
1994 index documents {
1995 field embedding: dense_vector<dims: 1536> [indexed<ivf_pq, num_clusters: 512>]
1996 }
1997 "#;
1998
1999 let indexes = parse_sdl(sdl).unwrap();
2000 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2001
2002 assert_eq!(config.dim, 1536);
2003 assert_eq!(config.index_type, VectorIndexType::IvfPq);
2004 assert_eq!(config.num_clusters, Some(512));
2005 }
2006
2007 #[test]
2008 fn test_dense_vector_ivf_pq_no_clusters() {
2009 use crate::dsl::schema::VectorIndexType;
2010
2011 let sdl = r#"
2012 index documents {
2013 field embedding: dense_vector<dims: 768> [indexed<ivf_pq>]
2014 }
2015 "#;
2016
2017 let indexes = parse_sdl(sdl).unwrap();
2018 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2019
2020 assert_eq!(config.dim, 768);
2021 assert_eq!(config.index_type, VectorIndexType::IvfPq);
2022 assert!(config.num_clusters.is_none());
2023 }
2024
2025 #[test]
2026 fn test_dense_vector_flat_index() {
2027 use crate::dsl::schema::VectorIndexType;
2028
2029 let sdl = r#"
2030 index documents {
2031 field embedding: dense_vector<dims: 768> [indexed<flat>]
2032 }
2033 "#;
2034
2035 let indexes = parse_sdl(sdl).unwrap();
2036 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2037
2038 assert_eq!(config.dim, 768);
2039 assert_eq!(config.index_type, VectorIndexType::Flat);
2040 }
2041
2042 #[test]
2043 fn test_dense_vector_default_index_type() {
2044 use crate::dsl::schema::VectorIndexType;
2045
2046 let sdl = r#"
2048 index documents {
2049 field embedding: dense_vector<dims: 768> [indexed]
2050 }
2051 "#;
2052
2053 let indexes = parse_sdl(sdl).unwrap();
2054 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2055
2056 assert_eq!(config.dim, 768);
2057 assert_eq!(config.index_type, VectorIndexType::IvfPq);
2058 }
2059
2060 #[test]
2061 fn test_dense_vector_f16_quantization() {
2062 use crate::dsl::schema::{DenseVectorQuantization, VectorIndexType};
2063
2064 let sdl = r#"
2065 index documents {
2066 field embedding: dense_vector<768, f16> [indexed]
2067 }
2068 "#;
2069
2070 let indexes = parse_sdl(sdl).unwrap();
2071 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2072
2073 assert_eq!(config.dim, 768);
2074 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2075 assert_eq!(config.index_type, VectorIndexType::IvfPq);
2076 }
2077
2078 #[test]
2079 fn test_dense_vector_uint8_quantization() {
2080 use crate::dsl::schema::DenseVectorQuantization;
2081
2082 let sdl = r#"
2083 index documents {
2084 field embedding: dense_vector<1024, uint8> [indexed<ivf_pq>]
2085 }
2086 "#;
2087
2088 let indexes = parse_sdl(sdl).unwrap();
2089 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2090
2091 assert_eq!(config.dim, 1024);
2092 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2093 }
2094
2095 #[test]
2096 fn test_dense_vector_u8_alias() {
2097 use crate::dsl::schema::DenseVectorQuantization;
2098
2099 let sdl = r#"
2100 index documents {
2101 field embedding: dense_vector<512, u8> [indexed]
2102 }
2103 "#;
2104
2105 let indexes = parse_sdl(sdl).unwrap();
2106 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2107
2108 assert_eq!(config.dim, 512);
2109 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2110 }
2111
2112 #[test]
2113 fn test_dense_vector_default_f32_quantization() {
2114 use crate::dsl::schema::DenseVectorQuantization;
2115
2116 let sdl = r#"
2118 index documents {
2119 field embedding: dense_vector<768> [indexed]
2120 }
2121 "#;
2122
2123 let indexes = parse_sdl(sdl).unwrap();
2124 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2125
2126 assert_eq!(config.dim, 768);
2127 assert_eq!(config.quantization, DenseVectorQuantization::F32);
2128 }
2129
2130 #[test]
2131 fn test_dense_vector_keyword_with_quantization() {
2132 use crate::dsl::schema::DenseVectorQuantization;
2133
2134 let sdl = r#"
2135 index documents {
2136 field embedding: dense_vector<dims: 768, f16> [indexed]
2137 }
2138 "#;
2139
2140 let indexes = parse_sdl(sdl).unwrap();
2141 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2142
2143 assert_eq!(config.dim, 768);
2144 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2145 }
2146
2147 #[test]
2148 fn test_json_field_type() {
2149 let sdl = r#"
2150 index documents {
2151 field title: text [indexed, stored]
2152 field metadata: json [stored]
2153 field extra: json
2154 }
2155 "#;
2156
2157 let indexes = parse_sdl(sdl).unwrap();
2158 let index = &indexes[0];
2159
2160 assert_eq!(index.fields.len(), 3);
2161
2162 assert_eq!(index.fields[1].name, "metadata");
2164 assert!(matches!(index.fields[1].field_type, FieldType::Json));
2165 assert!(index.fields[1].stored);
2166 assert_eq!(index.fields[2].name, "extra");
2170 assert!(matches!(index.fields[2].field_type, FieldType::Json));
2171
2172 let schema = index.to_schema();
2174 let metadata_field = schema.get_field("metadata").unwrap();
2175 let entry = schema.get_field_entry(metadata_field).unwrap();
2176 assert_eq!(entry.field_type, FieldType::Json);
2177 assert!(!entry.indexed); assert!(entry.stored);
2179 }
2180
2181 #[test]
2182 fn test_sparse_vector_query_config() {
2183 use crate::structures::QueryWeighting;
2184
2185 let sdl = r#"
2186 index documents {
2187 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "Alibaba-NLP/gte-Qwen2-1.5B-instruct", weighting: idf>>]
2188 }
2189 "#;
2190
2191 let indexes = parse_sdl(sdl).unwrap();
2192 let index = &indexes[0];
2193
2194 assert_eq!(index.fields.len(), 1);
2195 assert_eq!(index.fields[0].name, "embedding");
2196 assert!(matches!(
2197 index.fields[0].field_type,
2198 FieldType::SparseVector
2199 ));
2200
2201 let config = index.fields[0].sparse_vector_config.as_ref().unwrap();
2202 assert_eq!(config.index_size, IndexSize::U16);
2203 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2204
2205 let query_config = config.query_config.as_ref().unwrap();
2207 assert_eq!(
2208 query_config.tokenizer.as_deref(),
2209 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2210 );
2211 assert_eq!(query_config.weighting, QueryWeighting::Idf);
2212
2213 let schema = index.to_schema();
2215 let embedding_field = schema.get_field("embedding").unwrap();
2216 let entry = schema.get_field_entry(embedding_field).unwrap();
2217 let sv_config = entry.sparse_vector_config.as_ref().unwrap();
2218 let qc = sv_config.query_config.as_ref().unwrap();
2219 assert_eq!(
2220 qc.tokenizer.as_deref(),
2221 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2222 );
2223 assert_eq!(qc.weighting, QueryWeighting::Idf);
2224 }
2225
2226 #[test]
2227 fn test_sparse_vector_query_config_weighting_one() {
2228 use crate::structures::QueryWeighting;
2229
2230 let sdl = r#"
2231 index documents {
2232 field embedding: sparse_vector [indexed<query<weighting: one>>]
2233 }
2234 "#;
2235
2236 let indexes = parse_sdl(sdl).unwrap();
2237 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2238
2239 let query_config = config.query_config.as_ref().unwrap();
2240 assert!(query_config.tokenizer.is_none());
2241 assert_eq!(query_config.weighting, QueryWeighting::One);
2242 }
2243
2244 #[test]
2245 fn test_sparse_vector_query_config_weighting_idf_file() {
2246 use crate::structures::QueryWeighting;
2247
2248 let sdl = r#"
2249 index documents {
2250 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "opensearch-neural-sparse-encoding-v1", weighting: idf_file>>]
2251 }
2252 "#;
2253
2254 let indexes = parse_sdl(sdl).unwrap();
2255 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2256
2257 let query_config = config.query_config.as_ref().unwrap();
2258 assert_eq!(
2259 query_config.tokenizer.as_deref(),
2260 Some("opensearch-neural-sparse-encoding-v1")
2261 );
2262 assert_eq!(query_config.weighting, QueryWeighting::IdfFile);
2263
2264 let schema = indexes[0].to_schema();
2266 let field = schema.get_field("embedding").unwrap();
2267 let entry = schema.get_field_entry(field).unwrap();
2268 let sc = entry.sparse_vector_config.as_ref().unwrap();
2269 let qc = sc.query_config.as_ref().unwrap();
2270 assert_eq!(qc.weighting, QueryWeighting::IdfFile);
2271 }
2272
2273 #[test]
2274 fn test_sparse_vector_query_config_pruning_params() {
2275 let sdl = r#"
2276 index documents {
2277 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<weighting: idf, weight_threshold: 0.03, max_dims: 25, pruning: 0.2, lsp_gamma: 500>>]
2278 }
2279 "#;
2280
2281 let indexes = parse_sdl(sdl).unwrap();
2282 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2283
2284 let qc = config.query_config.as_ref().unwrap();
2285 assert_eq!(qc.weighting, QueryWeighting::Idf);
2286 assert!((qc.weight_threshold - 0.03).abs() < 0.001);
2287 assert_eq!(qc.max_query_dims, Some(25));
2288 assert!((qc.pruning.unwrap() - 0.2).abs() < 0.001);
2289 assert_eq!(qc.lsp_gamma, Some(500));
2290
2291 let schema = indexes[0].to_schema();
2293 let field = schema.get_field("embedding").unwrap();
2294 let entry = schema.get_field_entry(field).unwrap();
2295 let sc = entry.sparse_vector_config.as_ref().unwrap();
2296 let rqc = sc.query_config.as_ref().unwrap();
2297 assert!((rqc.weight_threshold - 0.03).abs() < 0.001);
2298 assert_eq!(rqc.max_query_dims, Some(25));
2299 assert!((rqc.pruning.unwrap() - 0.2).abs() < 0.001);
2300 assert_eq!(rqc.lsp_gamma, Some(500));
2301 }
2302
2303 #[test]
2304 fn test_sparse_vector_format_maxscore() {
2305 let sdl = r#"
2306 index documents {
2307 field embedding: sparse_vector<u16> [indexed<format: maxscore, quantization: uint8>]
2308 }
2309 "#;
2310
2311 let indexes = parse_sdl(sdl).unwrap();
2312 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2313 assert_eq!(config.format, SparseFormat::MaxScore);
2314 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2315
2316 let schema = indexes[0].to_schema();
2318 let field = schema.get_field("embedding").unwrap();
2319 let entry = schema.get_field_entry(field).unwrap();
2320 let sc = entry.sparse_vector_config.as_ref().unwrap();
2321 assert_eq!(sc.format, SparseFormat::MaxScore);
2322 }
2323
2324 #[test]
2325 fn test_sparse_vector_format_bmp() {
2326 let sdl = r#"
2327 index documents {
2328 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>]
2329 }
2330 "#;
2331
2332 let indexes = parse_sdl(sdl).unwrap();
2333 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2334 assert_eq!(config.format, SparseFormat::Bmp);
2335 }
2336
2337 #[test]
2338 fn test_fast_attribute() {
2339 let sdl = r#"
2340 index products {
2341 field name: text [indexed, stored]
2342 field price: f64 [indexed, fast]
2343 field category: text [indexed, stored, fast]
2344 field count: u64 [fast]
2345 field score: i64 [indexed, stored, fast]
2346 }
2347 "#;
2348
2349 let indexes = parse_sdl(sdl).unwrap();
2350 assert_eq!(indexes.len(), 1);
2351 let index = &indexes[0];
2352 assert_eq!(index.fields.len(), 5);
2353
2354 assert!(!index.fields[0].fast);
2356 assert!(index.fields[1].fast);
2358 assert!(matches!(index.fields[1].field_type, FieldType::F64));
2359 assert!(index.fields[2].fast);
2361 assert!(matches!(index.fields[2].field_type, FieldType::Text));
2362 assert!(index.fields[3].fast);
2364 assert!(matches!(index.fields[3].field_type, FieldType::U64));
2365 assert!(index.fields[4].fast);
2367 assert!(matches!(index.fields[4].field_type, FieldType::I64));
2368
2369 let schema = index.to_schema();
2371 let price_field = schema.get_field("price").unwrap();
2372 assert!(schema.get_field_entry(price_field).unwrap().fast);
2373
2374 let category_field = schema.get_field("category").unwrap();
2375 assert!(schema.get_field_entry(category_field).unwrap().fast);
2376
2377 let name_field = schema.get_field("name").unwrap();
2378 assert!(!schema.get_field_entry(name_field).unwrap().fast);
2379 }
2380
2381 #[test]
2382 fn test_primary_attribute() {
2383 let sdl = r#"
2384 index documents {
2385 field id: text [primary, stored]
2386 field title: text [indexed, stored]
2387 }
2388 "#;
2389
2390 let indexes = parse_sdl(sdl).unwrap();
2391 assert_eq!(indexes.len(), 1);
2392 let index = &indexes[0];
2393 assert_eq!(index.fields.len(), 2);
2394
2395 let id_field = &index.fields[0];
2397 assert!(id_field.primary, "id should be primary");
2398 assert!(id_field.fast, "primary implies fast");
2399 assert!(id_field.indexed, "primary implies indexed");
2400
2401 assert!(!index.fields[1].primary);
2403
2404 let schema = index.to_schema();
2406 let id = schema.get_field("id").unwrap();
2407 let id_entry = schema.get_field_entry(id).unwrap();
2408 assert!(id_entry.primary_key);
2409 assert!(id_entry.fast);
2410 assert!(id_entry.indexed);
2411
2412 let title = schema.get_field("title").unwrap();
2413 assert!(!schema.get_field_entry(title).unwrap().primary_key);
2414
2415 assert_eq!(schema.primary_field(), Some(id));
2417 }
2418
2419 #[test]
2420 fn test_primary_with_other_attributes() {
2421 let sdl = r#"
2422 index documents {
2423 field id: text<simple> [primary, indexed, stored]
2424 field body: text [indexed]
2425 }
2426 "#;
2427
2428 let indexes = parse_sdl(sdl).unwrap();
2429 let id_field = &indexes[0].fields[0];
2430 assert!(id_field.primary);
2431 assert!(id_field.indexed);
2432 assert!(id_field.stored);
2433 assert!(id_field.fast);
2434 assert_eq!(id_field.tokenizer, Some("simple".to_string()));
2435 }
2436
2437 #[test]
2438 fn test_primary_only_one_allowed() {
2439 let sdl = r#"
2440 index documents {
2441 field id: text [primary]
2442 field alt_id: text [primary]
2443 }
2444 "#;
2445
2446 let result = parse_sdl(sdl);
2447 assert!(result.is_err());
2448 let err = result.unwrap_err().to_string();
2449 assert!(
2450 err.contains("primary key"),
2451 "Error should mention primary key: {}",
2452 err
2453 );
2454 }
2455
2456 #[test]
2457 fn test_primary_must_be_text() {
2458 let sdl = r#"
2459 index documents {
2460 field id: u64 [primary]
2461 }
2462 "#;
2463
2464 let result = parse_sdl(sdl);
2465 assert!(result.is_err());
2466 let err = result.unwrap_err().to_string();
2467 assert!(
2468 err.contains("text"),
2469 "Error should mention text type: {}",
2470 err
2471 );
2472 }
2473
2474 #[test]
2475 fn test_primary_cannot_be_multi() {
2476 let sdl = r#"
2477 index documents {
2478 field id: text [primary, stored<multi>]
2479 }
2480 "#;
2481
2482 let result = parse_sdl(sdl);
2483 assert!(result.is_err());
2484 let err = result.unwrap_err().to_string();
2485 assert!(err.contains("multi"), "Error should mention multi: {}", err);
2486 }
2487
2488 #[test]
2489 fn test_no_primary_field() {
2490 let sdl = r#"
2492 index documents {
2493 field title: text [indexed, stored]
2494 }
2495 "#;
2496
2497 let indexes = parse_sdl(sdl).unwrap();
2498 let schema = indexes[0].to_schema();
2499 assert!(schema.primary_field().is_none());
2500 }
2501
2502 #[test]
2503 fn test_reorder_attribute() {
2504 let sdl = r#"
2505 index documents {
2506 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>, reorder]
2507 field embedding2: sparse_vector [indexed<format: bmp>]
2508 }
2509 "#;
2510
2511 let indexes = parse_sdl(sdl).unwrap();
2512 assert_eq!(indexes[0].fields.len(), 2);
2513
2514 assert!(indexes[0].fields[0].reorder);
2516 assert!(!indexes[0].fields[1].reorder);
2518
2519 let schema = indexes[0].to_schema();
2521 let f1 = schema.get_field("embedding").unwrap();
2522 assert!(schema.get_field_entry(f1).unwrap().reorder);
2523
2524 let f2 = schema.get_field("embedding2").unwrap();
2525 assert!(!schema.get_field_entry(f2).unwrap().reorder);
2526
2527 assert!(!schema.reorder_on_merge());
2529 }
2530
2531 #[test]
2532 fn test_reorder_on_merge_index_option() {
2533 let sdl = r#"
2534 index documents {
2535 reorder_on_merge: true
2536 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2537 }
2538 "#;
2539
2540 let indexes = parse_sdl(sdl).unwrap();
2541 assert!(indexes[0].reorder_on_merge);
2542 let schema = indexes[0].to_schema();
2543 assert!(schema.reorder_on_merge());
2544
2545 let sdl_off = r#"
2547 index documents {
2548 reorder_on_merge: false
2549 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2550 }
2551 "#;
2552 let indexes = parse_sdl(sdl_off).unwrap();
2553 assert!(!indexes[0].reorder_on_merge);
2554 assert!(!indexes[0].to_schema().reorder_on_merge());
2555
2556 let schema_on = parse_sdl(sdl).unwrap()[0].to_schema();
2558 let json = serde_json::to_string(&schema_on).unwrap();
2559 let back: crate::dsl::Schema = serde_json::from_str(&json).unwrap();
2560 assert!(back.reorder_on_merge());
2561 }
2562}